@tanstack/ai-gemini 0.23.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/esm/adapters/image.d.ts +14 -7
- package/dist/esm/adapters/image.js +27 -56
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/video.js +2 -1
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.d.ts +8 -5
- package/dist/esm/experimental/text-interactions/adapter.js +20 -18
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/image/image-provider-options.d.ts +167 -20
- package/dist/esm/image/image-provider-options.js +52 -5
- package/dist/esm/image/image-provider-options.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/model-meta.d.ts +15 -2
- package/dist/esm/model-meta.js +82 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.js +1 -5
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/client.js.map +1 -1
- package/dist/esm/realtime/token.js +3 -2
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/realtime/utils.js +4 -1
- package/dist/esm/realtime/utils.js.map +1 -1
- package/dist/esm/tools/code-execution-tool.js +3 -3
- package/dist/esm/tools/code-execution-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.js +4 -4
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.js +4 -4
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/gemini-provider-tool.d.ts +16 -0
- package/dist/esm/tools/gemini-provider-tool.js +42 -0
- package/dist/esm/tools/gemini-provider-tool.js.map +1 -0
- package/dist/esm/tools/google-maps-tool.js +4 -4
- package/dist/esm/tools/google-maps-tool.js.map +1 -1
- package/dist/esm/tools/google-search-retriveal-tool.js +4 -4
- package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
- package/dist/esm/tools/google-search-tool.js +4 -4
- package/dist/esm/tools/google-search-tool.js.map +1 -1
- package/dist/esm/tools/tool-converter.js +5 -3
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/tools/url-context-tool.js +3 -3
- package/dist/esm/tools/url-context-tool.js.map +1 -1
- package/dist/esm/usage.js +1 -3
- package/dist/esm/usage.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/image.ts +123 -36
- package/src/experimental/text-interactions/adapter.ts +31 -18
- package/src/image/image-provider-options.ts +226 -25
- package/src/index.ts +28 -0
- package/src/model-meta.ts +118 -1
- package/src/tools/code-execution-tool.ts +9 -6
- package/src/tools/computer-use-tool.ts +19 -13
- package/src/tools/file-search-tool.ts +13 -7
- package/src/tools/gemini-provider-tool.ts +60 -0
- package/src/tools/google-maps-tool.ts +13 -7
- package/src/tools/google-search-retriveal-tool.ts +15 -7
- package/src/tools/google-search-tool.ts +13 -7
- package/src/tools/tool-converter.ts +5 -4
- package/src/tools/url-context-tool.ts +9 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","names":[],"sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { createRealtimeEventEmitter } from '@tanstack/ai'\nimport { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils'\nimport { GeminiLiveClient } from './client'\nimport type { LiveResponse } from './client'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeAdapter,\n RealtimeConnection,\n RealtimeMessage,\n RealtimeMode,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types'\n\n/**\n * Creates a Gemini realtime adapter for client-side use.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { geminiRealtime } from '@tanstack/ai-gemini'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: geminiRealtime(),\n * onGoAway: () => client.updateSession({ ... }) // Resume session with new config (available only for Gemini Live adapter)\n * })\n *\n * ```\n */\nexport function geminiRealtime(\n options: GeminiRealtimeOptions = {},\n): RealtimeAdapter {\n return {\n provider: 'gemini',\n\n connect(\n token: RealtimeToken,\n clientTools?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n return createWebSocketConnection(token, options.model, clientTools)\n },\n }\n}\n\n/**\n * Creates a WebSocket connection to Gemini's realtime API\n */\nasync function createWebSocketConnection(\n token: RealtimeToken,\n model: GeminiRealtimeModel = 'gemini-3.1-flash-live-preview',\n tools?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter()\n\n // Current state\n let currentMode: RealtimeMode = 'idle'\n let currentMessageId: string | null = null\n let messageIdCounter = 0\n\n function generateMessageId(): string {\n return `gemini-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n const client = new GeminiLiveClient(token.token, model, tools)\n\n let message: RealtimeMessage = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n\n let pendingAssistantResponse = ''\n\n client.onClose = () => {\n emit('status_change', { status: 'idle' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onError = (error) => {\n emit('error', { error })\n emit('status_change', { status: 'error' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onReceiveResponse = (response: LiveResponse) => {\n switch (response.type) {\n case 'text':\n message.parts.push({\n type: 'text',\n content: response.data,\n })\n break\n case 'audio': {\n // Decode once, then reuse for both the stored message part and playback.\n const pcm = base64ToArrayBuffer(response.data.audioData)\n message.parts.push({\n type: 'audio',\n transcript: response.data.transcript,\n audioData: pcm,\n })\n if (currentMode !== 'speaking') {\n currentMode = 'speaking'\n emit('mode_change', { mode: 'speaking' })\n }\n audioPlayer.play(pcm).catch((error) => emit('error', { error }))\n break\n }\n case 'go_away':\n emit('go_away', { timeLeft: response.data.timeLeft })\n break\n case 'usage_metadata':\n emit('usage', {\n completionTokens: response.data.responseTokenCount ?? 0,\n promptTokens: response.data.promptTokenCount ?? 0,\n totalTokens: response.data.totalTokenCount ?? 0,\n })\n break\n case 'input_transcription':\n if (response.data.finished && currentMode !== 'thinking') {\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n }\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: response.data.text,\n role: 'user',\n })\n break\n case 'output_transcription':\n pendingAssistantResponse += response.data.text\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: pendingAssistantResponse,\n role: 'assistant',\n })\n break\n case 'interrupted':\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n break\n case 'tool_call':\n for (const tool of response.data.functionCalls || []) {\n if (tool.id && tool.name) {\n emit('tool_call', {\n toolCallId: tool.id,\n input: tool.args,\n toolName: tool.name,\n })\n }\n }\n break\n case 'turn_complete':\n currentMessageId = generateMessageId()\n message.id = currentMessageId\n message.timestamp = Date.now()\n message.parts.push({\n type: 'text',\n content: pendingAssistantResponse,\n })\n emit('message_complete', { message })\n\n pendingAssistantResponse = ''\n message = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n break\n case 'setup_complete':\n emit('status_change', { status: 'connected' })\n break\n case 'error':\n emit('error', {\n error: new Error(response.data),\n })\n break\n case 'thought':\n case 'session_resumption_update':\n // Handled internally by GeminiLiveClient; not surfaced to the client.\n break\n }\n }\n\n await client.connect()\n\n const audioStreamer = new AudioStreamer(client)\n const audioPlayer = new AudioPlayer()\n try {\n await audioPlayer.init()\n await audioStreamer.start()\n } catch (error) {\n // Tear down the socket + audio graph if mic/worklet setup fails (e.g. the\n // user denied microphone access) rather than leaking an open connection.\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n throw error\n }\n\n const connection: RealtimeConnection = {\n async disconnect() {\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n currentMode = 'idle'\n emit('status_change', { status: 'idle' })\n },\n\n async startAudioCapture() {\n // Audio capture is established during connection setup\n audioStreamer.startAudioCapture()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n audioStreamer.stopAudioCapture()\n currentMode = 'idle'\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n client.sendTextMessage(text)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendImage(imageData: string, mimeType: string) {\n client.sendImageMessage(imageData, mimeType)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendToolResult(callId: string, result: string) {\n client.sendToolResponse([\n {\n id: callId,\n response: {\n result,\n },\n },\n ])\n },\n\n updateSession(config) {\n client.updateSession(config).catch((error) => emit('error', { error }))\n emit('status_change', { status: 'reconnecting' })\n },\n\n updateToken(newToken) {\n client.updateToken(newToken)\n emit('status_change', { status: 'reconnecting' })\n },\n\n interrupt() {\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n },\n on: realtimeEventEmitterOn,\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n return audioStreamer.inputLevel\n },\n\n get outputLevel() {\n return audioPlayer.outputLevel\n },\n\n getInputFrequencyData() {\n return audioStreamer.inputFrequencyData\n },\n\n getOutputFrequencyData() {\n return audioPlayer.outputFrequencyData\n },\n\n getInputTimeDomainData() {\n return audioStreamer.inputTimeDomainData\n },\n\n getOutputTimeDomainData() {\n return audioPlayer.outputTimeDomainData\n },\n\n get inputSampleRate() {\n return audioStreamer.inputSampleRate\n },\n\n get outputSampleRate() {\n return audioPlayer.outputSampleRate\n },\n }\n },\n }\n\n return connection\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","names":[],"sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { createRealtimeEventEmitter } from '@tanstack/ai'\nimport { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils'\nimport { GeminiLiveClient } from './client'\nimport type { LiveResponse } from './client'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeAdapter,\n RealtimeConnection,\n RealtimeMessage,\n RealtimeMode,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types'\n\n/**\n * Creates a Gemini realtime adapter for client-side use.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { geminiRealtime } from '@tanstack/ai-gemini'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: geminiRealtime(),\n * onGoAway: () => client.updateSession({ ... }) // Resume session with new config (available only for Gemini Live adapter)\n * })\n *\n * ```\n */\nexport function geminiRealtime(\n options: GeminiRealtimeOptions = {},\n): RealtimeAdapter {\n return {\n provider: 'gemini',\n\n connect(\n token: RealtimeToken,\n clientTools?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n return createWebSocketConnection(token, options.model, clientTools)\n },\n }\n}\n\n/**\n * Creates a WebSocket connection to Gemini's realtime API\n */\nasync function createWebSocketConnection(\n token: RealtimeToken,\n model: GeminiRealtimeModel = 'gemini-3.1-flash-live-preview',\n tools?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter()\n\n // Current state\n let currentMode: RealtimeMode = 'idle'\n let currentMessageId: string | null = null\n let messageIdCounter = 0\n\n function generateMessageId(): string {\n return `gemini-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n const client = new GeminiLiveClient(token.token, model, tools)\n\n let message: RealtimeMessage = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n\n let pendingAssistantResponse = ''\n\n client.onClose = () => {\n emit('status_change', { status: 'idle' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onError = (error) => {\n emit('error', { error })\n emit('status_change', { status: 'error' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onReceiveResponse = (response: LiveResponse) => {\n switch (response.type) {\n case 'text':\n message.parts.push({\n type: 'text',\n content: response.data,\n })\n break\n case 'audio': {\n // Decode once, then reuse for both the stored message part and playback.\n const pcm = base64ToArrayBuffer(response.data.audioData)\n message.parts.push({\n type: 'audio',\n transcript: response.data.transcript,\n audioData: pcm,\n })\n if (currentMode !== 'speaking') {\n currentMode = 'speaking'\n emit('mode_change', { mode: 'speaking' })\n }\n audioPlayer.play(pcm).catch((error) => emit('error', { error }))\n break\n }\n case 'go_away':\n emit('go_away', { timeLeft: response.data.timeLeft })\n break\n case 'usage_metadata':\n emit('usage', {\n completionTokens: response.data.responseTokenCount ?? 0,\n promptTokens: response.data.promptTokenCount ?? 0,\n totalTokens: response.data.totalTokenCount ?? 0,\n })\n break\n case 'input_transcription':\n if (response.data.finished && currentMode !== 'thinking') {\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n }\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: response.data.text,\n role: 'user',\n })\n break\n case 'output_transcription':\n pendingAssistantResponse += response.data.text\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: pendingAssistantResponse,\n role: 'assistant',\n })\n break\n case 'interrupted':\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n break\n case 'tool_call':\n for (const tool of response.data.functionCalls || []) {\n if (tool.id && tool.name) {\n emit('tool_call', {\n toolCallId: tool.id,\n input: tool.args,\n toolName: tool.name,\n })\n }\n }\n break\n case 'turn_complete':\n currentMessageId = generateMessageId()\n message.id = currentMessageId\n message.timestamp = Date.now()\n message.parts.push({\n type: 'text',\n content: pendingAssistantResponse,\n })\n emit('message_complete', { message })\n\n pendingAssistantResponse = ''\n message = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n break\n case 'setup_complete':\n emit('status_change', { status: 'connected' })\n break\n case 'error':\n emit('error', {\n error: new Error(response.data),\n })\n break\n case 'thought':\n case 'session_resumption_update':\n // Handled internally by GeminiLiveClient; not surfaced to the client.\n break\n }\n }\n\n await client.connect()\n\n const audioStreamer = new AudioStreamer(client)\n const audioPlayer = new AudioPlayer()\n try {\n await audioPlayer.init()\n await audioStreamer.start()\n } catch (error) {\n // Tear down the socket + audio graph if mic/worklet setup fails (e.g. the\n // user denied microphone access) rather than leaking an open connection.\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n throw error\n }\n\n const connection: RealtimeConnection = {\n async disconnect() {\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n currentMode = 'idle'\n emit('status_change', { status: 'idle' })\n },\n\n async startAudioCapture() {\n // Audio capture is established during connection setup\n audioStreamer.startAudioCapture()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n audioStreamer.stopAudioCapture()\n currentMode = 'idle'\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n client.sendTextMessage(text)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendImage(imageData: string, mimeType: string) {\n client.sendImageMessage(imageData, mimeType)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendToolResult(callId: string, result: string) {\n client.sendToolResponse([\n {\n id: callId,\n response: {\n result,\n },\n },\n ])\n },\n\n updateSession(config) {\n client.updateSession(config).catch((error) => emit('error', { error }))\n emit('status_change', { status: 'reconnecting' })\n },\n\n updateToken(newToken) {\n client.updateToken(newToken)\n emit('status_change', { status: 'reconnecting' })\n },\n\n interrupt() {\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n },\n on: realtimeEventEmitterOn,\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n return audioStreamer.inputLevel\n },\n\n get outputLevel() {\n return audioPlayer.outputLevel\n },\n\n getInputFrequencyData() {\n return audioStreamer.inputFrequencyData\n },\n\n getOutputFrequencyData() {\n return audioPlayer.outputFrequencyData\n },\n\n getInputTimeDomainData() {\n return audioStreamer.inputTimeDomainData\n },\n\n getOutputTimeDomainData() {\n return audioPlayer.outputTimeDomainData\n },\n\n get inputSampleRate() {\n return audioStreamer.inputSampleRate\n },\n\n get outputSampleRate() {\n return audioPlayer.outputSampleRate\n },\n }\n },\n }\n\n return connection\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/realtime/client.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from '@tanstack/ai'\nimport {\n ActivityHandling,\n EndSensitivity,\n Modality,\n StartSensitivity,\n TurnCoverage,\n} from '@google/genai'\nimport type {\n ContextWindowCompressionConfig,\n FunctionDeclaration,\n FunctionResponse,\n LiveClientMessage,\n LiveServerGoAway,\n LiveServerMessage,\n LiveServerSessionResumptionUpdate,\n LiveServerToolCall,\n ThinkingConfig,\n UsageMetadata,\n} from '@google/genai'\nimport type {\n AnyClientTool,\n RealtimeSessionConfig,\n RealtimeToken,\n RealtimeToolConfig,\n} from '@tanstack/ai'\nimport type {\n GeminiRealtimeModel,\n GeminiRealtimeProviderOptions,\n GeminiRealtimeVoice,\n} from './types'\n\n/** Build a Gemini FunctionDeclaration from an isomorphic client tool (Zod). */\nfunction clientToolToDeclaration(tool: AnyClientTool): FunctionDeclaration {\n return {\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: convertSchemaToJsonSchema(tool.inputSchema),\n responseJsonSchema: convertSchemaToJsonSchema(tool.outputSchema),\n }\n}\n\n/** Build a Gemini FunctionDeclaration from an already-serialized tool config. */\nfunction toolConfigToDeclaration(\n tool: RealtimeToolConfig,\n): FunctionDeclaration {\n return {\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: tool.inputSchema,\n responseJsonSchema: tool.outputSchema,\n }\n}\n\ninterface LiveResponsePayloads {\n text: string\n thought: string\n audio: { audioData: string; transcript: string }\n setup_complete: string\n interrupted: string\n turn_complete: string\n tool_call: LiveServerToolCall\n session_resumption_update: LiveServerSessionResumptionUpdate\n go_away: LiveServerGoAway\n usage_metadata: UsageMetadata\n error: string\n input_transcription: { text: string; finished: boolean }\n output_transcription: { text: string; finished: boolean }\n}\n\nexport type MultimodalLiveResponseType = keyof LiveResponsePayloads\n\nexport type LiveResponse = {\n [K in MultimodalLiveResponseType]: {\n type: K\n data: LiveResponsePayloads[K]\n endOfTurn: boolean\n }\n}[MultimodalLiveResponseType]\n\n/**\n * Parses response messages from the Gemini Live API\n */\n/**\n * Parses ALL response types from a single server message.\n * The server can now bundle multiple fields (e.g. audio + transcription)\n * in the same message. Returns an array of response objects.\n */\nexport function parseResponseMessages(\n data: LiveServerMessage,\n): Array<LiveResponse> {\n const responses: Array<LiveResponse> = []\n const serverContent = data.serverContent\n const parts = serverContent?.modelTurn?.parts\n\n // Setup complete (exclusive — no other fields expected)\n if (data.setupComplete) {\n responses.push({ type: 'setup_complete', data: '', endOfTurn: false })\n return responses\n }\n\n // Tool call (exclusive)\n if (data.toolCall) {\n responses.push({\n type: 'tool_call',\n data: data.toolCall,\n endOfTurn: false,\n })\n return responses\n }\n\n if (data.sessionResumptionUpdate) {\n responses.push({\n type: 'session_resumption_update',\n data: data.sessionResumptionUpdate,\n endOfTurn: false,\n })\n }\n\n if (data.goAway) {\n responses.push({ type: 'go_away', data: data.goAway, endOfTurn: false })\n }\n\n if (data.usageMetadata) {\n responses.push({\n type: 'usage_metadata',\n data: data.usageMetadata,\n endOfTurn: false,\n })\n }\n\n // Audio data from model turn parts\n if (parts?.length) {\n for (const part of parts) {\n if (part.inlineData?.data) {\n responses.push({\n type: 'audio',\n data: {\n audioData: part.inlineData.data,\n // The transcription is independent to the model turn, which means it doesn't imply any ordering between transcription and model turn.\n transcript: '',\n },\n endOfTurn: false,\n })\n } else if (part.text) {\n responses.push({\n type: part.thought ? 'thought' : 'text',\n data: part.text,\n endOfTurn: false,\n })\n }\n }\n }\n\n // Transcriptions — checked independently, NOT in else-if with audio\n if (serverContent?.inputTranscription) {\n responses.push({\n type: 'input_transcription',\n data: {\n text: serverContent.inputTranscription.text || '',\n finished: serverContent.inputTranscription.finished || false,\n },\n endOfTurn: false,\n })\n }\n\n if (serverContent?.outputTranscription) {\n responses.push({\n type: 'output_transcription',\n data: {\n text: serverContent.outputTranscription.text || '',\n finished: serverContent.outputTranscription.finished || false,\n },\n endOfTurn: false,\n })\n }\n\n // Interrupted\n if (serverContent?.interrupted) {\n responses.push({ type: 'interrupted', data: '', endOfTurn: false })\n }\n\n // Turn complete\n if (serverContent?.turnComplete) {\n responses.push({ type: 'turn_complete', data: '', endOfTurn: true })\n }\n\n return responses\n}\n\nexport class GeminiLiveClient {\n private token: string | null = null\n private model: GeminiRealtimeModel | null = null\n\n private readonly responseModalities: Array<Modality> = [Modality.AUDIO]\n private systemInstructions = ''\n private googleGrounding = false\n private voiceName: GeminiRealtimeVoice = 'Puck'\n private temperature = 1.0\n private inputAudioTranscription = false\n private outputAudioTranscription = false\n private contextWindowCompression: ContextWindowCompressionConfig | undefined\n private proactiveAudio = false\n private enableAffectiveDialog = false\n private thinkingConfig: ThinkingConfig | undefined\n private speechLanguageCode: string | undefined\n\n private maxOutputTokens: number | undefined\n private functionDeclarations: Array<FunctionDeclaration> = []\n\n private readonly automaticActivityDetection = {\n disabled: false,\n silence_duration_ms: 2000,\n prefix_padding_ms: 500,\n end_of_speech_sensitivity: EndSensitivity.END_SENSITIVITY_UNSPECIFIED,\n start_of_speech_sensitivity: StartSensitivity.START_SENSITIVITY_UNSPECIFIED,\n }\n\n private readonly activityHandling =\n ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED\n\n private webSocket: WebSocket | null = null\n private lastResumptionUpdate: LiveServerSessionResumptionUpdate | null = null\n private setupComplete = false\n private connected = false\n\n public onReceiveResponse: (response: LiveResponse) => void = () => {}\n public onOpen: () => void = () => {}\n public onClose: () => void = () => {}\n public onError: (error: Error) => void = () => {}\n\n constructor(\n token: string,\n model: GeminiRealtimeModel,\n tools?: ReadonlyArray<AnyClientTool>,\n ) {\n this.token = token\n this.model = model\n\n if (tools) {\n this.functionDeclarations = tools.map(clientToolToDeclaration)\n }\n }\n\n get isConnected() {\n return this.connected\n }\n\n get isSetupComplete() {\n return this.setupComplete\n }\n\n /**\n * Connection management\n */\n connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const socket = new WebSocket(\n `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained?access_token=${this.token}`,\n )\n this.webSocket = socket\n\n // The browser fires `onerror` then `onclose` on an abnormal close; this\n // flag stops the close handler from overwriting the surfaced error with a\n // benign \"closed\" signal.\n let errored = false\n\n socket.onclose = () => {\n this.connected = false\n this.setupComplete = false\n if (!errored) this.onClose()\n reject(new Error('WebSocket closed before setup completed'))\n }\n\n socket.onerror = () => {\n errored = true\n this.connected = false\n this.setupComplete = false\n const error = new Error('Gemini realtime WebSocket connection error')\n this.onError(error)\n reject(error)\n }\n\n socket.onopen = () => {\n this.connected = true\n this.onOpen()\n resolve()\n }\n\n socket.onmessage = (event) => {\n void this.onReceiveMessage(event)\n }\n })\n }\n\n disconnect() {\n if (this.webSocket) {\n // Detach handlers first so a deliberate teardown (e.g. during a\n // reconnect) doesn't emit a spurious close/error to the client.\n this.webSocket.onclose = null\n this.webSocket.onerror = null\n this.webSocket.onopen = null\n this.webSocket.onmessage = null\n this.webSocket.close()\n this.webSocket = null\n }\n this.connected = false\n this.setupComplete = false\n }\n\n /**\n * Session management\n */\n sendInitialSetupMessage(resume = false) {\n const tools = this.functionDeclarations\n\n const setup: NonNullable<LiveClientMessage['setup']> = {\n model: `models/${this.model}`,\n generationConfig: {\n responseModalities: this.responseModalities,\n temperature: this.temperature,\n speechConfig: {\n languageCode: this.speechLanguageCode,\n voiceConfig: {\n prebuiltVoiceConfig: {\n voiceName: this.voiceName,\n },\n },\n },\n enableAffectiveDialog: this.enableAffectiveDialog,\n maxOutputTokens: this.maxOutputTokens,\n thinkingConfig: this.thinkingConfig,\n },\n sessionResumption: {\n transparent: true,\n handle: resume ? this.lastResumptionUpdate?.newHandle : undefined,\n },\n contextWindowCompression: this.contextWindowCompression,\n proactivity: {\n proactiveAudio: this.proactiveAudio,\n },\n systemInstruction: { parts: [{ text: this.systemInstructions }] },\n tools: [{ functionDeclarations: tools }],\n realtimeInputConfig: {\n automaticActivityDetection: {\n disabled: this.automaticActivityDetection.disabled,\n silenceDurationMs:\n this.automaticActivityDetection.silence_duration_ms,\n prefixPaddingMs: this.automaticActivityDetection.prefix_padding_ms,\n endOfSpeechSensitivity:\n this.automaticActivityDetection.end_of_speech_sensitivity,\n startOfSpeechSensitivity:\n this.automaticActivityDetection.start_of_speech_sensitivity,\n },\n activityHandling: this.activityHandling,\n turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY,\n },\n }\n\n if (this.inputAudioTranscription) {\n setup.inputAudioTranscription = {}\n }\n\n if (this.outputAudioTranscription) {\n setup.outputAudioTranscription = {}\n }\n\n if (this.googleGrounding) {\n // Currently can't have both Google Search with custom tools.\n console.warn(\n 'Google Grounding enabled, removing custom function calls if any.',\n )\n setup.tools = [{ googleSearch: {} }]\n }\n\n this.sendMessage({ setup })\n }\n\n async restartSession(resume = false) {\n this.disconnect()\n await this.connect()\n this.sendInitialSetupMessage(resume)\n }\n\n updateToken(token: RealtimeToken) {\n this.token = token.token\n\n // Restart completely with the new model, or resume the existing session.\n const resume = !(token.config.model && this.model != token.config.model)\n if (!resume) {\n this.model = token.config.model as GeminiRealtimeModel\n }\n this.restartSession(resume).catch((err) =>\n this.onError(err instanceof Error ? err : new Error(String(err))),\n )\n }\n\n async updateSession(config: Partial<RealtimeSessionConfig>) {\n // model can only be set during initial setup\n if (config.model && !this.setupComplete) {\n this.model = config.model as GeminiRealtimeModel\n }\n\n if (config.instructions) {\n this.systemInstructions = config.instructions\n }\n\n if (config.tools) {\n this.functionDeclarations = config.tools.map(toolConfigToDeclaration)\n }\n\n if (config.maxOutputTokens) {\n // Gemini has no \"inf\" sentinel; treat it as \"no explicit limit\".\n this.maxOutputTokens =\n typeof config.maxOutputTokens === 'number'\n ? config.maxOutputTokens\n : undefined\n }\n\n if (config.temperature) {\n this.temperature = config.temperature\n }\n\n if (config.voice) {\n this.voiceName = config.voice as GeminiRealtimeVoice\n }\n\n const providerOptions = config.providerOptions as\n | GeminiRealtimeProviderOptions\n | undefined\n\n if (providerOptions?.googleGrounding) {\n this.googleGrounding = providerOptions.googleGrounding\n }\n\n if (providerOptions?.proactiveAudio) {\n this.proactiveAudio = providerOptions.proactiveAudio\n }\n\n if (providerOptions?.enableAffectiveDialog) {\n this.enableAffectiveDialog = providerOptions.enableAffectiveDialog\n }\n\n if (providerOptions?.contextWindowCompression) {\n this.contextWindowCompression = providerOptions.contextWindowCompression\n }\n\n if (providerOptions?.thinkingConfig) {\n this.thinkingConfig = providerOptions.thinkingConfig\n }\n\n if (providerOptions?.languageCode) {\n this.speechLanguageCode = providerOptions.languageCode\n }\n\n const includeTranscription =\n config.outputModalities?.includes('text') || false\n this.inputAudioTranscription = includeTranscription\n this.outputAudioTranscription = includeTranscription\n\n if (!this.setupComplete) {\n this.sendInitialSetupMessage()\n } else {\n return this.restartSession(true)\n }\n }\n\n /**\n * Message transmission & receiving\n */\n sendMessage(message: LiveClientMessage) {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(JSON.stringify(message))\n } else {\n this.onError(\n new Error('Cannot send message: Gemini realtime socket is not open'),\n )\n }\n }\n\n async onReceiveMessage(messageEvent: MessageEvent) {\n let jsonData\n if (messageEvent.data instanceof Blob) {\n jsonData = await messageEvent.data.text()\n } else if (messageEvent.data instanceof ArrayBuffer) {\n jsonData = new TextDecoder().decode(messageEvent.data)\n } else {\n jsonData = messageEvent.data\n }\n\n try {\n const messageData = JSON.parse(jsonData)\n // Parse all response types from this message (audio + transcription can coexist)\n const responses = parseResponseMessages(messageData)\n for (const response of responses) {\n if (\n response.type === 'session_resumption_update' &&\n response.data.resumable\n ) {\n this.lastResumptionUpdate = response.data\n }\n if (response.type === 'setup_complete') {\n this.setupComplete = true\n }\n this.onReceiveResponse(response)\n }\n } catch (err) {\n this.onError(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n sendRealtimeInputMessage(data: string, mimeType: string) {\n const blob = { mimeType, data }\n\n if (mimeType.startsWith('audio/')) {\n this.sendMessage({ realtimeInput: { audio: blob } })\n } else if (mimeType.startsWith('image/') || mimeType.startsWith('video/')) {\n this.sendMessage({ realtimeInput: { video: blob } })\n }\n }\n\n sendAudioMessage(base64PCM: string) {\n this.sendRealtimeInputMessage(base64PCM, 'audio/pcm')\n }\n\n sendImageMessage(base64: string, mimeType = 'image/jpeg') {\n this.sendRealtimeInputMessage(base64, mimeType)\n }\n\n sendTextMessage(text: string) {\n const message: LiveClientMessage = {\n realtimeInput: {\n text,\n },\n }\n this.sendMessage(message)\n }\n\n sendToolResponse(functionResponses: Array<FunctionResponse>) {\n const message: LiveClientMessage = {\n toolResponse: {\n functionResponses,\n },\n }\n this.sendMessage(message)\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/realtime/client.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from '@tanstack/ai'\nimport {\n ActivityHandling,\n EndSensitivity,\n Modality,\n StartSensitivity,\n TurnCoverage,\n} from '@google/genai'\nimport type {\n ContextWindowCompressionConfig,\n FunctionDeclaration,\n FunctionResponse,\n LiveClientMessage,\n LiveServerGoAway,\n LiveServerMessage,\n LiveServerSessionResumptionUpdate,\n LiveServerToolCall,\n ThinkingConfig,\n UsageMetadata,\n} from '@google/genai'\nimport type {\n AnyClientTool,\n RealtimeSessionConfig,\n RealtimeToken,\n RealtimeToolConfig,\n} from '@tanstack/ai'\nimport type {\n GeminiRealtimeModel,\n GeminiRealtimeProviderOptions,\n GeminiRealtimeVoice,\n} from './types'\n\n/** Build a Gemini FunctionDeclaration from an isomorphic client tool (Zod). */\nfunction clientToolToDeclaration(tool: AnyClientTool): FunctionDeclaration {\n return {\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: convertSchemaToJsonSchema(tool.inputSchema),\n responseJsonSchema: convertSchemaToJsonSchema(tool.outputSchema),\n }\n}\n\n/** Build a Gemini FunctionDeclaration from an already-serialized tool config. */\nfunction toolConfigToDeclaration(\n tool: RealtimeToolConfig,\n): FunctionDeclaration {\n return {\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: tool.inputSchema,\n responseJsonSchema: tool.outputSchema,\n }\n}\n\ninterface LiveResponsePayloads {\n text: string\n thought: string\n audio: { audioData: string; transcript: string }\n setup_complete: string\n interrupted: string\n turn_complete: string\n tool_call: LiveServerToolCall\n session_resumption_update: LiveServerSessionResumptionUpdate\n go_away: LiveServerGoAway\n usage_metadata: UsageMetadata\n error: string\n input_transcription: { text: string; finished: boolean }\n output_transcription: { text: string; finished: boolean }\n}\n\nexport type MultimodalLiveResponseType = keyof LiveResponsePayloads\n\nexport type LiveResponse = {\n [K in MultimodalLiveResponseType]: {\n type: K\n data: LiveResponsePayloads[K]\n endOfTurn: boolean\n }\n}[MultimodalLiveResponseType]\n\n/**\n * Parses response messages from the Gemini Live API\n */\n/**\n * Parses ALL response types from a single server message.\n * The server can now bundle multiple fields (e.g. audio + transcription)\n * in the same message. Returns an array of response objects.\n */\nexport function parseResponseMessages(\n data: LiveServerMessage,\n): Array<LiveResponse> {\n const responses: Array<LiveResponse> = []\n const serverContent = data.serverContent\n const parts = serverContent?.modelTurn?.parts\n\n // Setup complete (exclusive — no other fields expected)\n if (data.setupComplete) {\n responses.push({ type: 'setup_complete', data: '', endOfTurn: false })\n return responses\n }\n\n // Tool call (exclusive)\n if (data.toolCall) {\n responses.push({\n type: 'tool_call',\n data: data.toolCall,\n endOfTurn: false,\n })\n return responses\n }\n\n if (data.sessionResumptionUpdate) {\n responses.push({\n type: 'session_resumption_update',\n data: data.sessionResumptionUpdate,\n endOfTurn: false,\n })\n }\n\n if (data.goAway) {\n responses.push({ type: 'go_away', data: data.goAway, endOfTurn: false })\n }\n\n if (data.usageMetadata) {\n responses.push({\n type: 'usage_metadata',\n data: data.usageMetadata,\n endOfTurn: false,\n })\n }\n\n // Audio data from model turn parts\n if (parts?.length) {\n for (const part of parts) {\n if (part.inlineData?.data) {\n responses.push({\n type: 'audio',\n data: {\n audioData: part.inlineData.data,\n // The transcription is independent to the model turn, which means it doesn't imply any ordering between transcription and model turn.\n transcript: '',\n },\n endOfTurn: false,\n })\n } else if (part.text) {\n responses.push({\n type: part.thought ? 'thought' : 'text',\n data: part.text,\n endOfTurn: false,\n })\n }\n }\n }\n\n // Transcriptions — checked independently, NOT in else-if with audio\n if (serverContent?.inputTranscription) {\n responses.push({\n type: 'input_transcription',\n data: {\n text: serverContent.inputTranscription.text || '',\n finished: serverContent.inputTranscription.finished || false,\n },\n endOfTurn: false,\n })\n }\n\n if (serverContent?.outputTranscription) {\n responses.push({\n type: 'output_transcription',\n data: {\n text: serverContent.outputTranscription.text || '',\n finished: serverContent.outputTranscription.finished || false,\n },\n endOfTurn: false,\n })\n }\n\n // Interrupted\n if (serverContent?.interrupted) {\n responses.push({ type: 'interrupted', data: '', endOfTurn: false })\n }\n\n // Turn complete\n if (serverContent?.turnComplete) {\n responses.push({ type: 'turn_complete', data: '', endOfTurn: true })\n }\n\n return responses\n}\n\nexport class GeminiLiveClient {\n private token: string | null = null\n private model: GeminiRealtimeModel | null = null\n\n private readonly responseModalities: Array<Modality> = [Modality.AUDIO]\n private systemInstructions = ''\n private googleGrounding = false\n private voiceName: GeminiRealtimeVoice = 'Puck'\n private temperature = 1.0\n private inputAudioTranscription = false\n private outputAudioTranscription = false\n private contextWindowCompression: ContextWindowCompressionConfig | undefined\n private proactiveAudio = false\n private enableAffectiveDialog = false\n private thinkingConfig: ThinkingConfig | undefined\n private speechLanguageCode: string | undefined\n\n private maxOutputTokens: number | undefined\n private functionDeclarations: Array<FunctionDeclaration> = []\n\n private readonly automaticActivityDetection = {\n disabled: false,\n silence_duration_ms: 2000,\n prefix_padding_ms: 500,\n end_of_speech_sensitivity: EndSensitivity.END_SENSITIVITY_UNSPECIFIED,\n start_of_speech_sensitivity: StartSensitivity.START_SENSITIVITY_UNSPECIFIED,\n }\n\n private readonly activityHandling =\n ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED\n\n private webSocket: WebSocket | null = null\n private lastResumptionUpdate: LiveServerSessionResumptionUpdate | null = null\n private setupComplete = false\n private connected = false\n\n public onReceiveResponse: (response: LiveResponse) => void = () => {}\n public onOpen: () => void = () => {}\n public onClose: () => void = () => {}\n public onError: (error: Error) => void = () => {}\n\n constructor(\n token: string,\n model: GeminiRealtimeModel,\n tools?: ReadonlyArray<AnyClientTool>,\n ) {\n this.token = token\n this.model = model\n\n if (tools) {\n this.functionDeclarations = tools.map(clientToolToDeclaration)\n }\n }\n\n get isConnected() {\n return this.connected\n }\n\n get isSetupComplete() {\n return this.setupComplete\n }\n\n /**\n * Connection management\n */\n connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const socket = new WebSocket(\n `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained?access_token=${this.token}`,\n )\n this.webSocket = socket\n\n // The browser fires `onerror` then `onclose` on an abnormal close; this\n // flag stops the close handler from overwriting the surfaced error with a\n // benign \"closed\" signal.\n let errored = false\n\n socket.onclose = () => {\n this.connected = false\n this.setupComplete = false\n if (!errored) this.onClose()\n reject(new Error('WebSocket closed before setup completed'))\n }\n\n socket.onerror = () => {\n errored = true\n this.connected = false\n this.setupComplete = false\n const error = new Error('Gemini realtime WebSocket connection error')\n this.onError(error)\n reject(error)\n }\n\n socket.onopen = () => {\n this.connected = true\n this.onOpen()\n resolve()\n }\n\n socket.onmessage = (event) => {\n void this.onReceiveMessage(event)\n }\n })\n }\n\n disconnect() {\n if (this.webSocket) {\n // Detach handlers first so a deliberate teardown (e.g. during a\n // reconnect) doesn't emit a spurious close/error to the client.\n this.webSocket.onclose = null\n this.webSocket.onerror = null\n this.webSocket.onopen = null\n this.webSocket.onmessage = null\n this.webSocket.close()\n this.webSocket = null\n }\n this.connected = false\n this.setupComplete = false\n }\n\n /**\n * Session management\n */\n sendInitialSetupMessage(resume = false) {\n const tools = this.functionDeclarations\n\n const setup: NonNullable<LiveClientMessage['setup']> = {\n model: `models/${this.model}`,\n generationConfig: {\n responseModalities: this.responseModalities,\n temperature: this.temperature,\n speechConfig: {\n languageCode: this.speechLanguageCode,\n voiceConfig: {\n prebuiltVoiceConfig: {\n voiceName: this.voiceName,\n },\n },\n },\n enableAffectiveDialog: this.enableAffectiveDialog,\n maxOutputTokens: this.maxOutputTokens,\n thinkingConfig: this.thinkingConfig,\n },\n sessionResumption: {\n transparent: true,\n handle: resume ? this.lastResumptionUpdate?.newHandle : undefined,\n },\n contextWindowCompression: this.contextWindowCompression,\n proactivity: {\n proactiveAudio: this.proactiveAudio,\n },\n systemInstruction: { parts: [{ text: this.systemInstructions }] },\n tools: [{ functionDeclarations: tools }],\n realtimeInputConfig: {\n automaticActivityDetection: {\n disabled: this.automaticActivityDetection.disabled,\n silenceDurationMs:\n this.automaticActivityDetection.silence_duration_ms,\n prefixPaddingMs: this.automaticActivityDetection.prefix_padding_ms,\n endOfSpeechSensitivity:\n this.automaticActivityDetection.end_of_speech_sensitivity,\n startOfSpeechSensitivity:\n this.automaticActivityDetection.start_of_speech_sensitivity,\n },\n activityHandling: this.activityHandling,\n turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY,\n },\n }\n\n if (this.inputAudioTranscription) {\n setup.inputAudioTranscription = {}\n }\n\n if (this.outputAudioTranscription) {\n setup.outputAudioTranscription = {}\n }\n\n if (this.googleGrounding) {\n // Currently can't have both Google Search with custom tools.\n console.warn(\n 'Google Grounding enabled, removing custom function calls if any.',\n )\n setup.tools = [{ googleSearch: {} }]\n }\n\n this.sendMessage({ setup })\n }\n\n async restartSession(resume = false) {\n this.disconnect()\n await this.connect()\n this.sendInitialSetupMessage(resume)\n }\n\n updateToken(token: RealtimeToken) {\n this.token = token.token\n\n // Restart completely with the new model, or resume the existing session.\n const resume = !(token.config.model && this.model != token.config.model)\n if (!resume) {\n this.model = token.config.model as GeminiRealtimeModel\n }\n this.restartSession(resume).catch((err) =>\n this.onError(err instanceof Error ? err : new Error(String(err))),\n )\n }\n\n async updateSession(config: Partial<RealtimeSessionConfig>) {\n // model can only be set during initial setup\n if (config.model && !this.setupComplete) {\n this.model = config.model as GeminiRealtimeModel\n }\n\n if (config.instructions) {\n this.systemInstructions = config.instructions\n }\n\n if (config.tools) {\n this.functionDeclarations = config.tools.map(toolConfigToDeclaration)\n }\n\n if (config.maxOutputTokens) {\n // Gemini has no \"inf\" sentinel; treat it as \"no explicit limit\".\n this.maxOutputTokens =\n typeof config.maxOutputTokens === 'number'\n ? config.maxOutputTokens\n : undefined\n }\n\n if (config.temperature) {\n this.temperature = config.temperature\n }\n\n if (config.voice) {\n this.voiceName = config.voice as GeminiRealtimeVoice\n }\n\n const providerOptions = config.providerOptions as\n | GeminiRealtimeProviderOptions\n | undefined\n\n if (providerOptions?.googleGrounding) {\n this.googleGrounding = providerOptions.googleGrounding\n }\n\n if (providerOptions?.proactiveAudio) {\n this.proactiveAudio = providerOptions.proactiveAudio\n }\n\n if (providerOptions?.enableAffectiveDialog) {\n this.enableAffectiveDialog = providerOptions.enableAffectiveDialog\n }\n\n if (providerOptions?.contextWindowCompression) {\n this.contextWindowCompression = providerOptions.contextWindowCompression\n }\n\n if (providerOptions?.thinkingConfig) {\n this.thinkingConfig = providerOptions.thinkingConfig\n }\n\n if (providerOptions?.languageCode) {\n this.speechLanguageCode = providerOptions.languageCode\n }\n\n const includeTranscription =\n config.outputModalities?.includes('text') || false\n this.inputAudioTranscription = includeTranscription\n this.outputAudioTranscription = includeTranscription\n\n if (!this.setupComplete) {\n this.sendInitialSetupMessage()\n } else {\n return this.restartSession(true)\n }\n }\n\n /**\n * Message transmission & receiving\n */\n sendMessage(message: LiveClientMessage) {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(JSON.stringify(message))\n } else {\n this.onError(\n new Error('Cannot send message: Gemini realtime socket is not open'),\n )\n }\n }\n\n async onReceiveMessage(messageEvent: MessageEvent) {\n let jsonData\n if (messageEvent.data instanceof Blob) {\n jsonData = await messageEvent.data.text()\n } else if (messageEvent.data instanceof ArrayBuffer) {\n jsonData = new TextDecoder().decode(messageEvent.data)\n } else {\n jsonData = messageEvent.data\n }\n\n try {\n const messageData = JSON.parse(jsonData)\n // Parse all response types from this message (audio + transcription can coexist)\n const responses = parseResponseMessages(messageData)\n for (const response of responses) {\n if (\n response.type === 'session_resumption_update' &&\n response.data.resumable\n ) {\n this.lastResumptionUpdate = response.data\n }\n if (response.type === 'setup_complete') {\n this.setupComplete = true\n }\n this.onReceiveResponse(response)\n }\n } catch (err) {\n this.onError(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n sendRealtimeInputMessage(data: string, mimeType: string) {\n const blob = { mimeType, data }\n\n if (mimeType.startsWith('audio/')) {\n this.sendMessage({ realtimeInput: { audio: blob } })\n } else if (mimeType.startsWith('image/') || mimeType.startsWith('video/')) {\n this.sendMessage({ realtimeInput: { video: blob } })\n }\n }\n\n sendAudioMessage(base64PCM: string) {\n this.sendRealtimeInputMessage(base64PCM, 'audio/pcm')\n }\n\n sendImageMessage(base64: string, mimeType = 'image/jpeg') {\n this.sendRealtimeInputMessage(base64, mimeType)\n }\n\n sendTextMessage(text: string) {\n const message: LiveClientMessage = {\n realtimeInput: {\n text,\n },\n }\n this.sendMessage(message)\n }\n\n sendToolResponse(functionResponses: Array<FunctionResponse>) {\n const message: LiveClientMessage = {\n toolResponse: {\n functionResponses,\n },\n }\n this.sendMessage(message)\n }\n}\n"],"mappings":";;;;AAiCA,SAAS,wBAAwB,MAA0C;CACzE,OAAO;EACL,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,sBAAsB,0BAA0B,KAAK,WAAW;EAChE,oBAAoB,0BAA0B,KAAK,YAAY;CACjE;AACF;;AAGA,SAAS,wBACP,MACqB;CACrB,OAAO;EACL,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,sBAAsB,KAAK;EAC3B,oBAAoB,KAAK;CAC3B;AACF;;;;;;;;;AAoCA,SAAgB,sBACd,MACqB;CACrB,MAAM,YAAiC,CAAC;CACxC,MAAM,gBAAgB,KAAK;CAC3B,MAAM,QAAQ,eAAe,WAAW;CAGxC,IAAI,KAAK,eAAe;EACtB,UAAU,KAAK;GAAE,MAAM;GAAkB,MAAM;GAAI,WAAW;EAAM,CAAC;EACrE,OAAO;CACT;CAGA,IAAI,KAAK,UAAU;EACjB,UAAU,KAAK;GACb,MAAM;GACN,MAAM,KAAK;GACX,WAAW;EACb,CAAC;EACD,OAAO;CACT;CAEA,IAAI,KAAK,yBACP,UAAU,KAAK;EACb,MAAM;EACN,MAAM,KAAK;EACX,WAAW;CACb,CAAC;CAGH,IAAI,KAAK,QACP,UAAU,KAAK;EAAE,MAAM;EAAW,MAAM,KAAK;EAAQ,WAAW;CAAM,CAAC;CAGzE,IAAI,KAAK,eACP,UAAU,KAAK;EACb,MAAM;EACN,MAAM,KAAK;EACX,WAAW;CACb,CAAC;CAIH,IAAI,OAAO,QACJ;OAAA,MAAM,QAAQ,OACjB,IAAI,KAAK,YAAY,MACnB,UAAU,KAAK;GACb,MAAM;GACN,MAAM;IACJ,WAAW,KAAK,WAAW;IAE3B,YAAY;GACd;GACA,WAAW;EACb,CAAC;OACI,IAAI,KAAK,MACd,UAAU,KAAK;GACb,MAAM,KAAK,UAAU,YAAY;GACjC,MAAM,KAAK;GACX,WAAW;EACb,CAAC;CAAA;CAMP,IAAI,eAAe,oBACjB,UAAU,KAAK;EACb,MAAM;EACN,MAAM;GACJ,MAAM,cAAc,mBAAmB,QAAQ;GAC/C,UAAU,cAAc,mBAAmB,YAAY;EACzD;EACA,WAAW;CACb,CAAC;CAGH,IAAI,eAAe,qBACjB,UAAU,KAAK;EACb,MAAM;EACN,MAAM;GACJ,MAAM,cAAc,oBAAoB,QAAQ;GAChD,UAAU,cAAc,oBAAoB,YAAY;EAC1D;EACA,WAAW;CACb,CAAC;CAIH,IAAI,eAAe,aACjB,UAAU,KAAK;EAAE,MAAM;EAAe,MAAM;EAAI,WAAW;CAAM,CAAC;CAIpE,IAAI,eAAe,cACjB,UAAU,KAAK;EAAE,MAAM;EAAiB,MAAM;EAAI,WAAW;CAAK,CAAC;CAGrE,OAAO;AACT;AAEA,IAAa,mBAAb,MAA8B;CAC5B,QAA+B;CAC/B,QAA4C;CAE5C,qBAAuD,CAAC,SAAS,KAAK;CACtE,qBAA6B;CAC7B,kBAA0B;CAC1B,YAAyC;CACzC,cAAsB;CACtB,0BAAkC;CAClC,2BAAmC;CACnC;CACA,iBAAyB;CACzB,wBAAgC;CAChC;CACA;CAEA;CACA,uBAA2D,CAAC;CAE5D,6BAA8C;EAC5C,UAAU;EACV,qBAAqB;EACrB,mBAAmB;EACnB,2BAA2B,eAAe;EAC1C,6BAA6B,iBAAiB;CAChD;CAEA,mBACE,iBAAiB;CAEnB,YAAsC;CACtC,uBAAyE;CACzE,gBAAwB;CACxB,YAAoB;CAEpB,0BAAmE,CAAC;CACpE,eAAkC,CAAC;CACnC,gBAAmC,CAAC;CACpC,gBAA+C,CAAC;CAEhD,YACE,OACA,OACA,OACA;EACA,KAAK,QAAQ;EACb,KAAK,QAAQ;EAEb,IAAI,OACF,KAAK,uBAAuB,MAAM,IAAI,uBAAuB;CAEjE;CAEA,IAAI,cAAc;EAChB,OAAO,KAAK;CACd;CAEA,IAAI,kBAAkB;EACpB,OAAO,KAAK;CACd;;;;CAKA,UAAyB;EACvB,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,SAAS,IAAI,UACjB,iJAAiJ,KAAK,OACxJ;GACA,KAAK,YAAY;GAKjB,IAAI,UAAU;GAEd,OAAO,gBAAgB;IACrB,KAAK,YAAY;IACjB,KAAK,gBAAgB;IACrB,IAAI,CAAC,SAAS,KAAK,QAAQ;IAC3B,uBAAO,IAAI,MAAM,yCAAyC,CAAC;GAC7D;GAEA,OAAO,gBAAgB;IACrB,UAAU;IACV,KAAK,YAAY;IACjB,KAAK,gBAAgB;IACrB,MAAM,wBAAQ,IAAI,MAAM,4CAA4C;IACpE,KAAK,QAAQ,KAAK;IAClB,OAAO,KAAK;GACd;GAEA,OAAO,eAAe;IACpB,KAAK,YAAY;IACjB,KAAK,OAAO;IACZ,QAAQ;GACV;GAEA,OAAO,aAAa,UAAU;IAC5B,KAAU,iBAAiB,KAAK;GAClC;EACF,CAAC;CACH;CAEA,aAAa;EACX,IAAI,KAAK,WAAW;GAGlB,KAAK,UAAU,UAAU;GACzB,KAAK,UAAU,UAAU;GACzB,KAAK,UAAU,SAAS;GACxB,KAAK,UAAU,YAAY;GAC3B,KAAK,UAAU,MAAM;GACrB,KAAK,YAAY;EACnB;EACA,KAAK,YAAY;EACjB,KAAK,gBAAgB;CACvB;;;;CAKA,wBAAwB,SAAS,OAAO;EACtC,MAAM,QAAQ,KAAK;EAEnB,MAAM,QAAiD;GACrD,OAAO,UAAU,KAAK;GACtB,kBAAkB;IAChB,oBAAoB,KAAK;IACzB,aAAa,KAAK;IAClB,cAAc;KACZ,cAAc,KAAK;KACnB,aAAa,EACX,qBAAqB,EACnB,WAAW,KAAK,UAClB,EACF;IACF;IACA,uBAAuB,KAAK;IAC5B,iBAAiB,KAAK;IACtB,gBAAgB,KAAK;GACvB;GACA,mBAAmB;IACjB,aAAa;IACb,QAAQ,SAAS,KAAK,sBAAsB,YAAY,KAAA;GAC1D;GACA,0BAA0B,KAAK;GAC/B,aAAa,EACX,gBAAgB,KAAK,eACvB;GACA,mBAAmB,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,mBAAmB,CAAC,EAAE;GAChE,OAAO,CAAC,EAAE,sBAAsB,MAAM,CAAC;GACvC,qBAAqB;IACnB,4BAA4B;KAC1B,UAAU,KAAK,2BAA2B;KAC1C,mBACE,KAAK,2BAA2B;KAClC,iBAAiB,KAAK,2BAA2B;KACjD,wBACE,KAAK,2BAA2B;KAClC,0BACE,KAAK,2BAA2B;IACpC;IACA,kBAAkB,KAAK;IACvB,cAAc,aAAa;GAC7B;EACF;EAEA,IAAI,KAAK,yBACP,MAAM,0BAA0B,CAAC;EAGnC,IAAI,KAAK,0BACP,MAAM,2BAA2B,CAAC;EAGpC,IAAI,KAAK,iBAAiB;GAExB,QAAQ,KACN,kEACF;GACA,MAAM,QAAQ,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC;EACrC;EAEA,KAAK,YAAY,EAAE,MAAM,CAAC;CAC5B;CAEA,MAAM,eAAe,SAAS,OAAO;EACnC,KAAK,WAAW;EAChB,MAAM,KAAK,QAAQ;EACnB,KAAK,wBAAwB,MAAM;CACrC;CAEA,YAAY,OAAsB;EAChC,KAAK,QAAQ,MAAM;EAGnB,MAAM,SAAS,EAAE,MAAM,OAAO,SAAS,KAAK,SAAS,MAAM,OAAO;EAClE,IAAI,CAAC,QACH,KAAK,QAAQ,MAAM,OAAO;EAE5B,KAAK,eAAe,MAAM,CAAC,CAAC,OAAO,QACjC,KAAK,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC,CAClE;CACF;CAEA,MAAM,cAAc,QAAwC;EAE1D,IAAI,OAAO,SAAS,CAAC,KAAK,eACxB,KAAK,QAAQ,OAAO;EAGtB,IAAI,OAAO,cACT,KAAK,qBAAqB,OAAO;EAGnC,IAAI,OAAO,OACT,KAAK,uBAAuB,OAAO,MAAM,IAAI,uBAAuB;EAGtE,IAAI,OAAO,iBAET,KAAK,kBACH,OAAO,OAAO,oBAAoB,WAC9B,OAAO,kBACP,KAAA;EAGR,IAAI,OAAO,aACT,KAAK,cAAc,OAAO;EAG5B,IAAI,OAAO,OACT,KAAK,YAAY,OAAO;EAG1B,MAAM,kBAAkB,OAAO;EAI/B,IAAI,iBAAiB,iBACnB,KAAK,kBAAkB,gBAAgB;EAGzC,IAAI,iBAAiB,gBACnB,KAAK,iBAAiB,gBAAgB;EAGxC,IAAI,iBAAiB,uBACnB,KAAK,wBAAwB,gBAAgB;EAG/C,IAAI,iBAAiB,0BACnB,KAAK,2BAA2B,gBAAgB;EAGlD,IAAI,iBAAiB,gBACnB,KAAK,iBAAiB,gBAAgB;EAGxC,IAAI,iBAAiB,cACnB,KAAK,qBAAqB,gBAAgB;EAG5C,MAAM,uBACJ,OAAO,kBAAkB,SAAS,MAAM,KAAK;EAC/C,KAAK,0BAA0B;EAC/B,KAAK,2BAA2B;EAEhC,IAAI,CAAC,KAAK,eACR,KAAK,wBAAwB;OAE7B,OAAO,KAAK,eAAe,IAAI;CAEnC;;;;CAKA,YAAY,SAA4B;EACtC,IAAI,KAAK,WAAW,eAAe,UAAU,MAC3C,KAAK,UAAU,KAAK,KAAK,UAAU,OAAO,CAAC;OAE3C,KAAK,wBACH,IAAI,MAAM,yDAAyD,CACrE;CAEJ;CAEA,MAAM,iBAAiB,cAA4B;EACjD,IAAI;EACJ,IAAI,aAAa,gBAAgB,MAC/B,WAAW,MAAM,aAAa,KAAK,KAAK;OACnC,IAAI,aAAa,gBAAgB,aACtC,WAAW,IAAI,YAAY,CAAC,CAAC,OAAO,aAAa,IAAI;OAErD,WAAW,aAAa;EAG1B,IAAI;GAGF,MAAM,YAAY,sBAFE,KAAK,MAAM,QAES,CAAW;GACnD,KAAK,MAAM,YAAY,WAAW;IAChC,IACE,SAAS,SAAS,+BAClB,SAAS,KAAK,WAEd,KAAK,uBAAuB,SAAS;IAEvC,IAAI,SAAS,SAAS,kBACpB,KAAK,gBAAgB;IAEvB,KAAK,kBAAkB,QAAQ;GACjC;EACF,SAAS,KAAK;GACZ,KAAK,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;EAClE;CACF;CAEA,yBAAyB,MAAc,UAAkB;EACvD,MAAM,OAAO;GAAE;GAAU;EAAK;EAE9B,IAAI,SAAS,WAAW,QAAQ,GAC9B,KAAK,YAAY,EAAE,eAAe,EAAE,OAAO,KAAK,EAAE,CAAC;OAC9C,IAAI,SAAS,WAAW,QAAQ,KAAK,SAAS,WAAW,QAAQ,GACtE,KAAK,YAAY,EAAE,eAAe,EAAE,OAAO,KAAK,EAAE,CAAC;CAEvD;CAEA,iBAAiB,WAAmB;EAClC,KAAK,yBAAyB,WAAW,WAAW;CACtD;CAEA,iBAAiB,QAAgB,WAAW,cAAc;EACxD,KAAK,yBAAyB,QAAQ,QAAQ;CAChD;CAEA,gBAAgB,MAAc;EAC5B,MAAM,UAA6B,EACjC,eAAe,EACb,KACF,EACF;EACA,KAAK,YAAY,OAAO;CAC1B;CAEA,iBAAiB,mBAA4C;EAC3D,MAAM,UAA6B,EACjC,cAAc,EACZ,kBACF,EACF;EACA,KAAK,YAAY,OAAO;CAC1B;AACF"}
|
|
@@ -26,11 +26,12 @@ import { GoogleGenAI } from "@google/genai";
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
function geminiRealtimeToken(options = {}) {
|
|
29
|
-
const
|
|
29
|
+
const apiKey = getGeminiApiKeyFromEnv();
|
|
30
|
+
const client = new GoogleGenAI({ apiKey });
|
|
30
31
|
return {
|
|
31
32
|
provider: "gemini",
|
|
32
33
|
async generateToken() {
|
|
33
|
-
const expireTime = options.expiresAt ?? Date.now() +
|
|
34
|
+
const expireTime = options.expiresAt ?? Date.now() + 18e5;
|
|
34
35
|
const token = await client.authTokens.create({ config: {
|
|
35
36
|
uses: options.uses ?? 1,
|
|
36
37
|
expireTime: new Date(expireTime).toISOString(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","names":[],"sources":["../../../src/realtime/token.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'\nimport type { GeminiRealtimeTokenOptions } from './types'\n\n/**\n * Creates a Google Gemini realtime token adapter.\n *\n * This adapter generates ephemeral tokens for client-side WebSocket connections.\n *\n * @param options - Configuration options for the realtime session\n * @returns A RealtimeTokenAdapter for use with realtimeToken()\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { geminiRealtimeToken } from '@tanstack/ai-gemini'\n *\n * const token = await realtimeToken({\n * adapter: geminiRealtimeToken({\n * // Optional: constraint model config by token\n * liveConnectConstraints: {\n * model: 'gemini-3.1-flash-live-preview',\n * },\n * }),\n * })\n * ```\n */\nexport function geminiRealtimeToken(\n options: GeminiRealtimeTokenOptions = {},\n): RealtimeTokenAdapter {\n const apiKey = getGeminiApiKeyFromEnv()\n\n const client = new GoogleGenAI({\n apiKey,\n })\n\n return {\n provider: 'gemini',\n async generateToken(): Promise<RealtimeToken> {\n // Computed per call so a reused adapter doesn't mint tokens with a\n // fixed (and eventually past) expiry. Defaults to 30 minutes.\n const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000\n\n const token = await client.authTokens.create({\n config: {\n uses: options.uses ?? 1,\n expireTime: new Date(expireTime).toISOString(),\n liveConnectConstraints: options.liveConnectConstraints,\n httpOptions: {\n apiVersion: 'v1alpha',\n },\n },\n })\n\n if (!token.name) {\n throw new Error('Gemini realtime token creation failed')\n }\n\n return {\n provider: 'gemini',\n token: token.name,\n expiresAt: expireTime,\n config: {\n model: options.liveConnectConstraints?.model,\n },\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,oBACd,UAAsC,CAAC,GACjB;
|
|
1
|
+
{"version":3,"file":"token.js","names":[],"sources":["../../../src/realtime/token.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'\nimport type { GeminiRealtimeTokenOptions } from './types'\n\n/**\n * Creates a Google Gemini realtime token adapter.\n *\n * This adapter generates ephemeral tokens for client-side WebSocket connections.\n *\n * @param options - Configuration options for the realtime session\n * @returns A RealtimeTokenAdapter for use with realtimeToken()\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { geminiRealtimeToken } from '@tanstack/ai-gemini'\n *\n * const token = await realtimeToken({\n * adapter: geminiRealtimeToken({\n * // Optional: constraint model config by token\n * liveConnectConstraints: {\n * model: 'gemini-3.1-flash-live-preview',\n * },\n * }),\n * })\n * ```\n */\nexport function geminiRealtimeToken(\n options: GeminiRealtimeTokenOptions = {},\n): RealtimeTokenAdapter {\n const apiKey = getGeminiApiKeyFromEnv()\n\n const client = new GoogleGenAI({\n apiKey,\n })\n\n return {\n provider: 'gemini',\n async generateToken(): Promise<RealtimeToken> {\n // Computed per call so a reused adapter doesn't mint tokens with a\n // fixed (and eventually past) expiry. Defaults to 30 minutes.\n const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000\n\n const token = await client.authTokens.create({\n config: {\n uses: options.uses ?? 1,\n expireTime: new Date(expireTime).toISOString(),\n liveConnectConstraints: options.liveConnectConstraints,\n httpOptions: {\n apiVersion: 'v1alpha',\n },\n },\n })\n\n if (!token.name) {\n throw new Error('Gemini realtime token creation failed')\n }\n\n return {\n provider: 'gemini',\n token: token.name,\n expiresAt: expireTime,\n config: {\n model: options.liveConnectConstraints?.model,\n },\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,oBACd,UAAsC,CAAC,GACjB;CACtB,MAAM,SAAS,uBAAuB;CAEtC,MAAM,SAAS,IAAI,YAAY,EAC7B,OACF,CAAC;CAED,OAAO;EACL,UAAU;EACV,MAAM,gBAAwC;GAG5C,MAAM,aAAa,QAAQ,aAAa,KAAK,IAAI,IAAI;GAErD,MAAM,QAAQ,MAAM,OAAO,WAAW,OAAO,EAC3C,QAAQ;IACN,MAAM,QAAQ,QAAQ;IACtB,YAAY,IAAI,KAAK,UAAU,CAAC,CAAC,YAAY;IAC7C,wBAAwB,QAAQ;IAChC,aAAa,EACX,YAAY,UACd;GACF,EACF,CAAC;GAED,IAAI,CAAC,MAAM,MACT,MAAM,IAAI,MAAM,uCAAuC;GAGzD,OAAO;IACL,UAAU;IACV,OAAO,MAAM;IACb,WAAW;IACX,QAAQ,EACN,OAAO,QAAQ,wBAAwB,MACzC;GACF;EACF;CACF;AACF"}
|
|
@@ -219,7 +219,10 @@ var AudioStreamer = class {
|
|
|
219
219
|
}
|
|
220
220
|
convertToPCM16(float32Array) {
|
|
221
221
|
const int16Array = new Int16Array(float32Array.length);
|
|
222
|
-
for (let i = 0; i < float32Array.length; i++)
|
|
222
|
+
for (let i = 0; i < float32Array.length; i++) {
|
|
223
|
+
const sample = Math.max(-1, Math.min(1, float32Array[i] ?? 0));
|
|
224
|
+
int16Array[i] = sample * 32767;
|
|
225
|
+
}
|
|
223
226
|
return int16Array.buffer;
|
|
224
227
|
}
|
|
225
228
|
arrayBufferToBase64(buffer) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../../src/realtime/utils.ts"],"sourcesContent":["import type { GeminiLiveClient } from './client'\n\n/**\n * Audio Worklet Processor for capturing and processing audio\n */\nconst captureWorkletCode = `\nclass AudioCaptureProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.bufferSize = 512; // 32ms at 16kHz — per Gemini best practices (20-40ms chunks)\n this.buffer = new Float32Array(this.bufferSize);\n this.bufferIndex = 0;\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n\n if (input && input.length > 0) {\n const inputChannel = input[0];\n\n // Buffer the incoming audio\n for (let i = 0; i < inputChannel.length; i++) {\n this.buffer[this.bufferIndex++] = inputChannel[i];\n\n // When buffer is full, send it to main thread\n if (this.bufferIndex >= this.bufferSize) {\n // Send the buffered audio to the main thread\n this.port.postMessage({\n type: \"audio\",\n data: this.buffer.slice(),\n });\n\n // Reset buffer\n this.bufferIndex = 0;\n }\n }\n }\n\n // Return true to keep the processor alive\n return true;\n }\n}\n\n// Register the processor\nregisterProcessor(\"audio-capture-processor\", AudioCaptureProcessor);`\n\n/**\n * Audio Playback Worklet Processor for playing PCM audio.\n * Uses an offset tracker instead of slice() to avoid allocations\n * on the real-time audio thread.\n */\nconst playbackWorkletCode = `\nclass PCMProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.audioQueue = [];\n this.currentOffset = 0; // Track position in current buffer (avoids slice())\n\n this.port.onmessage = (event) => {\n if (event.data === \"interrupt\") {\n // Clear the queue on interrupt\n this.audioQueue = [];\n this.currentOffset = 0;\n } else if (event.data instanceof Float32Array) {\n // Add audio data to the queue\n this.audioQueue.push(event.data);\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n if (output.length === 0) return true;\n\n const channel = output[0];\n let outputIndex = 0;\n\n // Fill the output buffer from the queue\n while (outputIndex < channel.length && this.audioQueue.length > 0) {\n const currentBuffer = this.audioQueue[0];\n\n if (!currentBuffer || currentBuffer.length === 0) {\n this.audioQueue.shift();\n this.currentOffset = 0;\n continue;\n }\n\n const remainingOutput = channel.length - outputIndex;\n const remainingBuffer = currentBuffer.length - this.currentOffset;\n const copyLength = Math.min(remainingOutput, remainingBuffer);\n\n // Copy audio data to output using offset (no slice allocation)\n for (let i = 0; i < copyLength; i++) {\n channel[outputIndex++] = currentBuffer[this.currentOffset++];\n }\n\n // If we've consumed the entire buffer, move to the next one\n if (this.currentOffset >= currentBuffer.length) {\n this.audioQueue.shift();\n this.currentOffset = 0;\n }\n }\n\n // Fill remaining output with silence\n while (outputIndex < channel.length) {\n channel[outputIndex++] = 0;\n }\n\n return true;\n }\n}\n\nregisterProcessor(\"pcm-processor\", PCMProcessor);`\n\nfunction calculateLevel(analyser: AnalyserNode): number {\n const data = new Uint8Array(analyser.fftSize)\n analyser.getByteTimeDomainData(data)\n\n // Find peak deviation from center (128 is silence)\n // This is more responsive than RMS for voice level meters\n let maxDeviation = 0\n for (const sample of data) {\n const deviation = Math.abs(sample - 128)\n if (deviation > maxDeviation) {\n maxDeviation = deviation\n }\n }\n\n // Normalize to 0-1 range (max deviation is 128)\n // Scale by 1.5x so that ~66% amplitude reads as full scale\n // This provides good visual feedback without pegging too early\n const normalized = maxDeviation / 128\n return Math.min(1, normalized * 1.5)\n}\n\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64)\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))\n return bytes.buffer\n}\n\n// Empty arrays for when visualization isn't available\n// frequencyBinCount = fftSize / 2 = 1024\nconst emptyFrequencyData = new Uint8Array(1024)\nconst emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence\n\nexport class AudioStreamer {\n private audioContext: AudioContext | null = null\n private audioWorklet: AudioWorkletNode | null = null\n private mediaStream: MediaStream | null = null\n private analyser: AnalyserNode | null = null\n private isStreaming = false\n private readonly sampleRate = 16000\n private readonly client: GeminiLiveClient | null = null\n\n constructor(client: GeminiLiveClient) {\n this.client = client\n }\n\n get inputLevel() {\n if (!this.analyser) return 0\n return calculateLevel(this.analyser)\n }\n\n get inputFrequencyData() {\n if (!this.analyser) return emptyFrequencyData\n const data = new Uint8Array(this.analyser.frequencyBinCount)\n this.analyser.getByteFrequencyData(data)\n return data\n }\n\n get inputTimeDomainData() {\n if (!this.analyser) return emptyTimeDomainData\n const data = new Uint8Array(this.analyser.fftSize)\n this.analyser.getByteTimeDomainData(data)\n return data\n }\n\n get inputSampleRate() {\n return this.sampleRate\n }\n\n async start() {\n try {\n const audioConstraints: MediaTrackConstraints = {\n sampleRate: this.sampleRate,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n }\n\n // Get microphone access\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: audioConstraints,\n })\n\n // Check if native AGC is active\n const track = this.mediaStream.getAudioTracks()[0]\n const settings = track?.getSettings()\n\n if (settings?.autoGainControl) {\n console.warn('Native AGC not supported.')\n }\n\n // Create audio context\n this.audioContext = new AudioContext({\n sampleRate: this.sampleRate,\n })\n\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume()\n }\n\n const workletBlob = new Blob([captureWorkletCode], {\n type: 'application/javascript',\n })\n const workletUrl = URL.createObjectURL(workletBlob)\n\n // Load the audio worklet module, then release the blob URL.\n await this.audioContext.audioWorklet.addModule(workletUrl)\n URL.revokeObjectURL(workletUrl)\n\n // Create the audio worklet node\n this.audioWorklet = new AudioWorkletNode(\n this.audioContext,\n 'audio-capture-processor',\n )\n\n // Set up message handling from the worklet\n this.audioWorklet.port.onmessage = (event) => {\n if (!this.isStreaming) return\n\n if (event.data.type === 'audio') {\n const inputData = event.data.data\n const pcmData = this.convertToPCM16(inputData)\n const base64Audio = this.arrayBufferToBase64(pcmData)\n\n // Send to Gemini only if after setup complete\n if (this.client?.isSetupComplete) {\n this.client.sendAudioMessage(base64Audio)\n }\n }\n }\n\n // Create analyser for volume detection\n this.analyser = this.audioContext.createAnalyser()\n this.analyser.fftSize = 2048 // Larger size for more accurate level detection\n this.analyser.smoothingTimeConstant = 0.3\n\n // Connect the audio graph\n const source = this.audioContext.createMediaStreamSource(this.mediaStream)\n source.connect(this.analyser)\n this.analyser.connect(this.audioWorklet)\n\n // Start streaming\n this.isStreaming = true\n } catch (error) {\n // Clean up the mic + audio context if setup failed partway through.\n this.stop()\n throw error\n }\n }\n\n stop() {\n this.isStreaming = false\n\n if (this.audioWorklet) {\n this.audioWorklet.disconnect()\n this.audioWorklet.port.close()\n this.audioWorklet = null\n }\n\n if (this.audioContext) {\n void this.audioContext.close()\n this.audioContext = null\n }\n\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => track.stop())\n this.mediaStream = null\n }\n }\n\n startAudioCapture() {\n if (this.mediaStream) {\n for (const track of this.mediaStream.getAudioTracks()) {\n track.enabled = true\n }\n }\n this.isStreaming = true\n }\n\n stopAudioCapture() {\n if (this.mediaStream) {\n // Disable tracks rather than stopping them to allow re-enabling\n for (const track of this.mediaStream.getAudioTracks()) {\n track.enabled = false\n }\n }\n this.isStreaming = false\n }\n\n private convertToPCM16(float32Array: Float32Array): ArrayBuffer {\n const int16Array = new Int16Array(float32Array.length)\n for (let i = 0; i < float32Array.length; i++) {\n const sample = Math.max(-1, Math.min(1, float32Array[i] ?? 0))\n int16Array[i] = sample * 0x7fff\n }\n return int16Array.buffer\n }\n\n private arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer)\n const binary = String.fromCharCode(...bytes)\n return btoa(binary)\n }\n}\n\nexport class AudioPlayer {\n private audioContext: AudioContext | null = null\n private workletNode: AudioWorkletNode | null = null\n private gainNode: GainNode | null = null\n private analyser: AnalyserNode | null = null\n private isInitialized = false\n private volume = 1.0\n private readonly sampleRate = 24000\n\n get outputLevel() {\n if (!this.analyser) return 0\n return calculateLevel(this.analyser)\n }\n\n get outputFrequencyData() {\n if (!this.analyser) return emptyFrequencyData\n const data = new Uint8Array(this.analyser.frequencyBinCount)\n this.analyser.getByteFrequencyData(data)\n return data\n }\n\n get outputTimeDomainData() {\n if (!this.analyser) return emptyTimeDomainData\n const data = new Uint8Array(this.analyser.fftSize)\n this.analyser.getByteTimeDomainData(data)\n return data\n }\n\n get outputSampleRate() {\n return this.sampleRate\n }\n\n async init() {\n if (this.isInitialized) return\n\n try {\n // Create audio context at 24kHz to match Gemini\n this.audioContext = new AudioContext({\n sampleRate: this.sampleRate,\n })\n\n const workletBlob = new Blob([playbackWorkletCode], {\n type: 'application/javascript',\n })\n const workletUrl = URL.createObjectURL(workletBlob)\n\n // Load the audio worklet module, then release the blob URL.\n await this.audioContext.audioWorklet.addModule(workletUrl)\n URL.revokeObjectURL(workletUrl)\n\n // Create worklet node\n this.workletNode = new AudioWorkletNode(\n this.audioContext,\n 'pcm-processor',\n )\n\n // Create gain node for volume control\n this.gainNode = this.audioContext.createGain()\n this.gainNode.gain.value = this.volume\n\n // Create analyser for volume detection\n this.analyser = this.audioContext.createAnalyser()\n this.analyser.fftSize = 2048 // Larger size for more accurate level detection\n this.analyser.smoothingTimeConstant = 0.3\n\n // Connect nodes\n this.workletNode.connect(this.gainNode)\n this.gainNode.connect(this.analyser)\n this.analyser.connect(this.audioContext.destination)\n\n this.isInitialized = true\n } catch (error) {\n // Release the audio context if initialization failed partway through.\n this.destroy()\n throw error\n }\n }\n\n async play(pcmData: ArrayBuffer) {\n if (!this.isInitialized) {\n await this.init()\n }\n\n // Resume audio context if suspended\n if (this.audioContext?.state === 'suspended') {\n await this.audioContext.resume()\n }\n\n // Convert PCM16 LE to Float32\n const inputArray = new Int16Array(pcmData)\n const float32Data = new Float32Array(inputArray.length)\n for (let i = 0; i < inputArray.length; i++) {\n float32Data[i] = (inputArray[i] ?? 0) / 32768\n }\n\n // Send to worklet for playback\n this.workletNode?.port.postMessage(float32Data)\n }\n\n /* Interrupt playback */\n interrupt() {\n if (this.workletNode) {\n this.workletNode.port.postMessage('interrupt')\n }\n }\n\n setVolume(volume: number) {\n this.volume = Math.max(0, Math.min(1, volume))\n if (this.gainNode) {\n this.gainNode.gain.value = this.volume\n }\n }\n\n destroy() {\n if (this.audioContext) {\n void this.audioContext.close()\n this.audioContext = null\n }\n this.isInitialized = false\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../../src/realtime/utils.ts"],"sourcesContent":["import type { GeminiLiveClient } from './client'\n\n/**\n * Audio Worklet Processor for capturing and processing audio\n */\nconst captureWorkletCode = `\nclass AudioCaptureProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.bufferSize = 512; // 32ms at 16kHz — per Gemini best practices (20-40ms chunks)\n this.buffer = new Float32Array(this.bufferSize);\n this.bufferIndex = 0;\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n\n if (input && input.length > 0) {\n const inputChannel = input[0];\n\n // Buffer the incoming audio\n for (let i = 0; i < inputChannel.length; i++) {\n this.buffer[this.bufferIndex++] = inputChannel[i];\n\n // When buffer is full, send it to main thread\n if (this.bufferIndex >= this.bufferSize) {\n // Send the buffered audio to the main thread\n this.port.postMessage({\n type: \"audio\",\n data: this.buffer.slice(),\n });\n\n // Reset buffer\n this.bufferIndex = 0;\n }\n }\n }\n\n // Return true to keep the processor alive\n return true;\n }\n}\n\n// Register the processor\nregisterProcessor(\"audio-capture-processor\", AudioCaptureProcessor);`\n\n/**\n * Audio Playback Worklet Processor for playing PCM audio.\n * Uses an offset tracker instead of slice() to avoid allocations\n * on the real-time audio thread.\n */\nconst playbackWorkletCode = `\nclass PCMProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.audioQueue = [];\n this.currentOffset = 0; // Track position in current buffer (avoids slice())\n\n this.port.onmessage = (event) => {\n if (event.data === \"interrupt\") {\n // Clear the queue on interrupt\n this.audioQueue = [];\n this.currentOffset = 0;\n } else if (event.data instanceof Float32Array) {\n // Add audio data to the queue\n this.audioQueue.push(event.data);\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n if (output.length === 0) return true;\n\n const channel = output[0];\n let outputIndex = 0;\n\n // Fill the output buffer from the queue\n while (outputIndex < channel.length && this.audioQueue.length > 0) {\n const currentBuffer = this.audioQueue[0];\n\n if (!currentBuffer || currentBuffer.length === 0) {\n this.audioQueue.shift();\n this.currentOffset = 0;\n continue;\n }\n\n const remainingOutput = channel.length - outputIndex;\n const remainingBuffer = currentBuffer.length - this.currentOffset;\n const copyLength = Math.min(remainingOutput, remainingBuffer);\n\n // Copy audio data to output using offset (no slice allocation)\n for (let i = 0; i < copyLength; i++) {\n channel[outputIndex++] = currentBuffer[this.currentOffset++];\n }\n\n // If we've consumed the entire buffer, move to the next one\n if (this.currentOffset >= currentBuffer.length) {\n this.audioQueue.shift();\n this.currentOffset = 0;\n }\n }\n\n // Fill remaining output with silence\n while (outputIndex < channel.length) {\n channel[outputIndex++] = 0;\n }\n\n return true;\n }\n}\n\nregisterProcessor(\"pcm-processor\", PCMProcessor);`\n\nfunction calculateLevel(analyser: AnalyserNode): number {\n const data = new Uint8Array(analyser.fftSize)\n analyser.getByteTimeDomainData(data)\n\n // Find peak deviation from center (128 is silence)\n // This is more responsive than RMS for voice level meters\n let maxDeviation = 0\n for (const sample of data) {\n const deviation = Math.abs(sample - 128)\n if (deviation > maxDeviation) {\n maxDeviation = deviation\n }\n }\n\n // Normalize to 0-1 range (max deviation is 128)\n // Scale by 1.5x so that ~66% amplitude reads as full scale\n // This provides good visual feedback without pegging too early\n const normalized = maxDeviation / 128\n return Math.min(1, normalized * 1.5)\n}\n\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64)\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))\n return bytes.buffer\n}\n\n// Empty arrays for when visualization isn't available\n// frequencyBinCount = fftSize / 2 = 1024\nconst emptyFrequencyData = new Uint8Array(1024)\nconst emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence\n\nexport class AudioStreamer {\n private audioContext: AudioContext | null = null\n private audioWorklet: AudioWorkletNode | null = null\n private mediaStream: MediaStream | null = null\n private analyser: AnalyserNode | null = null\n private isStreaming = false\n private readonly sampleRate = 16000\n private readonly client: GeminiLiveClient | null = null\n\n constructor(client: GeminiLiveClient) {\n this.client = client\n }\n\n get inputLevel() {\n if (!this.analyser) return 0\n return calculateLevel(this.analyser)\n }\n\n get inputFrequencyData() {\n if (!this.analyser) return emptyFrequencyData\n const data = new Uint8Array(this.analyser.frequencyBinCount)\n this.analyser.getByteFrequencyData(data)\n return data\n }\n\n get inputTimeDomainData() {\n if (!this.analyser) return emptyTimeDomainData\n const data = new Uint8Array(this.analyser.fftSize)\n this.analyser.getByteTimeDomainData(data)\n return data\n }\n\n get inputSampleRate() {\n return this.sampleRate\n }\n\n async start() {\n try {\n const audioConstraints: MediaTrackConstraints = {\n sampleRate: this.sampleRate,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n }\n\n // Get microphone access\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: audioConstraints,\n })\n\n // Check if native AGC is active\n const track = this.mediaStream.getAudioTracks()[0]\n const settings = track?.getSettings()\n\n if (settings?.autoGainControl) {\n console.warn('Native AGC not supported.')\n }\n\n // Create audio context\n this.audioContext = new AudioContext({\n sampleRate: this.sampleRate,\n })\n\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume()\n }\n\n const workletBlob = new Blob([captureWorkletCode], {\n type: 'application/javascript',\n })\n const workletUrl = URL.createObjectURL(workletBlob)\n\n // Load the audio worklet module, then release the blob URL.\n await this.audioContext.audioWorklet.addModule(workletUrl)\n URL.revokeObjectURL(workletUrl)\n\n // Create the audio worklet node\n this.audioWorklet = new AudioWorkletNode(\n this.audioContext,\n 'audio-capture-processor',\n )\n\n // Set up message handling from the worklet\n this.audioWorklet.port.onmessage = (event) => {\n if (!this.isStreaming) return\n\n if (event.data.type === 'audio') {\n const inputData = event.data.data\n const pcmData = this.convertToPCM16(inputData)\n const base64Audio = this.arrayBufferToBase64(pcmData)\n\n // Send to Gemini only if after setup complete\n if (this.client?.isSetupComplete) {\n this.client.sendAudioMessage(base64Audio)\n }\n }\n }\n\n // Create analyser for volume detection\n this.analyser = this.audioContext.createAnalyser()\n this.analyser.fftSize = 2048 // Larger size for more accurate level detection\n this.analyser.smoothingTimeConstant = 0.3\n\n // Connect the audio graph\n const source = this.audioContext.createMediaStreamSource(this.mediaStream)\n source.connect(this.analyser)\n this.analyser.connect(this.audioWorklet)\n\n // Start streaming\n this.isStreaming = true\n } catch (error) {\n // Clean up the mic + audio context if setup failed partway through.\n this.stop()\n throw error\n }\n }\n\n stop() {\n this.isStreaming = false\n\n if (this.audioWorklet) {\n this.audioWorklet.disconnect()\n this.audioWorklet.port.close()\n this.audioWorklet = null\n }\n\n if (this.audioContext) {\n void this.audioContext.close()\n this.audioContext = null\n }\n\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => track.stop())\n this.mediaStream = null\n }\n }\n\n startAudioCapture() {\n if (this.mediaStream) {\n for (const track of this.mediaStream.getAudioTracks()) {\n track.enabled = true\n }\n }\n this.isStreaming = true\n }\n\n stopAudioCapture() {\n if (this.mediaStream) {\n // Disable tracks rather than stopping them to allow re-enabling\n for (const track of this.mediaStream.getAudioTracks()) {\n track.enabled = false\n }\n }\n this.isStreaming = false\n }\n\n private convertToPCM16(float32Array: Float32Array): ArrayBuffer {\n const int16Array = new Int16Array(float32Array.length)\n for (let i = 0; i < float32Array.length; i++) {\n const sample = Math.max(-1, Math.min(1, float32Array[i] ?? 0))\n int16Array[i] = sample * 0x7fff\n }\n return int16Array.buffer\n }\n\n private arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer)\n const binary = String.fromCharCode(...bytes)\n return btoa(binary)\n }\n}\n\nexport class AudioPlayer {\n private audioContext: AudioContext | null = null\n private workletNode: AudioWorkletNode | null = null\n private gainNode: GainNode | null = null\n private analyser: AnalyserNode | null = null\n private isInitialized = false\n private volume = 1.0\n private readonly sampleRate = 24000\n\n get outputLevel() {\n if (!this.analyser) return 0\n return calculateLevel(this.analyser)\n }\n\n get outputFrequencyData() {\n if (!this.analyser) return emptyFrequencyData\n const data = new Uint8Array(this.analyser.frequencyBinCount)\n this.analyser.getByteFrequencyData(data)\n return data\n }\n\n get outputTimeDomainData() {\n if (!this.analyser) return emptyTimeDomainData\n const data = new Uint8Array(this.analyser.fftSize)\n this.analyser.getByteTimeDomainData(data)\n return data\n }\n\n get outputSampleRate() {\n return this.sampleRate\n }\n\n async init() {\n if (this.isInitialized) return\n\n try {\n // Create audio context at 24kHz to match Gemini\n this.audioContext = new AudioContext({\n sampleRate: this.sampleRate,\n })\n\n const workletBlob = new Blob([playbackWorkletCode], {\n type: 'application/javascript',\n })\n const workletUrl = URL.createObjectURL(workletBlob)\n\n // Load the audio worklet module, then release the blob URL.\n await this.audioContext.audioWorklet.addModule(workletUrl)\n URL.revokeObjectURL(workletUrl)\n\n // Create worklet node\n this.workletNode = new AudioWorkletNode(\n this.audioContext,\n 'pcm-processor',\n )\n\n // Create gain node for volume control\n this.gainNode = this.audioContext.createGain()\n this.gainNode.gain.value = this.volume\n\n // Create analyser for volume detection\n this.analyser = this.audioContext.createAnalyser()\n this.analyser.fftSize = 2048 // Larger size for more accurate level detection\n this.analyser.smoothingTimeConstant = 0.3\n\n // Connect nodes\n this.workletNode.connect(this.gainNode)\n this.gainNode.connect(this.analyser)\n this.analyser.connect(this.audioContext.destination)\n\n this.isInitialized = true\n } catch (error) {\n // Release the audio context if initialization failed partway through.\n this.destroy()\n throw error\n }\n }\n\n async play(pcmData: ArrayBuffer) {\n if (!this.isInitialized) {\n await this.init()\n }\n\n // Resume audio context if suspended\n if (this.audioContext?.state === 'suspended') {\n await this.audioContext.resume()\n }\n\n // Convert PCM16 LE to Float32\n const inputArray = new Int16Array(pcmData)\n const float32Data = new Float32Array(inputArray.length)\n for (let i = 0; i < inputArray.length; i++) {\n float32Data[i] = (inputArray[i] ?? 0) / 32768\n }\n\n // Send to worklet for playback\n this.workletNode?.port.postMessage(float32Data)\n }\n\n /* Interrupt playback */\n interrupt() {\n if (this.workletNode) {\n this.workletNode.port.postMessage('interrupt')\n }\n }\n\n setVolume(volume: number) {\n this.volume = Math.max(0, Math.min(1, volume))\n if (this.gainNode) {\n this.gainNode.gain.value = this.volume\n }\n }\n\n destroy() {\n if (this.audioContext) {\n void this.audioContext.close()\n this.audioContext = null\n }\n this.isInitialized = false\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/code-execution-tool.ts
|
|
3
3
|
function convertCodeExecutionToolToAdapterFormat(_tool) {
|
|
4
4
|
return { codeExecution: {} };
|
|
5
5
|
}
|
|
6
6
|
function codeExecutionTool() {
|
|
7
|
-
return
|
|
7
|
+
return brandGeminiProviderTool({
|
|
8
8
|
name: "code_execution",
|
|
9
9
|
description: "",
|
|
10
10
|
metadata: {}
|
|
11
|
-
});
|
|
11
|
+
}, "code_execution");
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { codeExecutionTool, convertCodeExecutionToolToAdapterFormat };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-execution-tool.js","names":[],"sources":["../../../src/tools/code-execution-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"code-execution-tool.js","names":[],"sources":["../../../src/tools/code-execution-tool.ts"],"sourcesContent":["import { brandGeminiProviderTool } from './gemini-provider-tool'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport interface CodeExecutionToolConfig {}\n\n/** @deprecated Renamed to `CodeExecutionToolConfig`. Will be removed in a future release. */\nexport type CodeExecutionTool = CodeExecutionToolConfig\n\nexport type GeminiCodeExecutionTool = ProviderTool<'gemini', 'code_execution'>\n\nexport function convertCodeExecutionToolToAdapterFormat(_tool: Tool) {\n return {\n codeExecution: {},\n }\n}\n\nexport function codeExecutionTool(): GeminiCodeExecutionTool {\n return brandGeminiProviderTool<GeminiCodeExecutionTool>(\n {\n name: 'code_execution',\n description: '',\n metadata: {},\n },\n 'code_execution',\n )\n}\n"],"mappings":";;AAUA,SAAgB,wCAAwC,OAAa;CACnE,OAAO,EACL,eAAe,CAAC,EAClB;AACF;AAEA,SAAgB,oBAA6C;CAC3D,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU,CAAC;CACb,GACA,gBACF;AACF"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool, getGeminiProviderToolMetadata } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/computer-use-tool.ts
|
|
3
3
|
function convertComputerUseToolToAdapterFormat(tool) {
|
|
4
|
-
const metadata = tool
|
|
4
|
+
const metadata = getGeminiProviderToolMetadata(tool);
|
|
5
5
|
return { computerUse: {
|
|
6
6
|
...metadata.environment !== void 0 && { environment: metadata.environment },
|
|
7
7
|
...metadata.excludedPredefinedFunctions !== void 0 && { excludedPredefinedFunctions: metadata.excludedPredefinedFunctions }
|
|
8
8
|
} };
|
|
9
9
|
}
|
|
10
10
|
function computerUseTool(config) {
|
|
11
|
-
return
|
|
11
|
+
return brandGeminiProviderTool({
|
|
12
12
|
name: "computer_use",
|
|
13
13
|
description: "",
|
|
14
14
|
metadata: {
|
|
15
15
|
...config.environment !== void 0 && { environment: config.environment },
|
|
16
16
|
...config.excludedPredefinedFunctions !== void 0 && { excludedPredefinedFunctions: config.excludedPredefinedFunctions }
|
|
17
17
|
}
|
|
18
|
-
});
|
|
18
|
+
}, "computer_use");
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
21
|
export { computerUseTool, convertComputerUseToolToAdapterFormat };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computer-use-tool.js","names":[],"sources":["../../../src/tools/computer-use-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"computer-use-tool.js","names":[],"sources":["../../../src/tools/computer-use-tool.ts"],"sourcesContent":["import {\n brandGeminiProviderTool,\n getGeminiProviderToolMetadata,\n} from './gemini-provider-tool'\nimport type { ComputerUse } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type ComputerUseToolConfig = ComputerUse\n\n/** @deprecated Renamed to `ComputerUseToolConfig`. Will be removed in a future release. */\nexport type ComputerUseTool = ComputerUseToolConfig\n\nexport type GeminiComputerUseTool = ProviderTool<'gemini', 'computer_use'>\n\nexport function convertComputerUseToolToAdapterFormat(tool: Tool) {\n const metadata = getGeminiProviderToolMetadata(tool) as ComputerUseToolConfig\n // Vendor `ComputerUse` fields are `field?: T` (no `| undefined`) under EOPT,\n // so spread each field conditionally rather than emitting explicit\n // `undefined`s on the wire payload.\n return {\n computerUse: {\n ...(metadata.environment !== undefined && {\n environment: metadata.environment,\n }),\n ...(metadata.excludedPredefinedFunctions !== undefined && {\n excludedPredefinedFunctions: metadata.excludedPredefinedFunctions,\n }),\n },\n }\n}\n\nexport function computerUseTool(\n config: ComputerUseToolConfig,\n): GeminiComputerUseTool {\n return brandGeminiProviderTool<GeminiComputerUseTool>(\n {\n name: 'computer_use',\n description: '',\n metadata: {\n ...(config.environment !== undefined && {\n environment: config.environment,\n }),\n ...(config.excludedPredefinedFunctions !== undefined && {\n excludedPredefinedFunctions: config.excludedPredefinedFunctions,\n }),\n },\n },\n 'computer_use',\n )\n}\n"],"mappings":";;AAcA,SAAgB,sCAAsC,MAAY;CAChE,MAAM,WAAW,8BAA8B,IAAI;CAInD,OAAO,EACL,aAAa;EACX,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,SAAS,YACxB;EACA,GAAI,SAAS,gCAAgC,KAAA,KAAa,EACxD,6BAA6B,SAAS,4BACxC;CACF,EACF;AACF;AAEA,SAAgB,gBACd,QACuB;CACvB,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU;GACR,GAAI,OAAO,gBAAgB,KAAA,KAAa,EACtC,aAAa,OAAO,YACtB;GACA,GAAI,OAAO,gCAAgC,KAAA,KAAa,EACtD,6BAA6B,OAAO,4BACtC;EACF;CACF,GACA,cACF;AACF"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool, getGeminiProviderToolMetadata } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/file-search-tool.ts
|
|
3
3
|
function convertFileSearchToolToAdapterFormat(tool) {
|
|
4
|
-
return { fileSearch: tool
|
|
4
|
+
return { fileSearch: getGeminiProviderToolMetadata(tool) };
|
|
5
5
|
}
|
|
6
6
|
function fileSearchTool(config) {
|
|
7
|
-
return
|
|
7
|
+
return brandGeminiProviderTool({
|
|
8
8
|
name: "file_search",
|
|
9
9
|
description: "",
|
|
10
10
|
metadata: config
|
|
11
|
-
});
|
|
11
|
+
}, "file_search");
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { convertFileSearchToolToAdapterFormat, fileSearchTool };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-search-tool.js","names":[],"sources":["../../../src/tools/file-search-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"file-search-tool.js","names":[],"sources":["../../../src/tools/file-search-tool.ts"],"sourcesContent":["import {\n brandGeminiProviderTool,\n getGeminiProviderToolMetadata,\n} from './gemini-provider-tool'\nimport type { FileSearch } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type FileSearchToolConfig = FileSearch\n\n/** @deprecated Renamed to `FileSearchToolConfig`. Will be removed in a future release. */\nexport type FileSearchTool = FileSearchToolConfig\n\nexport type GeminiFileSearchTool = ProviderTool<'gemini', 'file_search'>\n\nexport function convertFileSearchToolToAdapterFormat(tool: Tool) {\n const metadata = getGeminiProviderToolMetadata(tool) as FileSearchToolConfig\n return {\n fileSearch: metadata,\n }\n}\n\nexport function fileSearchTool(\n config: FileSearchToolConfig,\n): GeminiFileSearchTool {\n return brandGeminiProviderTool<GeminiFileSearchTool>(\n {\n name: 'file_search',\n description: '',\n metadata: config,\n },\n 'file_search',\n )\n}\n"],"mappings":";;AAcA,SAAgB,qCAAqC,MAAY;CAE/D,OAAO,EACL,YAFe,8BAA8B,IAEjC,EACd;AACF;AAEA,SAAgB,eACd,QACsB;CACtB,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU;CACZ,GACA,aACF;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ProviderTool, Tool } from '@tanstack/ai';
|
|
2
|
+
declare const GEMINI_PROVIDER_TOOL_KINDS: {
|
|
3
|
+
readonly code_execution: "gemini.code_execution";
|
|
4
|
+
readonly computer_use: "gemini.computer_use";
|
|
5
|
+
readonly file_search: "gemini.file_search";
|
|
6
|
+
readonly google_maps: "gemini.google_maps";
|
|
7
|
+
readonly google_search: "gemini.google_search";
|
|
8
|
+
readonly google_search_retrieval: "gemini.google_search_retrieval";
|
|
9
|
+
readonly url_context: "gemini.url_context";
|
|
10
|
+
};
|
|
11
|
+
type GeminiProviderToolKind = keyof typeof GEMINI_PROVIDER_TOOL_KINDS;
|
|
12
|
+
export declare function brandGeminiProviderTool<T extends ProviderTool<'gemini', GeminiProviderToolKind>>(tool: Omit<T, '~provider' | '~toolKind'>, toolKind: T['~toolKind']): T;
|
|
13
|
+
export declare function getGeminiProviderToolKind(tool: Tool): GeminiProviderToolKind | undefined;
|
|
14
|
+
/** Returns adapter metadata without the internal runtime discriminator. */
|
|
15
|
+
export declare function getGeminiProviderToolMetadata(tool: Tool): Tool['metadata'];
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { brandProviderTool } from "@tanstack/ai";
|
|
2
|
+
//#region src/tools/gemini-provider-tool.ts
|
|
3
|
+
var GEMINI_PROVIDER_TOOL_KINDS = {
|
|
4
|
+
code_execution: "gemini.code_execution",
|
|
5
|
+
computer_use: "gemini.computer_use",
|
|
6
|
+
file_search: "gemini.file_search",
|
|
7
|
+
google_maps: "gemini.google_maps",
|
|
8
|
+
google_search: "gemini.google_search",
|
|
9
|
+
google_search_retrieval: "gemini.google_search_retrieval",
|
|
10
|
+
url_context: "gemini.url_context"
|
|
11
|
+
};
|
|
12
|
+
function brandGeminiProviderTool(tool, toolKind) {
|
|
13
|
+
return brandProviderTool({
|
|
14
|
+
...tool,
|
|
15
|
+
metadata: {
|
|
16
|
+
...tool.metadata,
|
|
17
|
+
__kind: GEMINI_PROVIDER_TOOL_KINDS[toolKind]
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function getGeminiProviderToolKind(tool) {
|
|
22
|
+
switch (tool.metadata?.["__kind"]) {
|
|
23
|
+
case "gemini.code_execution": return "code_execution";
|
|
24
|
+
case "gemini.computer_use": return "computer_use";
|
|
25
|
+
case "gemini.file_search": return "file_search";
|
|
26
|
+
case "gemini.google_maps": return "google_maps";
|
|
27
|
+
case "gemini.google_search": return "google_search";
|
|
28
|
+
case "gemini.google_search_retrieval": return "google_search_retrieval";
|
|
29
|
+
case "gemini.url_context": return "url_context";
|
|
30
|
+
default: return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Returns adapter metadata without the internal runtime discriminator. */
|
|
34
|
+
function getGeminiProviderToolMetadata(tool) {
|
|
35
|
+
if (!tool.metadata) return;
|
|
36
|
+
const { __kind: _kind, ...metadata } = tool.metadata;
|
|
37
|
+
return metadata;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { brandGeminiProviderTool, getGeminiProviderToolKind, getGeminiProviderToolMetadata };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=gemini-provider-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-provider-tool.js","names":[],"sources":["../../../src/tools/gemini-provider-tool.ts"],"sourcesContent":["import { brandProviderTool } from '@tanstack/ai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nconst GEMINI_PROVIDER_TOOL_KINDS = {\n code_execution: 'gemini.code_execution',\n computer_use: 'gemini.computer_use',\n file_search: 'gemini.file_search',\n google_maps: 'gemini.google_maps',\n google_search: 'gemini.google_search',\n google_search_retrieval: 'gemini.google_search_retrieval',\n url_context: 'gemini.url_context',\n} as const\n\ntype GeminiProviderToolKind = keyof typeof GEMINI_PROVIDER_TOOL_KINDS\n\nexport function brandGeminiProviderTool<\n T extends ProviderTool<'gemini', GeminiProviderToolKind>,\n>(tool: Omit<T, '~provider' | '~toolKind'>, toolKind: T['~toolKind']): T {\n return brandProviderTool<T>({\n ...tool,\n metadata: {\n ...tool.metadata,\n __kind: GEMINI_PROVIDER_TOOL_KINDS[toolKind],\n },\n })\n}\n\nexport function getGeminiProviderToolKind(\n tool: Tool,\n): GeminiProviderToolKind | undefined {\n const toolKind = tool.metadata?.['__kind']\n switch (toolKind) {\n case 'gemini.code_execution':\n return 'code_execution'\n case 'gemini.computer_use':\n return 'computer_use'\n case 'gemini.file_search':\n return 'file_search'\n case 'gemini.google_maps':\n return 'google_maps'\n case 'gemini.google_search':\n return 'google_search'\n case 'gemini.google_search_retrieval':\n return 'google_search_retrieval'\n case 'gemini.url_context':\n return 'url_context'\n default:\n return undefined\n }\n}\n\n/** Returns adapter metadata without the internal runtime discriminator. */\nexport function getGeminiProviderToolMetadata(tool: Tool): Tool['metadata'] {\n if (!tool.metadata) {\n return undefined\n }\n const { __kind: _kind, ...metadata } = tool.metadata\n void _kind\n return metadata\n}\n"],"mappings":";;AAGA,IAAM,6BAA6B;CACjC,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,aAAa;CACb,eAAe;CACf,yBAAyB;CACzB,aAAa;AACf;AAIA,SAAgB,wBAEd,MAA0C,UAA6B;CACvE,OAAO,kBAAqB;EAC1B,GAAG;EACH,UAAU;GACR,GAAG,KAAK;GACR,QAAQ,2BAA2B;EACrC;CACF,CAAC;AACH;AAEA,SAAgB,0BACd,MACoC;CAEpC,QADiB,KAAK,WAAW,WACjC;EACE,KAAK,yBACH,OAAO;EACT,KAAK,uBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,wBACH,OAAO;EACT,KAAK,kCACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,SACE;CACJ;AACF;;AAGA,SAAgB,8BAA8B,MAA8B;CAC1E,IAAI,CAAC,KAAK,UACR;CAEF,MAAM,EAAE,QAAQ,OAAO,GAAG,aAAa,KAAK;CAE5C,OAAO;AACT"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool, getGeminiProviderToolMetadata } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/google-maps-tool.ts
|
|
3
3
|
function convertGoogleMapsToolToAdapterFormat(tool) {
|
|
4
|
-
return { googleMaps: tool
|
|
4
|
+
return { googleMaps: getGeminiProviderToolMetadata(tool) };
|
|
5
5
|
}
|
|
6
6
|
function googleMapsTool(config) {
|
|
7
|
-
return
|
|
7
|
+
return brandGeminiProviderTool({
|
|
8
8
|
name: "google_maps",
|
|
9
9
|
description: "",
|
|
10
10
|
metadata: config
|
|
11
|
-
});
|
|
11
|
+
}, "google_maps");
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { convertGoogleMapsToolToAdapterFormat, googleMapsTool };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-maps-tool.js","names":[],"sources":["../../../src/tools/google-maps-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"google-maps-tool.js","names":[],"sources":["../../../src/tools/google-maps-tool.ts"],"sourcesContent":["import {\n brandGeminiProviderTool,\n getGeminiProviderToolMetadata,\n} from './gemini-provider-tool'\nimport type { GoogleMaps } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleMapsToolConfig = GoogleMaps\n\n/** @deprecated Renamed to `GoogleMapsToolConfig`. Will be removed in a future release. */\nexport type GoogleMapsTool = GoogleMapsToolConfig\n\nexport type GeminiGoogleMapsTool = ProviderTool<'gemini', 'google_maps'>\n\nexport function convertGoogleMapsToolToAdapterFormat(tool: Tool) {\n const metadata = getGeminiProviderToolMetadata(tool) as GoogleMapsToolConfig\n return {\n googleMaps: metadata,\n }\n}\n\nexport function googleMapsTool(\n config?: GoogleMapsToolConfig,\n): GeminiGoogleMapsTool {\n return brandGeminiProviderTool<GeminiGoogleMapsTool>(\n {\n name: 'google_maps',\n description: '',\n metadata: config,\n },\n 'google_maps',\n )\n}\n"],"mappings":";;AAcA,SAAgB,qCAAqC,MAAY;CAE/D,OAAO,EACL,YAFe,8BAA8B,IAEjC,EACd;AACF;AAEA,SAAgB,eACd,QACsB;CACtB,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU;CACZ,GACA,aACF;AACF"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool, getGeminiProviderToolMetadata } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/google-search-retriveal-tool.ts
|
|
3
3
|
function convertGoogleSearchRetrievalToolToAdapterFormat(tool) {
|
|
4
|
-
return { googleSearchRetrieval: tool
|
|
4
|
+
return { googleSearchRetrieval: getGeminiProviderToolMetadata(tool) };
|
|
5
5
|
}
|
|
6
6
|
function googleSearchRetrievalTool(config) {
|
|
7
|
-
return
|
|
7
|
+
return brandGeminiProviderTool({
|
|
8
8
|
name: "google_search_retrieval",
|
|
9
9
|
description: "",
|
|
10
10
|
metadata: config
|
|
11
|
-
});
|
|
11
|
+
}, "google_search_retrieval");
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { convertGoogleSearchRetrievalToolToAdapterFormat, googleSearchRetrievalTool };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-search-retriveal-tool.js","names":[],"sources":["../../../src/tools/google-search-retriveal-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"google-search-retriveal-tool.js","names":[],"sources":["../../../src/tools/google-search-retriveal-tool.ts"],"sourcesContent":["import {\n brandGeminiProviderTool,\n getGeminiProviderToolMetadata,\n} from './gemini-provider-tool'\nimport type { GoogleSearchRetrieval } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleSearchRetrievalToolConfig = GoogleSearchRetrieval\n\n/** @deprecated Renamed to `GoogleSearchRetrievalToolConfig`. Will be removed in a future release. */\nexport type GoogleSearchRetrievalTool = GoogleSearchRetrievalToolConfig\n\nexport type GeminiGoogleSearchRetrievalTool = ProviderTool<\n 'gemini',\n 'google_search_retrieval'\n>\n\nexport function convertGoogleSearchRetrievalToolToAdapterFormat(tool: Tool) {\n const metadata = getGeminiProviderToolMetadata(\n tool,\n ) as GoogleSearchRetrievalToolConfig\n return {\n googleSearchRetrieval: metadata,\n }\n}\n\nexport function googleSearchRetrievalTool(\n config?: GoogleSearchRetrievalToolConfig,\n): GeminiGoogleSearchRetrievalTool {\n return brandGeminiProviderTool<GeminiGoogleSearchRetrievalTool>(\n {\n name: 'google_search_retrieval',\n description: '',\n metadata: config,\n },\n 'google_search_retrieval',\n )\n}\n"],"mappings":";;AAiBA,SAAgB,gDAAgD,MAAY;CAI1E,OAAO,EACL,uBAJe,8BACf,IAGuB,EACzB;AACF;AAEA,SAAgB,0BACd,QACiC;CACjC,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU;CACZ,GACA,yBACF;AACF"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool, getGeminiProviderToolMetadata } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/google-search-tool.ts
|
|
3
3
|
function convertGoogleSearchToolToAdapterFormat(tool) {
|
|
4
|
-
return { googleSearch: tool
|
|
4
|
+
return { googleSearch: getGeminiProviderToolMetadata(tool) };
|
|
5
5
|
}
|
|
6
6
|
function googleSearchTool(config) {
|
|
7
|
-
return
|
|
7
|
+
return brandGeminiProviderTool({
|
|
8
8
|
name: "google_search",
|
|
9
9
|
description: "",
|
|
10
10
|
metadata: config
|
|
11
|
-
});
|
|
11
|
+
}, "google_search");
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { convertGoogleSearchToolToAdapterFormat, googleSearchTool };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-search-tool.js","names":[],"sources":["../../../src/tools/google-search-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"google-search-tool.js","names":[],"sources":["../../../src/tools/google-search-tool.ts"],"sourcesContent":["import {\n brandGeminiProviderTool,\n getGeminiProviderToolMetadata,\n} from './gemini-provider-tool'\nimport type { GoogleSearch } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleSearchToolConfig = GoogleSearch\n\n/** @deprecated Renamed to `GoogleSearchToolConfig`. Will be removed in a future release. */\nexport type GoogleSearchTool = GoogleSearchToolConfig\n\nexport type GeminiGoogleSearchTool = ProviderTool<'gemini', 'google_search'>\n\nexport function convertGoogleSearchToolToAdapterFormat(tool: Tool) {\n const metadata = getGeminiProviderToolMetadata(tool) as GoogleSearchToolConfig\n return {\n googleSearch: metadata,\n }\n}\n\nexport function googleSearchTool(\n config?: GoogleSearchToolConfig,\n): GeminiGoogleSearchTool {\n return brandGeminiProviderTool<GeminiGoogleSearchTool>(\n {\n name: 'google_search',\n description: '',\n metadata: config,\n },\n 'google_search',\n )\n}\n"],"mappings":";;AAcA,SAAgB,uCAAuC,MAAY;CAEjE,OAAO,EACL,cAFe,8BAA8B,IAE/B,EAChB;AACF;AAEA,SAAgB,iBACd,QACwB;CACxB,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU;CACZ,GACA,eACF;AACF"}
|