import { randomUUID } from "node:crypto" import { AccessToken, RoomAgentDispatch, RoomConfiguration, } from "livekit-server-sdk" import { getVoiceAssistantAgentName, getVoiceAssistantBootstrap, getVoiceAssistantSiteUrl, } from "@/lib/voice-assistant/shared" import type { LiveKitTokenResponse, VoiceAssistantVisitorMetadata, } from "@/lib/voice-assistant/types" import { VOICE_ASSISTANT_SOURCE } from "@/lib/voice-assistant/types" function readRequiredEnv(name: string) { const value = process.env[name] if (!value) { throw new Error( `Missing required voice assistant environment variable: ${name}` ) } return value } export function getVoiceAssistantServerEnv() { const livekitUrl = readRequiredEnv("LIVEKIT_URL") const livekitApiKey = readRequiredEnv("LIVEKIT_API_KEY") const livekitApiSecret = readRequiredEnv("LIVEKIT_API_SECRET") const xaiApiKey = readRequiredEnv("XAI_API_KEY") const realtimeModel = process.env.XAI_REALTIME_MODEL || "grok-4-1-fast-non-reasoning" return { livekitUrl, livekitApiKey, livekitApiSecret, xaiApiKey, realtimeModel, siteUrl: getVoiceAssistantSiteUrl(), } } function buildRoomName() { return `rmv-voice-${randomUUID().slice(0, 8)}` } function buildParticipantIdentity() { return `website-visitor-${randomUUID().slice(0, 10)}` } function buildVisitorMetadata(pathname: string): VoiceAssistantVisitorMetadata { const siteUrl = getVoiceAssistantSiteUrl() const safePathname = pathname.startsWith("/") ? pathname : `/${pathname}` return { source: VOICE_ASSISTANT_SOURCE, pathname: safePathname, pageUrl: new URL(safePathname, siteUrl).toString(), startedAt: new Date().toISOString(), } } export async function createVoiceAssistantTokenResponse( pathname: string ): Promise { const env = getVoiceAssistantServerEnv() const roomName = buildRoomName() const participantIdentity = buildParticipantIdentity() const visitorMetadata = buildVisitorMetadata(pathname) const agentName = getVoiceAssistantAgentName() const token = new AccessToken(env.livekitApiKey, env.livekitApiSecret, { identity: participantIdentity, name: "Website visitor", metadata: JSON.stringify(visitorMetadata), ttl: "1 hour", }) token.addGrant({ roomJoin: true, room: roomName, canPublish: true, canPublishData: true, canSubscribe: true, }) token.roomConfig = new RoomConfiguration({ agents: [ new RoomAgentDispatch({ agentName, metadata: JSON.stringify(visitorMetadata), }), ], }) return { serverUrl: env.livekitUrl, participantToken: await token.toJwt(), roomName, participantIdentity, bootstrap: getVoiceAssistantBootstrap(), } }