Rocky_Mountain_Vending/lib/voice-assistant/server.ts

99 lines
2.7 KiB
TypeScript

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<LiveKitTokenResponse> {
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(),
}
}