import process from "node:process"; import { ECHO_LIVE_MODEL, ECHO_LIVE_VOICE, liveInstructions } from "./gemini-policy"; // Mints a short-lived, single-use Google token locked to Echo's model, voice and instructions. // The real GEMINI_API_KEY never leaves the server. export async function createLiveToken(name: string) { const key = process.env["GEMINI_API_KEY"]; return mintLiveToken(key, { model: `models/${ECHO_LIVE_MODEL}`, generationConfig: { responseModalities: ["AUDIO"], speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: ECHO_LIVE_VOICE } } } }, systemInstruction: { parts: [{ text: liveInstructions(name) }] }, inputAudioTranscription: {}, outputAudioTranscription: {}, tools: [{ functionDeclarations: [ { name: "get_conversation_history", description: "Read only the caller's saved conversations and messages.", parameters: { type: "OBJECT", properties: {} } }, { name: "build_website", description: "Write and show a live runnable web page preview. Pass a full description including changes to the previous build.", parameters: { type: "OBJECT", properties: { request: { type: "STRING" } }, required: ["request"] } }, { name: "list_projects", description: "List the caller's saved website projects (newest first, numbered).", parameters: { type: "OBJECT", properties: {} } }, { name: "open_project", description: "Open a saved project on screen so later build_website calls continue it. query is its name words, its number from list_projects, or 'latest'.", parameters: { type: "OBJECT", properties: { query: { type: "STRING" } }, required: ["query"] } }, { name: "connect_domain", description: "Connect the caller's own custom domain (e.g. mywebsite.com) to the current published project when they ask to connect, link or add a domain.", parameters: { type: "OBJECT", properties: { domain: { type: "STRING" } }, required: ["domain"] } }, { name: "deploy_website", description: "Publish the latest built website when the caller asks to publish, deploy or share it.", parameters: { type: "OBJECT", properties: {} } }, ] }], }); } export async function mintLiveToken(key: string | undefined, setup: Record) { if (!key) return { error: "Gemini key is missing. Add GEMINI_API_KEY in project secrets." } as const; const now = Date.now(); const response = await fetch("https://generativelanguage.googleapis.com/v1alpha/auth_tokens", { method: "POST", headers: { "x-goog-api-key": key, "content-type": "application/json" }, body: JSON.stringify({ uses: 1, expireTime: new Date(now + 30 * 60_000).toISOString(), newSessionExpireTime: new Date(now + 60_000).toISOString(), bidiGenerateContentSetup: setup, }), }); if (!response.ok) { if (response.status === 404) return { error: "The configured Gemini Live model is unavailable. Check Google model access before starting a new call." } as const; if (response.status === 429) return { error: "Today's free Gemini voice limit is reached. Try again after it resets." } as const; if (response.status === 401 || response.status === 403) return { error: "Google rejected your Gemini key. Check it in project secrets." } as const; return { error: "Live voice could not start right now. Try again in a moment." } as const; } const token = (await response.json()) as { name?: string }; if (!token.name) return { error: "Live voice could not start right now." } as const; return { token: token.name } as const; }