From d4874c6b97de36e858427df2e2d4e10344b1acdd Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sun, 29 Mar 2026 13:16:57 +0300 Subject: [PATCH 01/16] Added gemini ephemeral realtime token --- .changeset/huge-lizards-admire.md | 5 + packages/typescript/ai-gemini/package.json | 6 +- .../ai-gemini/src/realtime/index.ts | 12 + .../ai-gemini/src/realtime/token.ts | 79 +++ .../ai-gemini/src/realtime/types.ts | 30 + pnpm-lock.yaml | 614 +++++++++++++----- 6 files changed, 570 insertions(+), 176 deletions(-) create mode 100644 .changeset/huge-lizards-admire.md create mode 100644 packages/typescript/ai-gemini/src/realtime/index.ts create mode 100644 packages/typescript/ai-gemini/src/realtime/token.ts create mode 100644 packages/typescript/ai-gemini/src/realtime/types.ts diff --git a/.changeset/huge-lizards-admire.md b/.changeset/huge-lizards-admire.md new file mode 100644 index 000000000..cc17b39d0 --- /dev/null +++ b/.changeset/huge-lizards-admire.md @@ -0,0 +1,5 @@ +--- +'@tanstack/ai-gemini': minor +--- + +Added Gemini Realtime Adapter diff --git a/packages/typescript/ai-gemini/package.json b/packages/typescript/ai-gemini/package.json index 17fa4705c..e616b5b17 100644 --- a/packages/typescript/ai-gemini/package.json +++ b/packages/typescript/ai-gemini/package.json @@ -40,13 +40,15 @@ "adapter" ], "dependencies": { - "@google/genai": "^1.43.0" + "@google/genai": "^1.46.0" }, "peerDependencies": { - "@tanstack/ai": "workspace:^" + "@tanstack/ai": "workspace:^", + "@tanstack/ai-client": "workspace:^" }, "devDependencies": { "@tanstack/ai": "workspace:*", + "@tanstack/ai-client": "workspace:*", "@vitest/coverage-v8": "4.0.14", "vite": "^7.2.7" } diff --git a/packages/typescript/ai-gemini/src/realtime/index.ts b/packages/typescript/ai-gemini/src/realtime/index.ts new file mode 100644 index 000000000..be743cb47 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/index.ts @@ -0,0 +1,12 @@ +// Token adapter for server-side use +export { geminiRealtimeToken } from './token' + +// Client adapter for browser use +export { geminiRealtime } from './adapter' + +// Types +export type { + GeminiRealtimeModel, + GeminiRealtimeTokenOptions, + GeminiRealtimeOptions, +} from './types' diff --git a/packages/typescript/ai-gemini/src/realtime/token.ts b/packages/typescript/ai-gemini/src/realtime/token.ts new file mode 100644 index 000000000..ed6ebd3fb --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/token.ts @@ -0,0 +1,79 @@ +import { GoogleGenAI, Modality } from "@google/genai"; +import { getGeminiApiKeyFromEnv } from "../utils"; +import type { RealtimeToken, RealtimeTokenAdapter } from "@tanstack/ai"; +import type { GeminiRealtimeModel, GeminiRealtimeTokenOptions } from "./types"; + +/** + * Creates a Google Gemini realtime token adapter. + * + * This adapter generates ephemeral tokens for client-side WebSocket connections. + * + * @param options - Configuration options for the realtime session + * @returns A RealtimeTokenAdapter for use with realtimeToken() + * + * @example + * ```typescript + * import { realtimeToken } from '@tanstack/ai' + * import { geminiRealtimeToken } from '@tanstack/ai-gemini' + * + * const token = await realtimeToken({ + * adapter: geminiRealtimeToken({ + * model: 'gemini-live-2.5-flash-native-audio', + * }), + * }) + * ``` + */ +export function geminiRealtimeToken( + options: GeminiRealtimeTokenOptions = {}, +): RealtimeTokenAdapter { + const apiKey = getGeminiApiKeyFromEnv() + + const client = new GoogleGenAI({ + apiKey, + }); + + // Defaults to 30 minutes + const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000; + + return { + provider: 'gemini', + async generateToken(): Promise { + const model: GeminiRealtimeModel = + options.model ?? "gemini-live-2.5-flash-native-audio" + + const token = await client.authTokens.create({ + config: { + uses: 1, // The default + expireTime: new Date(expireTime).toISOString(), + liveConnectConstraints: { + model, + config: { + sessionResumption: {}, + maxOutputTokens: options.maxOutputTokens, + responseModalities: [Modality.AUDIO] + } + }, + httpOptions: { + apiVersion: 'v1alpha' + } + } + }); + + if (!token.name) { + throw new Error('Gemini realtime token creation failed') + } + + return { + provider: 'gemini', + token: token.name, + expiresAt: expireTime, + config: { + model, + maxOutputTokens: options.maxOutputTokens, + outputModalities: ["audio"], + } + } + } + } +} + diff --git a/packages/typescript/ai-gemini/src/realtime/types.ts b/packages/typescript/ai-gemini/src/realtime/types.ts new file mode 100644 index 000000000..3eb89f661 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -0,0 +1,30 @@ +/** + * Gemini realtime model options + */ +export type GeminiRealtimeModel = + | 'gemini-live-2.5-flash-native-audio' + +/** + * Options for the Gemini realtime client adapter + */ +export interface GeminiRealtimeOptions { + /** Connection mode (default: 'websocket' in browser) */ + connectionMode?: 'websocket' +} + +/** + * Options for the Gemini realtime token adapter + */ +export interface GeminiRealtimeTokenOptions { + /** Model to use (default: 'gemini-live-2.5-flash-native-audio') */ + model?: GeminiRealtimeModel + expiresAt?: number + maxOutputTokens?: number +} + +/** + * Gemini Realtime session response from the API + */ +export interface GeminiRealtimeSessionResponse { + +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37caa9ab7..d8c40f0cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -673,7 +673,7 @@ importers: devDependencies: vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai: dependencies: @@ -689,7 +689,7 @@ importers: version: 1.1.0 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) zod: specifier: ^4.2.0 version: 4.2.1 @@ -705,7 +705,7 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) zod: specifier: ^4.2.0 version: 4.2.1 @@ -721,10 +721,10 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) zod: specifier: ^4.2.0 version: 4.2.1 @@ -752,22 +752,22 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 version: 27.3.0(postcss@8.5.6) tsup: specifier: ^8.5.1 - version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.0.1))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.0.1))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-solid: specifier: ^2.11.10 - version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) packages/typescript/ai-elevenlabs: dependencies: @@ -783,7 +783,7 @@ importers: version: link:../ai-client '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) packages/typescript/ai-event-client: dependencies: @@ -796,7 +796,7 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) packages/typescript/ai-fal: dependencies: @@ -809,26 +809,29 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-gemini: dependencies: '@google/genai': - specifier: ^1.43.0 - version: 1.43.0 + specifier: ^1.46.0 + version: 1.46.0 devDependencies: '@tanstack/ai': specifier: workspace:* version: link:../ai + '@tanstack/ai-client': + specifier: workspace:* + version: link:../ai-client '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: - specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-grok: dependencies: @@ -837,17 +840,17 @@ importers: version: link:../ai openai: specifier: ^6.9.1 - version: 6.10.0(ws@8.18.3)(zod@4.2.1) + version: 6.10.0(ws@8.20.0)(zod@4.2.1) zod: specifier: ^4.0.0 version: 4.2.1 devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-groq: dependencies: @@ -863,10 +866,10 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-ollama: dependencies: @@ -879,16 +882,16 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-openai: dependencies: openai: specifier: ^6.9.1 - version: 6.10.0(ws@8.18.3)(zod@4.2.1) + version: 6.10.0(ws@8.20.0)(zod@4.2.1) devDependencies: '@tanstack/ai': specifier: workspace:* @@ -898,10 +901,10 @@ importers: version: link:../ai-client '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) zod: specifier: ^4.2.0 version: 4.2.1 @@ -917,10 +920,10 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-preact: dependencies: @@ -936,7 +939,7 @@ importers: version: 3.2.4(preact@10.28.2) '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 version: 27.3.0(postcss@8.5.6) @@ -945,7 +948,7 @@ importers: version: 10.28.2 vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-react: dependencies: @@ -964,7 +967,7 @@ importers: version: 19.2.7 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 version: 27.3.0(postcss@8.5.6) @@ -973,7 +976,7 @@ importers: version: 19.2.3 vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-react-ui: dependencies: @@ -1004,7 +1007,7 @@ importers: version: 19.2.7 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) react: specifier: ^19.2.3 version: 19.2.3 @@ -1013,7 +1016,7 @@ importers: version: 19.2.3(react@19.2.3) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-solid: dependencies: @@ -1075,13 +1078,13 @@ importers: version: link:../ai-solid '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) solid-js: specifier: ^1.9.10 version: 1.9.10 vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-svelte: dependencies: @@ -1103,7 +1106,7 @@ importers: version: 24.10.3 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 version: 27.3.0(postcss@8.5.6) @@ -1177,13 +1180,13 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^6.0.2 - version: 6.0.3(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) + version: 6.0.3(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vue: specifier: ^3.5.25 version: 3.5.25(typescript@5.9.3) @@ -1205,10 +1208,10 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/react-ai-devtools: dependencies: @@ -1224,13 +1227,13 @@ importers: version: 19.2.7 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) react: specifier: ^19.2.3 version: 19.2.3 vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/smoke-tests: {} @@ -1364,16 +1367,16 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) solid-js: specifier: ^1.9.10 version: 1.9.10 vite: specifier: ^7.2.7 - version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-solid: specifier: ^2.11.10 - version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) testing/panel: dependencies: @@ -1619,6 +1622,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} @@ -2435,6 +2443,10 @@ packages: resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.4.2': resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2447,6 +2459,10 @@ packages: resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.39.1': resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2474,8 +2490,8 @@ packages: '@gerrit0/mini-shiki@3.19.0': resolution: {integrity: sha512-ZSlWfLvr8Nl0T4iA3FF/8VH8HivYF82xQts2DY0tJxZd4wtXJ8AA0nmdW9lmO4hlrh3f9xNwEPtOgqETPqKwDA==} - '@google/genai@1.43.0': - resolution: {integrity: sha512-hklCsJNdMlDM1IwcCVcGQFBg2izY0+t5BIGbRsxi2UnKi6AGKL7pqJqmBDNRbw0bYCs4y3NA7TB+fkKfP/Nrdw==} + '@google/genai@1.46.0': + resolution: {integrity: sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -2646,21 +2662,25 @@ packages: resolution: {integrity: sha512-K6l/qa1rUM1saFlcT/KnJfhRtLyPkpYCxWGNYaMQ3gEFozPCHYdAJUQ+sKS8kVyWt2anAWx2XkmXUaz04OB8BQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@nx/nx-linux-arm64-musl@22.1.2': resolution: {integrity: sha512-vZUAUsaop5fdcyWpYzED+hWTKOuDtwG9DNNYUlII0dZhSA8kZwmXoYmrCGeMe5nQX9tF4pNzF+oddC/E169Z6g==} cpu: [arm64] os: [linux] + libc: [musl] '@nx/nx-linux-x64-gnu@22.1.2': resolution: {integrity: sha512-+NiA5uNh1cdpk2k984NlfIxRXaO0Bu0S4qCvWWKmL/150f31qJ/eHN6rd78/Re2qKO1NDoyDZLW6jqRXIm/GgA==} cpu: [x64] os: [linux] + libc: [glibc] '@nx/nx-linux-x64-musl@22.1.2': resolution: {integrity: sha512-8O7dXems/Of/biCKeuGMh3nmbS2PNvaL8R4xQzaBl94XitzFMxVFjjoTST7y3Ksmsa5Wrbzwyh+kHOMoIMlVpA==} cpu: [x64] os: [linux] + libc: [musl] '@nx/nx-win32-arm64-msvc@22.1.2': resolution: {integrity: sha512-/Wt3kdj5BksswSWL4N8tef6B+d5r0LbdEPqZimx3AqDMC9H1YkVuwwdBWFGOh+ldj/N8adRuZKjEMQfa/oqPGg==} @@ -2757,48 +2777,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-arm64-musl@0.110.0': resolution: {integrity: sha512-53GjCVY8kvymk9P6qNDh6zyblcehF5QHstq9QgCjv13ONGRnSHjeds0PxIwiihD7h295bxsWs84DN39syLPH4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-minify/binding-linux-ppc64-gnu@0.110.0': resolution: {integrity: sha512-li8XcN81dxbJDMBESnTgGhoiAQ+CNIdM0QGscZ4duVPjCry1RpX+5FJySFbGqG3pk4s9ZzlL/vtQtbRzZIZOzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-riscv64-gnu@0.110.0': resolution: {integrity: sha512-SweKfsnLKShu6UFV8mwuj1d1wmlNoL/FlAxPUzwjEBgwiT2HQkY24KnjBH+TIA+//1O83kzmWKvvs4OuEhdIEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-riscv64-musl@0.110.0': resolution: {integrity: sha512-oH8G4aFMP8XyTsEpdANC5PQyHgSeGlopHZuW1rpyYcaErg5YaK0vXjQ4EM5HVvPm+feBV24JjxgakTnZoF3aOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-minify/binding-linux-s390x-gnu@0.110.0': resolution: {integrity: sha512-W9na+Vza7XVUlpf8wMt4QBfH35KeTENEmnpPUq3NSlbQHz8lSlSvhAafvo43NcKvHAXV3ckD/mUf2VkqSdbklg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-x64-gnu@0.110.0': resolution: {integrity: sha512-XJdA4mmmXOjJxSRgNJXsDP7Xe8h3gQhmb56hUcCrvq5d+h5UcEi2pR8rxsdIrS8QmkLuBA3eHkGK8E27D7DTgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-minify/binding-linux-x64-musl@0.110.0': resolution: {integrity: sha512-QqzvALuOTtSckI8x467R4GNArzYDb/yEh6aNzLoeaY1O7vfT7SPDwlOEcchaTznutpeS9Dy8gUS/AfqtUHaufw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-minify/binding-openharmony-arm64@0.110.0': resolution: {integrity: sha512-gAMssLs2Q3+uhLZxanh1DF+27Kaug3cf4PXb9AB7XK81DR+LVcKySXaoGYoOs20Co0fFSphd6rRzKge2qDK3dA==} @@ -2871,41 +2899,49 @@ packages: resolution: {integrity: sha512-SVjjjtMW66Mza76PBGJLqB0KKyFTBnxmtDXLJPbL6ZPGSctcXVmujz7/WAc0rb9m2oV0cHQTtVjnq6orQnI/jg==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.15.0': resolution: {integrity: sha512-JDv2/AycPF2qgzEiDeMJCcSzKNDm3KxNg0KKWipoKEMDFqfM7LxNwwSVyAOGmrYlE4l3dg290hOMsr9xG7jv9g==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.15.0': resolution: {integrity: sha512-zbu9FhvBLW4KJxo7ElFvZWbSt4vP685Qc/Gyk/Ns3g2gR9qh2qWXouH8PWySy+Ko/qJ42+HJCLg+ZNcxikERfg==} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.15.0': resolution: {integrity: sha512-Kfleehe6B09C2qCnyIU01xLFqFXCHI4ylzkicfX/89j+gNHh9xyNdpEvit88Kq6i5tTGdavVnM6DQfOE2qNtlg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.15.0': resolution: {integrity: sha512-J7LPiEt27Tpm8P+qURDwNc8q45+n+mWgyys4/V6r5A8v5gDentHRGUx3iVk5NxdKhgoGulrzQocPTZVosq25Eg==} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.15.0': resolution: {integrity: sha512-+8/d2tAScPjVJNyqa7GPGnqleTB/XW9dZJQ2D/oIM3wpH3TG+DaFEXBbk4QFJ9K9AUGBhvQvWU2mQyhK/yYn3Q==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.15.0': resolution: {integrity: sha512-xtvSzH7Nr5MCZI2FKImmOdTl9kzuQ51RPyLh451tvD2qnkg3BaqI9Ox78bTk57YJhlXPuxWSOL5aZhKAc9J6qg==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.15.0': resolution: {integrity: sha512-14YL1zuXj06+/tqsuUZuzL0T425WA/I4nSVN1kBXeC5WHxem6lQ+2HGvG+crjeJEqHgZUT62YIgj88W+8E7eyg==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.15.0': resolution: {integrity: sha512-/7Qli+1Wk93coxnrQaU8ySlICYN8HsgyIrzqjgIkQEpI//9eUeaeIHZptNl2fMvBGeXa7k2QgLbRNaBRgpnvMw==} @@ -2979,48 +3015,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-arm64-musl@0.110.0': resolution: {integrity: sha512-e5JN94/oy+wevk76q+LMr+2klTTcO60uXa+Wkq558Ms7mdF2TvkKFI++d/JeiuIwJLTi/BxQ4qdT5FWcsHM/ug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-ppc64-gnu@0.110.0': resolution: {integrity: sha512-Y3/Tnnz1GvDpmv8FXBIKtdZPsdZklOEPdrL6NHrN5i2u54BOkybFaDSptgWF53wOrJlTrcmAVSE6fRKK9XCM2Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-gnu@0.110.0': resolution: {integrity: sha512-Y0E35iA9/v9jlkNcP6tMJ+ZFOS0rLsWDqG6rU9z+X2R3fBFJBO9UARIK6ngx8upxk81y1TFR2CmBFhupfYdH6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-musl@0.110.0': resolution: {integrity: sha512-JOUSYFfHjBUs7xp2FHmZHb8eTYD/oEu0NklS6JgUauqnoXZHiTLPLVW2o2uVCqldnabYHcomuwI2iqVFYJNhTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-s390x-gnu@0.110.0': resolution: {integrity: sha512-7blgoXF9D3Ngzb7eun23pNrHJpoV/TtE6LObwlZ3Nmb4oZ6Z+yMvBVaoW68NarbmvNGfZ95zrOjgm6cVETLYBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-gnu@0.110.0': resolution: {integrity: sha512-YQ2joGWCVDZVEU2cD/r/w49hVjDm/Qu1BvC/7zs8LvprzdLS/HyMXGF2oA0puw0b+AqgYaz3bhwKB2xexHyITQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-musl@0.110.0': resolution: {integrity: sha512-fkjr5qE632ULmNgvFXWDR/8668WxERz3tU7TQFp6JebPBneColitjSkdx6VKNVXEoMmQnOvBIGeP5tUNT384oA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-transform/binding-openharmony-arm64@0.110.0': resolution: {integrity: sha512-HWH9Zj+lMrdSTqFRCZsvDWMz7OnMjbdGsm3xURXWfRZpuaz0bVvyuZNDQXc4FyyhRDsemICaJbU1bgeIpUJDGw==} @@ -3080,36 +3124,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.3.0': resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} @@ -3238,24 +3288,28 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': resolution: {integrity: sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==} @@ -3443,121 +3497,145 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.57.1': resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -3849,24 +3927,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -4556,8 +4638,8 @@ packages: '@types/node@24.10.3': resolution: {integrity: sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==} - '@types/node@25.0.1': - resolution: {integrity: sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==} + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} @@ -4686,41 +4768,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4784,12 +4874,26 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@4.0.14': + resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} + '@vitest/expect@4.0.15': resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} '@vitest/expect@4.0.18': resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/mocker@4.0.14': + resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@4.0.15': resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: @@ -4821,18 +4925,27 @@ packages: '@vitest/pretty-format@4.0.18': resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@vitest/runner@4.0.14': + resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} + '@vitest/runner@4.0.15': resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} '@vitest/runner@4.0.18': resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/snapshot@4.0.14': + resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} + '@vitest/snapshot@4.0.15': resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} '@vitest/snapshot@4.0.18': resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/spy@4.0.14': + resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} + '@vitest/spy@4.0.15': resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} @@ -4985,6 +5098,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} @@ -5076,8 +5192,8 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} - ast-v8-to-istanbul@0.3.11: - resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} + ast-v8-to-istanbul@0.3.12: + resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} async-sema@3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} @@ -6184,8 +6300,8 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gaxios@7.1.3: - resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} + gaxios@7.1.4: + resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==} engines: {node: '>=18'} gcp-metadata@8.1.2: @@ -6271,8 +6387,8 @@ packages: peerDependencies: csstype: ^3.0.10 - google-auth-library@10.5.0: - resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} + google-auth-library@10.6.2: + resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==} engines: {node: '>=18'} google-logging-utils@1.1.3: @@ -6289,10 +6405,6 @@ packages: groq-sdk@0.37.0: resolution: {integrity: sha512-lT72pcT8b/X5XrzdKf+rWVzUGW1OQSKESmL8fFN5cTbsf02gq6oFam4SVeNtzELt9cYE2Pt3pdGgSImuTbHFDg==} - gtoken@8.0.0: - resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} - engines: {node: '>=18'} - gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6916,24 +7028,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -7311,6 +7427,9 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -8030,10 +8149,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} - hasBin: true - robot3@0.4.1: resolution: {integrity: sha512-hzjy826lrxzx8eRgv80idkf8ua1JAepRc9Efdtj03N3KNJuznQCPlyCJ7gnUmDFwZCLQjxy567mQVKmdv2BsXQ==} @@ -8576,6 +8691,10 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tldts-core@7.0.19: resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} @@ -8802,6 +8921,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici@7.16.0: resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} engines: {node: '>=20.18.1'} @@ -9239,6 +9361,40 @@ packages: vite: optional: true + vitest@4.0.14: + resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.14 + '@vitest/browser-preview': 4.0.14 + '@vitest/browser-webdriverio': 4.0.14 + '@vitest/ui': 4.0.14 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@4.0.15: resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -9458,6 +9614,18 @@ packages: utf-8-validate: optional: true + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -9704,6 +9872,10 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10328,6 +10500,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + '@eslint/config-helpers@0.4.2': dependencies: '@eslint/core': 0.17.0 @@ -10350,6 +10530,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/js@9.39.1': {} '@eslint/js@9.39.2': {} @@ -10377,12 +10571,12 @@ snapshots: '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 - '@google/genai@1.43.0': + '@google/genai@1.46.0': dependencies: - google-auth-library: 10.5.0 + google-auth-library: 10.6.2 p-retry: 4.6.2 protobufjs: 7.5.4 - ws: 8.18.3 + ws: 8.20.0 transitivePeerDependencies: - bufferutil - supports-color @@ -10504,11 +10698,11 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor-model@7.29.6(@types/node@25.0.1)': + '@microsoft/api-extractor-model@7.29.6(@types/node@25.5.0)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.7.0(@types/node@25.0.1) + '@rushstack/node-core-library': 5.7.0(@types/node@25.5.0) transitivePeerDependencies: - '@types/node' optional: true @@ -10531,15 +10725,15 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.47.7(@types/node@25.0.1)': + '@microsoft/api-extractor@7.47.7(@types/node@25.5.0)': dependencies: - '@microsoft/api-extractor-model': 7.29.6(@types/node@25.0.1) + '@microsoft/api-extractor-model': 7.29.6(@types/node@25.5.0) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.7.0(@types/node@25.0.1) + '@rushstack/node-core-library': 5.7.0(@types/node@25.5.0) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.14.0(@types/node@25.0.1) - '@rushstack/ts-command-line': 4.22.6(@types/node@25.0.1) + '@rushstack/terminal': 0.14.0(@types/node@25.5.0) + '@rushstack/ts-command-line': 4.22.6(@types/node@25.5.0) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.11 @@ -11260,7 +11454,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.3 - '@rushstack/node-core-library@5.7.0(@types/node@25.0.1)': + '@rushstack/node-core-library@5.7.0(@types/node@25.5.0)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -11271,7 +11465,7 @@ snapshots: resolve: 1.22.11 semver: 7.5.4 optionalDependencies: - '@types/node': 25.0.1 + '@types/node': 25.5.0 optional: true '@rushstack/rig-package@0.5.3': @@ -11286,12 +11480,12 @@ snapshots: optionalDependencies: '@types/node': 24.10.3 - '@rushstack/terminal@0.14.0(@types/node@25.0.1)': + '@rushstack/terminal@0.14.0(@types/node@25.5.0)': dependencies: - '@rushstack/node-core-library': 5.7.0(@types/node@25.0.1) + '@rushstack/node-core-library': 5.7.0(@types/node@25.5.0) supports-color: 8.1.1 optionalDependencies: - '@types/node': 25.0.1 + '@types/node': 25.5.0 optional: true '@rushstack/ts-command-line@4.22.6(@types/node@24.10.3)': @@ -11303,9 +11497,9 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@rushstack/ts-command-line@4.22.6(@types/node@25.0.1)': + '@rushstack/ts-command-line@4.22.6(@types/node@25.5.0)': dependencies: - '@rushstack/terminal': 0.14.0(@types/node@25.0.1) + '@rushstack/terminal': 0.14.0(@types/node@25.5.0) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -11616,7 +11810,7 @@ snapshots: '@tanstack/devtools-event-bus@0.3.3': dependencies: - ws: 8.18.3 + ws: 8.20.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -13051,10 +13245,9 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.0.1': + '@types/node@25.5.0': dependencies: - undici-types: 7.16.0 - optional: true + undici-types: 7.18.2 '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: @@ -13341,17 +13534,17 @@ snapshots: vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vue: 3.5.25(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.3(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.3(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.53 - vite: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vue: 3.5.25(typescript@5.9.3) - '@vitest/coverage-v8@4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.14 - ast-v8-to-istanbul: 0.3.11 + ast-v8-to-istanbul: 0.3.12 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -13359,16 +13552,16 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 std-env: 3.10.0 - tinyrainbow: 3.0.3 - vitest: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + tinyrainbow: 3.1.0 + vitest: 4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.14(vitest@4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.14 - ast-v8-to-istanbul: 0.3.11 + ast-v8-to-istanbul: 0.3.12 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -13376,16 +13569,16 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 std-env: 3.10.0 - tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + tinyrainbow: 3.1.0 + vitest: 4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.14(vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.14 - ast-v8-to-istanbul: 0.3.11 + ast-v8-to-istanbul: 0.3.12 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -13393,11 +13586,20 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 std-env: 3.10.0 - tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + tinyrainbow: 3.1.0 + vitest: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color + '@vitest/expect@4.0.14': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + chai: 6.2.2 + tinyrainbow: 3.1.0 + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 @@ -13416,33 +13618,41 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.15(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.14(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.15 + '@vitest/spy': 4.0.14 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.14(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.18 + '@vitest/spy': 4.0.14 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.15(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.0.15 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@4.0.14': dependencies: - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@vitest/pretty-format@4.0.15': dependencies: @@ -13452,6 +13662,11 @@ snapshots: dependencies: tinyrainbow: 3.0.3 + '@vitest/runner@4.0.14': + dependencies: + '@vitest/utils': 4.0.14 + pathe: 2.0.3 + '@vitest/runner@4.0.15': dependencies: '@vitest/utils': 4.0.15 @@ -13462,6 +13677,12 @@ snapshots: '@vitest/utils': 4.0.18 pathe: 2.0.3 + '@vitest/snapshot@4.0.14': + dependencies: + '@vitest/pretty-format': 4.0.14 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/snapshot@4.0.15': dependencies: '@vitest/pretty-format': 4.0.15 @@ -13474,6 +13695,8 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@4.0.14': {} + '@vitest/spy@4.0.15': {} '@vitest/spy@4.0.18': {} @@ -13481,7 +13704,7 @@ snapshots: '@vitest/utils@4.0.14': dependencies: '@vitest/pretty-format': 4.0.14 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@vitest/utils@4.0.15': dependencies: @@ -13662,6 +13885,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.12.0: dependencies: fast-deep-equal: 3.1.3 @@ -13762,7 +13992,7 @@ snapshots: dependencies: tslib: 2.8.1 - ast-v8-to-istanbul@0.3.11: + ast-v8-to-istanbul@0.3.12: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -14728,17 +14958,17 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -14757,7 +14987,7 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -15027,18 +15257,17 @@ snapshots: functions-have-names@1.2.3: {} - gaxios@7.1.3: + gaxios@7.1.4: dependencies: extend: 3.0.2 https-proxy-agent: 7.0.6 node-fetch: 3.3.2 - rimraf: 5.0.10 transitivePeerDependencies: - supports-color gcp-metadata@8.1.2: dependencies: - gaxios: 7.1.3 + gaxios: 7.1.4 google-logging-utils: 1.1.3 json-bigint: 1.0.0 transitivePeerDependencies: @@ -15145,14 +15374,13 @@ snapshots: dependencies: csstype: 3.2.3 - google-auth-library@10.5.0: + google-auth-library@10.6.2: dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.1.3 + gaxios: 7.1.4 gcp-metadata: 8.1.2 google-logging-utils: 1.1.3 - gtoken: 8.0.0 jws: 4.0.1 transitivePeerDependencies: - supports-color @@ -15175,13 +15403,6 @@ snapshots: transitivePeerDependencies: - encoding - gtoken@8.0.0: - dependencies: - gaxios: 7.1.3 - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - gzip-size@7.0.0: dependencies: duplexer: 0.1.2 @@ -16029,7 +16250,7 @@ snapshots: magicast@0.5.2: dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 source-map-js: 1.2.1 @@ -16462,6 +16683,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.2 @@ -16941,9 +17166,9 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@6.10.0(ws@8.18.3)(zod@4.2.1): + openai@6.10.0(ws@8.20.0)(zod@4.2.1): optionalDependencies: - ws: 8.18.3 + ws: 8.20.0 zod: 4.2.1 optionator@0.9.4: @@ -17245,7 +17470,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 24.10.3 + '@types/node': 25.5.0 long: 5.3.2 proxy-addr@2.0.7: @@ -17477,10 +17702,6 @@ snapshots: reusify@1.1.0: {} - rimraf@5.0.10: - dependencies: - glob: 10.5.0 - robot3@0.4.1: {} rolldown-plugin-dts@0.18.3(oxc-resolver@11.15.0)(rolldown@1.0.0-beta.53)(typescript@5.9.3): @@ -18125,6 +18346,8 @@ snapshots: tinyrainbow@3.0.3: {} + tinyrainbow@3.1.0: {} + tldts-core@7.0.19: {} tldts@7.0.19: @@ -18211,16 +18434,16 @@ snapshots: tslib@2.8.1: {} - tsup-preset-solid@2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.0.1))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + tsup-preset-solid@2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: esbuild-plugin-solid: 0.5.0(esbuild@0.27.3)(solid-js@1.9.10) - tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.0.1))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) transitivePeerDependencies: - esbuild - solid-js - supports-color - tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.0.1))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) cac: 6.7.14 @@ -18240,7 +18463,7 @@ snapshots: tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: - '@microsoft/api-extractor': 7.47.7(@types/node@25.0.1) + '@microsoft/api-extractor': 7.47.7(@types/node@25.5.0) postcss: 8.5.6 typescript: 5.9.3 transitivePeerDependencies: @@ -18346,6 +18569,8 @@ snapshots: undici-types@7.16.0: {} + undici-types@7.18.2: {} + undici@7.16.0: {} undici@7.21.0: {} @@ -18717,7 +18942,7 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@babel/core': 7.28.5 '@types/babel__core': 7.20.5 @@ -18725,8 +18950,8 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.10 solid-refresh: 0.6.3(solid-js@1.9.10) - vite: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - vitefu: 1.1.1(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -18800,7 +19025,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -18809,7 +19034,7 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.1 + '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 @@ -18834,7 +19059,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vite@7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -18843,7 +19068,7 @@ snapshots: rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.1 + '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 @@ -18855,23 +19080,23 @@ snapshots: optionalDependencies: vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - vitefu@1.1.1(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): + vitefu@1.1.1(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: - vite: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vitefu@1.1.1(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.15 - '@vitest/runner': 4.0.15 - '@vitest/snapshot': 4.0.15 - '@vitest/spy': 4.0.15 - '@vitest/utils': 4.0.15 + '@vitest/expect': 4.0.14 + '@vitest/mocker': 4.0.14(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.14 + '@vitest/runner': 4.0.14 + '@vitest/snapshot': 4.0.14 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 es-module-lexer: 1.7.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -18880,9 +19105,9 @@ snapshots: picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 1.0.2 + tinyexec: 0.3.2 tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: @@ -18902,15 +19127,54 @@ snapshots: - tsx - yaml - vitest@4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 + '@vitest/expect': 4.0.14 + '@vitest/mocker': 4.0.14(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.14 + '@vitest/runner': 4.0.14 + '@vitest/snapshot': 4.0.14 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.0 + happy-dom: 20.0.11 + jsdom: 27.3.0(postcss@8.5.6) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + dependencies: + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -18922,7 +19186,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.3 @@ -18941,10 +19205,10 @@ snapshots: - tsx - yaml - vitest@4.0.18(@types/node@25.0.1)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -18961,10 +19225,10 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.0.1 + '@types/node': 24.10.3 happy-dom: 20.0.11 jsdom: 27.3.0(postcss@8.5.6) transitivePeerDependencies: @@ -19125,6 +19389,8 @@ snapshots: ws@8.18.3: {} + ws@8.20.0: {} + xml-name-validator@5.0.0: {} xmlbuilder2@3.1.1: From bb7d7b2c3b61f8450d11a2c70a6cf9627e6a4e80 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sun, 29 Mar 2026 13:22:12 +0300 Subject: [PATCH 02/16] added initial realtime adapter --- packages/typescript/ai-gemini/package.json | 2 +- .../ai-gemini/src/realtime/adapter.ts | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/typescript/ai-gemini/src/realtime/adapter.ts diff --git a/packages/typescript/ai-gemini/package.json b/packages/typescript/ai-gemini/package.json index e616b5b17..02fdc3830 100644 --- a/packages/typescript/ai-gemini/package.json +++ b/packages/typescript/ai-gemini/package.json @@ -50,6 +50,6 @@ "@tanstack/ai": "workspace:*", "@tanstack/ai-client": "workspace:*", "@vitest/coverage-v8": "4.0.14", - "vite": "^7.2.7" + "vite": "^7.3.1" } } diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts new file mode 100644 index 000000000..fdbf1e68e --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -0,0 +1,49 @@ +import type { AnyClientTool, RealtimeEvent, RealtimeEventHandler, RealtimeToken } from "@tanstack/ai" +import type { RealtimeAdapter, RealtimeConnection } from "@tanstack/ai-client" +import type { GeminiRealtimeOptions } from "./types" + +/** + * Creates a Gemini realtime adapter for client-side use. + * + * @param options - Optional configuration + * @returns A RealtimeAdapter for use with RealtimeClient + * + * @example + * ```typescript + * import { RealtimeClient } from '@tanstack/ai-client' + * import { geminiRealtime } from '@tanstack/ai-gemini' + * + * const client = new RealtimeClient({ + * getToken: () => fetch('/api/realtime-token').then(r => r.json()), + * adapter: geminiRealtime(), + * }) + * ``` + */ +export function geminiRealtime( + options: GeminiRealtimeOptions = {} +): RealtimeAdapter { + return { + provider: 'gemini', + + connect( + token: RealtimeToken, + _clientTools?: ReadonlyArray, + ): Promise { + return createWebSocketConnection(token) + } + } +} + +/** + * Creates a WebSocket connection to Gemini's realtime API + */ +function createWebSocketConnection( + token: RealtimeToken +): Promise { + const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' + const eventHandlers = new Map>>() + + return new Promise((resolve, reject) => { + + }) +} \ No newline at end of file From 69ae10ccbddeef1f3187347ee81748a263e3a7c7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:23:39 +0000 Subject: [PATCH 03/16] ci: apply automated fixes --- .../ai-gemini/src/realtime/adapter.ts | 25 +++++++------- .../ai-gemini/src/realtime/token.ts | 33 +++++++++---------- .../ai-gemini/src/realtime/types.ts | 7 ++-- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index fdbf1e68e..9be0b5507 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -1,6 +1,11 @@ -import type { AnyClientTool, RealtimeEvent, RealtimeEventHandler, RealtimeToken } from "@tanstack/ai" -import type { RealtimeAdapter, RealtimeConnection } from "@tanstack/ai-client" -import type { GeminiRealtimeOptions } from "./types" +import type { + AnyClientTool, + RealtimeEvent, + RealtimeEventHandler, + RealtimeToken, +} from '@tanstack/ai' +import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' +import type { GeminiRealtimeOptions } from './types' /** * Creates a Gemini realtime adapter for client-side use. @@ -20,17 +25,17 @@ import type { GeminiRealtimeOptions } from "./types" * ``` */ export function geminiRealtime( - options: GeminiRealtimeOptions = {} + options: GeminiRealtimeOptions = {}, ): RealtimeAdapter { return { provider: 'gemini', - + connect( token: RealtimeToken, _clientTools?: ReadonlyArray, ): Promise { return createWebSocketConnection(token) - } + }, } } @@ -38,12 +43,10 @@ export function geminiRealtime( * Creates a WebSocket connection to Gemini's realtime API */ function createWebSocketConnection( - token: RealtimeToken + token: RealtimeToken, ): Promise { const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' const eventHandlers = new Map>>() - return new Promise((resolve, reject) => { - - }) -} \ No newline at end of file + return new Promise((resolve, reject) => {}) +} diff --git a/packages/typescript/ai-gemini/src/realtime/token.ts b/packages/typescript/ai-gemini/src/realtime/token.ts index ed6ebd3fb..edeb43f9c 100644 --- a/packages/typescript/ai-gemini/src/realtime/token.ts +++ b/packages/typescript/ai-gemini/src/realtime/token.ts @@ -1,7 +1,7 @@ -import { GoogleGenAI, Modality } from "@google/genai"; -import { getGeminiApiKeyFromEnv } from "../utils"; -import type { RealtimeToken, RealtimeTokenAdapter } from "@tanstack/ai"; -import type { GeminiRealtimeModel, GeminiRealtimeTokenOptions } from "./types"; +import { GoogleGenAI, Modality } from '@google/genai' +import { getGeminiApiKeyFromEnv } from '../utils' +import type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai' +import type { GeminiRealtimeModel, GeminiRealtimeTokenOptions } from './types' /** * Creates a Google Gemini realtime token adapter. @@ -30,16 +30,16 @@ export function geminiRealtimeToken( const client = new GoogleGenAI({ apiKey, - }); + }) // Defaults to 30 minutes - const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000; + const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000 return { provider: 'gemini', async generateToken(): Promise { const model: GeminiRealtimeModel = - options.model ?? "gemini-live-2.5-flash-native-audio" + options.model ?? 'gemini-live-2.5-flash-native-audio' const token = await client.authTokens.create({ config: { @@ -50,14 +50,14 @@ export function geminiRealtimeToken( config: { sessionResumption: {}, maxOutputTokens: options.maxOutputTokens, - responseModalities: [Modality.AUDIO] - } + responseModalities: [Modality.AUDIO], + }, }, httpOptions: { - apiVersion: 'v1alpha' - } - } - }); + apiVersion: 'v1alpha', + }, + }, + }) if (!token.name) { throw new Error('Gemini realtime token creation failed') @@ -70,10 +70,9 @@ export function geminiRealtimeToken( config: { model, maxOutputTokens: options.maxOutputTokens, - outputModalities: ["audio"], - } + outputModalities: ['audio'], + }, } - } + }, } } - diff --git a/packages/typescript/ai-gemini/src/realtime/types.ts b/packages/typescript/ai-gemini/src/realtime/types.ts index 3eb89f661..8cef9b310 100644 --- a/packages/typescript/ai-gemini/src/realtime/types.ts +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -1,8 +1,7 @@ /** * Gemini realtime model options */ -export type GeminiRealtimeModel = - | 'gemini-live-2.5-flash-native-audio' +export type GeminiRealtimeModel = 'gemini-live-2.5-flash-native-audio' /** * Options for the Gemini realtime client adapter @@ -25,6 +24,4 @@ export interface GeminiRealtimeTokenOptions { /** * Gemini Realtime session response from the API */ -export interface GeminiRealtimeSessionResponse { - -} \ No newline at end of file +export interface GeminiRealtimeSessionResponse {} From 8d4097f55da0bfb554acfbde0fa1bf8b4abb9ceb Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Mon, 13 Apr 2026 22:44:14 +0300 Subject: [PATCH 04/16] Added gemini adapter, refactored realtime client --- .../ai-client/src/realtime-client.ts | 47 +- .../ai-client/src/realtime-types.ts | 9 +- packages/typescript/ai-gemini/src/index.ts | 6 + .../ai-gemini/src/realtime/adapter.ts | 559 +++++++++++++++++- .../ai-gemini/src/realtime/types.ts | 40 +- .../ai-openai/src/realtime/adapter.ts | 5 +- packages/typescript/ai/src/realtime/types.ts | 1 + 7 files changed, 648 insertions(+), 19 deletions(-) diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index 2683294c0..fc63c1109 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -96,14 +96,45 @@ export class RealtimeClient { this.scheduleTokenRefresh() // Connect via adapter (pass tools for providers like ElevenLabs that need them at connect time) - const toolsList = - this.clientTools.size > 0 - ? Array.from(this.clientTools.values()) - : undefined - this.connection = await this.options.adapter.connect( - this.token, - toolsList, - ) + // const toolsList = + // this.clientTools.size > 0 + // ? Array.from(this.clientTools.values()) + // : undefined + + const toolsConfig = this.clientTools.size > 0 + ? Array.from(this.clientTools.values()).map((t) => ({ + name: t.name, + description: t.description, + inputSchema: t.inputSchema + ? convertSchemaToJsonSchema(t.inputSchema) + : undefined, + outputSchema: t.outputSchema + ? convertSchemaToJsonSchema(t.outputSchema) + : undefined, + })) + : undefined + const { + instructions, + voice, + vadMode, + outputModalities, + temperature, + maxOutputTokens, + semanticEagerness, + providerOptions, + } = this.options + + this.connection = await this.options.adapter.connect(this.token, { + instructions, + voice, + vadMode, + outputModalities, + temperature, + maxOutputTokens, + semanticEagerness, + providerOptions, + tools: toolsConfig, + }) // Subscribe to connection events this.subscribeToConnectionEvents() diff --git a/packages/typescript/ai-client/src/realtime-types.ts b/packages/typescript/ai-client/src/realtime-types.ts index bffd6df34..6b7880384 100644 --- a/packages/typescript/ai-client/src/realtime-types.ts +++ b/packages/typescript/ai-client/src/realtime-types.ts @@ -25,12 +25,12 @@ export interface RealtimeAdapter { /** * Create a connection using the provided token * @param token - The ephemeral token from the server - * @param clientTools - Optional client-side tools to register with the provider + * @param config - Initial session configuration (voice, instructions, etc.) * @returns A connection instance */ connect: ( token: RealtimeToken, - clientTools?: ReadonlyArray, + config: RealtimeSessionConfig, ) => Promise } @@ -148,6 +148,11 @@ export interface RealtimeClientOptions { */ semanticEagerness?: 'low' | 'medium' | 'high' + /** + * Provider-specific options + */ + providerOptions?: Record + // Callbacks onStatusChange?: (status: RealtimeStatus) => void onModeChange?: (mode: RealtimeMode) => void diff --git a/packages/typescript/ai-gemini/src/index.ts b/packages/typescript/ai-gemini/src/index.ts index 05c46547a..6181ba3c3 100644 --- a/packages/typescript/ai-gemini/src/index.ts +++ b/packages/typescript/ai-gemini/src/index.ts @@ -82,3 +82,9 @@ export type { GeminiDocumentMetadata, GeminiMessageMetadataByModality, } from './message-types' + +// Realtime adapter +export { + geminiRealtime, + geminiRealtimeToken, +} from './realtime/index' diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index 9be0b5507..1fec9979f 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -1,12 +1,54 @@ +import { GoogleGenAI } from '@google/genai' +import { + convertSchemaToJsonSchema, +} from '@tanstack/ai' +import { generateId } from '../utils' +import type { LiveConnectConfig, Modality } from '@google/genai' import type { AnyClientTool, + AudioVisualization, RealtimeEvent, RealtimeEventHandler, - RealtimeToken, -} from '@tanstack/ai' + RealtimeMessage, + RealtimeMode, + RealtimeSessionConfig, + RealtimeToken +} from "@tanstack/ai" import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { GeminiRealtimeOptions } from './types' +const textEncoder = new TextEncoder() +const workletCode = ` +class PCMProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.bufferSize = 4096; + this.buffer = new Float32Array(this.bufferSize); + this.bufferIndex = 0; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + if (!input || !input.length) return true; + + const channelData = input[0]; + + for (let i = 0; i < channelData.length; i++) { + this.buffer[this.bufferIndex++] = channelData[i]; + + if (this.bufferIndex >= this.bufferSize) { + this.port.postMessage(this.buffer); + this.bufferIndex = 0; + } + } + + return true; + } +} + +registerProcessor("pcm-processor", PCMProcessor); +` + /** * Creates a Gemini realtime adapter for client-side use. * @@ -32,9 +74,9 @@ export function geminiRealtime( connect( token: RealtimeToken, - _clientTools?: ReadonlyArray, + config: RealtimeSessionConfig ): Promise { - return createWebSocketConnection(token) + return createWebSocketConnection(token, config) }, } } @@ -42,11 +84,516 @@ export function geminiRealtime( /** * Creates a WebSocket connection to Gemini's realtime API */ -function createWebSocketConnection( +async function createWebSocketConnection( token: RealtimeToken, + config: RealtimeSessionConfig, ): Promise { const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' const eventHandlers = new Map>>() - return new Promise((resolve, reject) => {}) + const responseModalities = config.outputModalities?.map(modality => modality.toUpperCase()) as Array + + const liveConfig: LiveConnectConfig = { + responseModalities, + tools: [{ + functionDeclarations: config.tools + }], + speechConfig: { + voiceConfig: { + prebuiltVoiceConfig: { + voiceName: config.voice + } + } + }, + maxOutputTokens: config.maxOutputTokens !== 'inf' ? config.maxOutputTokens : undefined, + systemInstruction: config.instructions, + temperature: config.temperature, + ...config.providerOptions + }; + + // Audio context + let audioContext: AudioContext | null = null + let inputAnalyser: AnalyserNode | null = null + let outputAnalyser: AnalyserNode | null = null + let inputSource: MediaStreamAudioSourceNode | null = null + let localStream: MediaStream | null = null + + // Audio element for playback (more reliable than AudioContext.destination) + let nextPlayTime = 0 + let scheduledSources: Array = [] + // let audioElement: HTMLAudioElement | null = null + + // Current state + let currentMode: RealtimeMode = 'idle' + let currentMessageId: string | null = null + let messageIdCounter = 0 + + // Empty arrays for when visualization isn't available + // frequencyBinCount = fftSize / 2 = 1024 + const emptyFrequencyData = new Uint8Array(1024) + const emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence + + // Helper to emit events (defined early so it can be used during setup) + function emit( + event: TEvent, + payload: Parameters>[0], + ) { + const handlers = eventHandlers.get(event) + if (handlers) { + for (const handler of handlers) { + handler(payload) + } + } + } + + function generateMessageId(): string { + return `gemini-msg-${Date.now()}-${++messageIdCounter}` + } + + function downsampleBuffer(buffer: Float32Array, sampleRate: number, outSampleRate: number) { + if (outSampleRate === sampleRate) return buffer; + const ratio = sampleRate / outSampleRate; + const newLength = Math.round(buffer.length / ratio); + const result = new Float32Array(newLength); + let offsetResult = 0; + let offsetBuffer = 0; + while (offsetResult < result.length) { + const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio); + let accum = 0, + count = 0; + for ( + let i = offsetBuffer; + i < nextOffsetBuffer && i < buffer.length; + i++ + ) { + accum += buffer[i]!; + count++; + } + result[offsetResult] = accum / count; + offsetResult++; + offsetBuffer = nextOffsetBuffer; + } + return result; + } + + function convertFloat32ToInt16(buffer: Float32Array) { + let l = buffer.length; + const buf = new Int16Array(l); + while (l--) { + buf[l] = Math.min(1, Math.max(-1, buffer[l]!)) * 0x7fff; + } + return buf.toString(); + } + + const ai = new GoogleGenAI({ + apiKey: token.token + }); + + const session = await ai.live.connect({ + model: model, + config: liveConfig, + callbacks: { + onopen() { + emit("status_change", { status: "connected" }) + }, + onclose() { + emit("status_change", { status: "idle" }) + }, + onmessage(response) { + + const content = response.serverContent; + const inputTranscription = content?.inputTranscription; + const outputTranscription = content?.outputTranscription; + + if (response.data) { + // TODO: Decode chunk and play using an `AudioWorklet` or + // buffer them into an AudioContext + + playIncomingAudioChunk(textEncoder.encode(response.data).buffer) + + if (currentMode !== 'speaking') { + currentMode = 'speaking' + emit('mode_change', { mode: 'speaking' }) + } + } + + if ( + inputTranscription && + inputTranscription.text != undefined && + inputTranscription.finished != undefined + ) { + if (inputTranscription.finished && currentMode !== 'thinking') { + currentMode = 'thinking' + emit('mode_change', { mode: 'thinking' }) + } + + emit('transcript', { + isFinal: inputTranscription.finished, + transcript: inputTranscription.text, + role: 'user', + }) + } + + if ( + outputTranscription && + outputTranscription.text != undefined && + outputTranscription.finished != undefined + ) { + emit('transcript', { + isFinal: outputTranscription.finished, + transcript: outputTranscription.text, + role: 'assistant', + }) + } + + if (response.toolCall?.functionCalls) { + + for (const fc of response.toolCall.functionCalls) { + if (!fc.id || !fc.name) { + continue; + } + emit('tool_call', { + toolCallId: fc.id, + input: fc.args, + toolName: fc.name + }) + } + } + + if (response.serverContent?.turnComplete) { + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + + if (response.serverContent.modelTurn?.role == 'model') { + currentMessageId = generateMessageId() + const message: RealtimeMessage = { + id: currentMessageId, + role: 'assistant', + timestamp: Date.now(), + parts: [] + } + + for (const item of response.serverContent.modelTurn.parts || []) { + if (item.text) { + message.parts.push({ + type: 'audio', + transcript: item.text + }) + } + } + + emit('message_complete', { message }) + } + + } + }, + onerror(event) { + emit("error", { + error: new Error(event.message) + }) + }, + } + }); + + // Request microphone access + try { + localStream = await navigator.mediaDevices.getUserMedia({ + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 24000, + }, + }) + } catch (error) { + throw new Error( + `Microphone access required for realtime voice: ${error instanceof Error ? error.message : error}`, + ) + } + + // Set up audio analysis now that we have the stream + await setupAudioAnalysis(localStream) + + // Set up audio analysis for input + async function setupAudioAnalysis(stream: MediaStream) { + if (!audioContext) { + // Best to specify Gemini's 16kHz here if possible for the whole context + audioContext = new AudioContext() + + const blob = new Blob([workletCode], { type: 'application/javascript' }) + const workletUrl = URL.createObjectURL(blob) + + await audioContext.audioWorklet.addModule(workletUrl) + } + + // Resume AudioContext if suspended (browsers require user interaction) + if (audioContext.state === 'suspended') { + audioContext.resume().catch(() => { + // Ignore - visualization just won't work + }) + } + + // 1. Setup Input (Microphone) Analyser + inputAnalyser = audioContext.createAnalyser() + inputAnalyser.fftSize = 2048 // Larger size for more accurate level detection + inputAnalyser.smoothingTimeConstant = 0.3 + + inputSource = audioContext.createMediaStreamSource(stream) + inputSource.connect(inputAnalyser) + + // 2. Setup Output (Gemini) Analyser + outputAnalyser = audioContext.createAnalyser() + outputAnalyser.fftSize = 2048 + outputAnalyser.smoothingTimeConstant = 0.3 + + // Connect output analyser directly to speakers + outputAnalyser.connect(audioContext.destination) + + const source = audioContext.createMediaStreamSource( + stream + ); + const audioWorkletNode = new AudioWorkletNode( + audioContext, + "pcm-processor" + ); + + audioWorkletNode.port.onmessage = (event) => { + if (currentMode === 'listening') { + const downsampled = downsampleBuffer( + event.data, + audioContext!.sampleRate, + 16000 + ); + const pcm16 = convertFloat32ToInt16(downsampled); + session.sendRealtimeInput({ + audio: { + data: pcm16, + mimeType: 'audio/pcm;rate=16000' + } + }) + } + }; + + source.connect(audioWorkletNode); + } + + // Play incoming audio chunk from WebSocket connection + function playIncomingAudioChunk(arrayBuffer: ArrayBuffer) { + if (!audioContext) return + if (audioContext.state === 'suspended') { + audioContext.resume().catch(() => { + // Ignore - visualization just won't work + }) + } + + const pcmData = new Int16Array(arrayBuffer) + const float32Data = new Float32Array(pcmData.length) + for (let i = 0; i < pcmData.length; i++) { + float32Data[i] = pcmData[i]! / 32768.0; + } + + const buffer = audioContext.createBuffer(1, float32Data.length, 24000); + buffer.getChannelData(0).set(float32Data); + + const source = audioContext.createBufferSource(); + source.buffer = buffer; + if (outputAnalyser) { + source.connect(outputAnalyser) + } else { + source.connect(audioContext.destination); + } + + const now = audioContext.currentTime; + nextPlayTime = Math.max(now, nextPlayTime); + source.start(nextPlayTime); + nextPlayTime += buffer.duration; + + scheduledSources.push(source); + source.onended = () => { + const idx = scheduledSources.indexOf(source); + if (idx > -1) scheduledSources.splice(idx, 1); + }; + } + + const connection: RealtimeConnection = { + async disconnect() { + if (localStream) { + for (const track of localStream.getTracks()) { + track.stop() + } + localStream = null + } + + if (audioContext) { + await audioContext.close() + audioContext = null + } + + await session.close(); + + currentMode = 'idle' + emit('status_change', { status: 'idle' }) + }, + + async startAudioCapture() { + // Audio capture is established during connection setup + // This method enables the tracks and signals listening mode + if (localStream) { + for (const track of localStream.getAudioTracks()) { + track.enabled = true + } + } + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + }, + + stopAudioCapture() { + // Disable tracks rather than stopping them to allow re-enabling + if (localStream) { + for (const track of localStream.getAudioTracks()) { + track.enabled = false + } + } + currentMode = 'idle' + emit('mode_change', { mode: 'idle' }) + }, + + sendText(text: string) { + session.sendRealtimeInput({ text }) + currentMode = 'thinking' + emit('mode_change', { mode: 'thinking' }) + }, + + sendImage(imageData: string, mimeType: string) { + // Only accepts raw image data, not URLs + session.sendRealtimeInput({ + media: { + data: imageData, + mimeType: mimeType, + } + }) + currentMode = 'thinking' + emit('mode_change', { mode: 'thinking' }) + }, + + sendToolResult(callId: string, result: string) { + session.sendToolResponse({ + functionResponses: { + id: callId, + response: { + result + } + } + }) + }, + + updateSession() { + // No equivalent of updateSession() exists dynamically as it does in OpenAI + // for updating system instructions, tools, etc mid-session. + }, + + interrupt() { + scheduledSources.forEach((s) => { + try { + s.stop(); + } catch (e) { } + }); + scheduledSources = []; + if (audioContext) { + nextPlayTime = audioContext.currentTime; + } + + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + emit('interrupted', { messageId: currentMessageId ?? undefined }) + }, + + on( + event: TEvent, + handler: RealtimeEventHandler + ): () => void { + if (!eventHandlers.has(event)) { + eventHandlers.set(event, new Set()); + } + eventHandlers.get(event)!.add(handler) + + return () => { + eventHandlers.get(event)!.delete(handler) + } + }, + + getAudioVisualization(): AudioVisualization { + // Helper to calculate audio level from time domain data + // Uses peak amplitude which is more responsive for voice audio meters + function calculateLevel(analyser: AnalyserNode): number { + const data = new Uint8Array(analyser.fftSize) + analyser.getByteTimeDomainData(data) + + // Find peak deviation from center (128 is silence) + // This is more responsive than RMS for voice level meters + let maxDeviation = 0 + for (const sample of data) { + const deviation = Math.abs(sample - 128) + if (deviation > maxDeviation) { + maxDeviation = deviation + } + } + + // Normalize to 0-1 range (max deviation is 128) + // Scale by 1.5x so that ~66% amplitude reads as full scale + // This provides good visual feedback without pegging too early + const normalized = maxDeviation / 128 + return Math.min(1, normalized * 1.5) + } + + return { + get inputLevel() { + if (!inputAnalyser) return 0 + return calculateLevel(inputAnalyser) + }, + + get outputLevel() { + if (!outputAnalyser) return 0 + return calculateLevel(outputAnalyser) + }, + + getInputFrequencyData() { + if (!inputAnalyser) return emptyFrequencyData + const data = new Uint8Array(inputAnalyser.frequencyBinCount) + inputAnalyser.getByteFrequencyData(data) + return data + }, + + getOutputFrequencyData() { + if (!outputAnalyser) return emptyFrequencyData + const data = new Uint8Array(outputAnalyser.frequencyBinCount) + outputAnalyser.getByteFrequencyData(data) + return data + }, + + getInputTimeDomainData() { + if (!inputAnalyser) return emptyTimeDomainData + const data = new Uint8Array(inputAnalyser.fftSize) + inputAnalyser.getByteTimeDomainData(data) + return data + }, + + getOutputTimeDomainData() { + if (!outputAnalyser) return emptyTimeDomainData + const data = new Uint8Array(outputAnalyser.fftSize) + outputAnalyser.getByteTimeDomainData(data) + return data + }, + + get inputSampleRate() { + return 24000 + }, + + get outputSampleRate() { + return 24000 + }, + } + }, + } + + return connection; } diff --git a/packages/typescript/ai-gemini/src/realtime/types.ts b/packages/typescript/ai-gemini/src/realtime/types.ts index 8cef9b310..c9ea619db 100644 --- a/packages/typescript/ai-gemini/src/realtime/types.ts +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -1,7 +1,45 @@ + +/** + * Gemini realtime voice options + */ +export type GeminiRealtimeVoice = + | "Achernar" + | "Achird" + | "Algenib" + | "Algieba" + | "Alnilam" + | "Aoede" + | "Autonoe" + | "Callirrhoe" + | "Charon" + | "Despina" + | "Enceladus" + | "Erinome" + | "Fenrir" + | "Gacrux" + | "Iapetus" + | "Kore" + | "Laomedeia" + | "Leda" + | "Orus" + | "Pulcherrima" + | "Puck" + | "Rasalgethi" + | "Sadachbia" + | "Sadaltager" + | "Schedar" + | "Sulafat" + | "Umbriel" + | "Vindemiatrix" + | "Zephyr" + | "Zubenelgenubi"; + /** * Gemini realtime model options */ -export type GeminiRealtimeModel = 'gemini-live-2.5-flash-native-audio' +export type GeminiRealtimeModel = + | 'gemini-3.1-flash-live-preview' + | 'gemini-2.5-flash-native-audio-preview-12-2025' /** * Options for the Gemini realtime client adapter diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index 35187a5d2..9ac6b82ec 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -43,10 +43,10 @@ export function openaiRealtime( async connect( token: RealtimeToken, - _clientTools?: ReadonlyArray, + config: RealtimeSessionConfig, ): Promise { if (connectionMode === 'webrtc') { - return createWebRTCConnection(token) + return createWebRTCConnection(token, config) } throw new Error('WebSocket connection mode not yet implemented') }, @@ -58,6 +58,7 @@ export function openaiRealtime( */ async function createWebRTCConnection( token: RealtimeToken, + config: RealtimeSessionConfig, ): Promise { const model = token.config.model ?? 'gpt-4o-realtime-preview' const eventHandlers = new Map>>() diff --git a/packages/typescript/ai/src/realtime/types.ts b/packages/typescript/ai/src/realtime/types.ts index daaf6f57c..62c08b8f4 100644 --- a/packages/typescript/ai/src/realtime/types.ts +++ b/packages/typescript/ai/src/realtime/types.ts @@ -22,6 +22,7 @@ export interface RealtimeToolConfig { name: string description: string inputSchema?: Record + outputSchema?: Record } /** From dadfae943dac4b384d3ba0242788b44e7e94ab10 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Mon, 13 Apr 2026 22:44:33 +0300 Subject: [PATCH 05/16] Added gemini live option to example --- examples/ts-react-chat/src/lib/use-realtime.ts | 15 +++++++++++++-- examples/ts-react-chat/src/routes/realtime.tsx | 5 +++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/ts-react-chat/src/lib/use-realtime.ts b/examples/ts-react-chat/src/lib/use-realtime.ts index 848c702ca..1970083c9 100644 --- a/examples/ts-react-chat/src/lib/use-realtime.ts +++ b/examples/ts-react-chat/src/lib/use-realtime.ts @@ -6,9 +6,10 @@ import { elevenlabsRealtime, elevenlabsRealtimeToken, } from '@tanstack/ai-elevenlabs' +import { geminiRealtime, geminiRealtimeToken } from '@tanstack/ai-gemini' import { realtimeClientTools } from '@/lib/realtime-tools' -type Provider = 'openai' | 'elevenlabs' +type Provider = 'openai' | 'elevenlabs' | 'gemini' const getRealtimeTokenFn = createServerFn({ method: 'POST' }) .inputValidator((data: { provider: Provider; agentId?: string }) => { @@ -24,6 +25,12 @@ const getRealtimeTokenFn = createServerFn({ method: 'POST' }) }) } + if (data.provider === 'gemini') { + return realtimeToken({ + adapter: geminiRealtimeToken(), + }) + } + if (data.provider === 'elevenlabs') { const agentId = data.agentId || process.env.ELEVENLABS_AGENT_ID if (!agentId) { @@ -55,7 +62,11 @@ export function useRealtime({ semanticEagerness?: 'low' | 'medium' | 'high' }) { const adapter = - provider === 'openai' ? openaiRealtime() : elevenlabsRealtime() + provider === 'openai' + ? openaiRealtime() + : provider === 'gemini' + ? geminiRealtime() + : elevenlabsRealtime() return useRealtimeChat({ getToken: () => diff --git a/examples/ts-react-chat/src/routes/realtime.tsx b/examples/ts-react-chat/src/routes/realtime.tsx index 3225249e2..9b95cec80 100644 --- a/examples/ts-react-chat/src/routes/realtime.tsx +++ b/examples/ts-react-chat/src/routes/realtime.tsx @@ -13,11 +13,12 @@ import { import { AudioSparkline } from '@/components/AudioSparkline' import { useRealtime } from '@/lib/use-realtime' -type Provider = 'openai' | 'elevenlabs' +type Provider = 'openai' | 'elevenlabs' | 'gemini' type OutputMode = 'audio+text' | 'text-only' | 'audio-only' const PROVIDER_OPTIONS: Array<{ value: Provider; label: string }> = [ { value: 'openai', label: 'OpenAI Realtime' }, + { value: 'gemini', label: 'Google Gemini' }, { value: 'elevenlabs', label: 'ElevenLabs' }, ] @@ -275,7 +276,7 @@ function RealtimePage() { {/* Tools indicator */} - {provider === 'openai' && ( + {(provider === 'openai' || provider === 'gemini') && (
From 07114415808715272d1432e70d91afe804950a5f Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Mon, 13 Apr 2026 23:11:22 +0300 Subject: [PATCH 06/16] Fixed refactoring of realtime client --- .../ai-client/src/realtime-client.ts | 59 ++++++++----------- .../ai-client/src/realtime-types.ts | 1 + .../ai-elevenlabs/src/realtime/adapter.ts | 4 +- .../ai-gemini/src/realtime/adapter.ts | 37 ++++++++---- .../ai-openai/src/realtime/adapter.ts | 14 ++--- 5 files changed, 60 insertions(+), 55 deletions(-) diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index fc63c1109..861c88e52 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -96,23 +96,11 @@ export class RealtimeClient { this.scheduleTokenRefresh() // Connect via adapter (pass tools for providers like ElevenLabs that need them at connect time) - // const toolsList = - // this.clientTools.size > 0 - // ? Array.from(this.clientTools.values()) - // : undefined - - const toolsConfig = this.clientTools.size > 0 - ? Array.from(this.clientTools.values()).map((t) => ({ - name: t.name, - description: t.description, - inputSchema: t.inputSchema - ? convertSchemaToJsonSchema(t.inputSchema) - : undefined, - outputSchema: t.outputSchema - ? convertSchemaToJsonSchema(t.outputSchema) - : undefined, - })) - : undefined + const toolsList = + this.clientTools.size > 0 + ? Array.from(this.clientTools.values()) + : undefined + const { instructions, voice, @@ -124,17 +112,20 @@ export class RealtimeClient { providerOptions, } = this.options - this.connection = await this.options.adapter.connect(this.token, { - instructions, - voice, - vadMode, - outputModalities, - temperature, - maxOutputTokens, - semanticEagerness, - providerOptions, - tools: toolsConfig, - }) + this.connection = await this.options.adapter.connect( + this.token, + { + instructions, + voice, + vadMode, + outputModalities, + temperature, + maxOutputTokens, + semanticEagerness, + providerOptions, + }, + toolsList, + ) // Subscribe to connection events this.subscribeToConnectionEvents() @@ -531,12 +522,12 @@ export class RealtimeClient { const toolsConfig = tools ? Array.from(this.clientTools.values()).map((t) => ({ - name: t.name, - description: t.description, - inputSchema: t.inputSchema - ? convertSchemaToJsonSchema(t.inputSchema) - : undefined, - })) + name: t.name, + description: t.description, + inputSchema: t.inputSchema + ? convertSchemaToJsonSchema(t.inputSchema) + : undefined, + })) : undefined this.connection.updateSession({ diff --git a/packages/typescript/ai-client/src/realtime-types.ts b/packages/typescript/ai-client/src/realtime-types.ts index 6b7880384..c33dd8b14 100644 --- a/packages/typescript/ai-client/src/realtime-types.ts +++ b/packages/typescript/ai-client/src/realtime-types.ts @@ -31,6 +31,7 @@ export interface RealtimeAdapter { connect: ( token: RealtimeToken, config: RealtimeSessionConfig, + clientTools?: ReadonlyArray, ) => Promise } diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index 33bc5344e..4a341a4ad 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -40,9 +40,10 @@ export function elevenlabsRealtime( async connect( token: RealtimeToken, + _config: RealtimeSessionConfig, clientToolDefs?: ReadonlyArray, ): Promise { - return createElevenLabsConnection(token, options, clientToolDefs) + return createElevenLabsConnection(token, clientToolDefs) }, } } @@ -52,7 +53,6 @@ export function elevenlabsRealtime( */ async function createElevenLabsConnection( token: RealtimeToken, - _options: ElevenLabsRealtimeOptions, clientToolDefs?: ReadonlyArray, ): Promise { const eventHandlers = new Map>>() diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index 1fec9979f..45b3270dd 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -1,11 +1,8 @@ import { GoogleGenAI } from '@google/genai' import { convertSchemaToJsonSchema, -} from '@tanstack/ai' -import { generateId } from '../utils' -import type { LiveConnectConfig, Modality } from '@google/genai' +} from "@tanstack/ai" import type { - AnyClientTool, AudioVisualization, RealtimeEvent, RealtimeEventHandler, @@ -13,8 +10,9 @@ import type { RealtimeMode, RealtimeSessionConfig, RealtimeToken -} from "@tanstack/ai" -import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' +} from '@tanstack/ai' +import type { LiveConnectConfig, Modality } from '@google/genai' +import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { GeminiRealtimeOptions } from './types' const textEncoder = new TextEncoder() @@ -74,9 +72,10 @@ export function geminiRealtime( connect( token: RealtimeToken, - config: RealtimeSessionConfig + config: RealtimeSessionConfig, + clientTools?: ReadonlyArray, ): Promise { - return createWebSocketConnection(token, config) + return createWebSocketConnection(token, config, clientTools) }, } } @@ -87,17 +86,31 @@ export function geminiRealtime( async function createWebSocketConnection( token: RealtimeToken, config: RealtimeSessionConfig, + tools?: ReadonlyArray, ): Promise { const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' const eventHandlers = new Map>>() const responseModalities = config.outputModalities?.map(modality => modality.toUpperCase()) as Array + const toolsConfig = tools + ? tools.map((t) => ({ + name: t.name, + description: t.description, + inputSchema: t.inputSchema + ? convertSchemaToJsonSchema(t.inputSchema) + : undefined, + outputSchema: t.outputSchema + ? convertSchemaToJsonSchema(t.outputSchema) + : undefined, + })) + : undefined + const liveConfig: LiveConnectConfig = { responseModalities, - tools: [{ - functionDeclarations: config.tools - }], + tools: toolsConfig ? [{ + functionDeclarations: toolsConfig + }] : undefined, speechConfig: { voiceConfig: { prebuiltVoiceConfig: { @@ -587,7 +600,7 @@ async function createWebSocketConnection( get inputSampleRate() { return 24000 }, - + get outputSampleRate() { return 24000 }, diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index 9ac6b82ec..d10128e70 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -7,7 +7,7 @@ import type { RealtimeMode, RealtimeSessionConfig, RealtimeStatus, - RealtimeToken, + RealtimeToken } from '@tanstack/ai' import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { OpenAIRealtimeOptions } from './types' @@ -43,10 +43,11 @@ export function openaiRealtime( async connect( token: RealtimeToken, - config: RealtimeSessionConfig, + _config: RealtimeSessionConfig, + _clientTools?: ReadonlyArray, ): Promise { if (connectionMode === 'webrtc') { - return createWebRTCConnection(token, config) + return createWebRTCConnection(token) } throw new Error('WebSocket connection mode not yet implemented') }, @@ -58,7 +59,6 @@ export function openaiRealtime( */ async function createWebRTCConnection( token: RealtimeToken, - config: RealtimeSessionConfig, ): Promise { const model = token.config.model ?? 'gpt-4o-realtime-preview' const eventHandlers = new Map>>() @@ -490,9 +490,9 @@ async function createWebRTCConnection( const imageContent = isUrl ? { type: 'input_image', image_url: imageData } : { - type: 'input_image', - image_url: `data:${mimeType};base64,${imageData}`, - } + type: 'input_image', + image_url: `data:${mimeType};base64,${imageData}`, + } sendEvent({ type: 'conversation.item.create', From 5bd8d46d6f7b91d83b0110ab30bb315d1bf3159d Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Wed, 15 Apr 2026 11:45:18 +0300 Subject: [PATCH 07/16] Bug fixes, added go_away event, more gemini live provider config options --- .../ts-react-chat/src/lib/use-realtime.ts | 8 +++- packages/typescript/ai-gemini/src/index.ts | 18 +++++--- .../ai-gemini/src/realtime/adapter.ts | 41 ++++++++++++++----- .../ai-gemini/src/realtime/index.ts | 1 + .../ai-gemini/src/realtime/token.ts | 2 +- .../ai-gemini/src/realtime/types.ts | 12 +++++- packages/typescript/ai/src/realtime/types.ts | 2 + 7 files changed, 64 insertions(+), 20 deletions(-) diff --git a/examples/ts-react-chat/src/lib/use-realtime.ts b/examples/ts-react-chat/src/lib/use-realtime.ts index 1970083c9..2ac96c6b5 100644 --- a/examples/ts-react-chat/src/lib/use-realtime.ts +++ b/examples/ts-react-chat/src/lib/use-realtime.ts @@ -7,12 +7,14 @@ import { elevenlabsRealtimeToken, } from '@tanstack/ai-elevenlabs' import { geminiRealtime, geminiRealtimeToken } from '@tanstack/ai-gemini' +import type { GeminiRealtimeVoice } from "@tanstack/ai-gemini" +import type { OpenAIRealtimeVoice } from "@tanstack/ai-openai" import { realtimeClientTools } from '@/lib/realtime-tools' type Provider = 'openai' | 'elevenlabs' | 'gemini' const getRealtimeTokenFn = createServerFn({ method: 'POST' }) - .inputValidator((data: { provider: Provider; agentId?: string }) => { + .inputValidator((data: { provider?: Provider; agentId?: string }) => { if (!data.provider) throw new Error('Provider is required') return data }) @@ -53,6 +55,7 @@ export function useRealtime({ temperature, maxOutputTokens, semanticEagerness, + voice }: { provider: Provider agentId: string @@ -60,6 +63,7 @@ export function useRealtime({ temperature?: number maxOutputTokens?: number | 'inf' semanticEagerness?: 'low' | 'medium' | 'high' + voice?: OpenAIRealtimeVoice | GeminiRealtimeVoice }) { const adapter = provider === 'openai' @@ -89,7 +93,7 @@ Keep your responses concise and conversational since this is a voice interface. When using tools, briefly explain what you're doing and then share the results naturally. If the user sends an image, describe what you see and answer any questions about it. Be friendly and engaging!`, - voice: 'alloy', + voice: voice || provider === 'gemini' ? 'Puck' : 'alloy', tools: realtimeClientTools, outputModalities, temperature, diff --git a/packages/typescript/ai-gemini/src/index.ts b/packages/typescript/ai-gemini/src/index.ts index 6181ba3c3..75ae77e54 100644 --- a/packages/typescript/ai-gemini/src/index.ts +++ b/packages/typescript/ai-gemini/src/index.ts @@ -83,8 +83,16 @@ export type { GeminiMessageMetadataByModality, } from './message-types' -// Realtime adapter -export { - geminiRealtime, - geminiRealtimeToken, -} from './realtime/index' +// ============================================================================ +// Realtime (Voice) Adapters +// ============================================================================ + +export { geminiRealtime, geminiRealtimeToken } from './realtime/index' + +export type { + GeminiRealtimeModel, + GeminiRealtimeOptions, + GeminiRealtimeTokenOptions, + GeminiRealtimeVoice, + GeminiRealtimeEvent +} from './realtime/index' \ No newline at end of file diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index 45b3270dd..eb9ac3032 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -1,4 +1,4 @@ -import { GoogleGenAI } from '@google/genai' +import { GoogleGenAI, Modality } from '@google/genai' import { convertSchemaToJsonSchema, } from "@tanstack/ai" @@ -11,9 +11,9 @@ import type { RealtimeSessionConfig, RealtimeToken } from '@tanstack/ai' -import type { LiveConnectConfig, Modality } from '@google/genai' +import type { LiveConnectConfig } from '@google/genai' import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' -import type { GeminiRealtimeOptions } from './types' +import type { GeminiRealtimeOptions, GeminiRealtimeProviderOptions } from './types' const textEncoder = new TextEncoder() const workletCode = ` @@ -91,8 +91,6 @@ async function createWebSocketConnection( const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' const eventHandlers = new Map>>() - const responseModalities = config.outputModalities?.map(modality => modality.toUpperCase()) as Array - const toolsConfig = tools ? tools.map((t) => ({ name: t.name, @@ -106,8 +104,18 @@ async function createWebSocketConnection( })) : undefined + const { + languageCode, + contextWindowCompression, + proactivity, + enableAffectiveDialog, + thinkingConfig + } = config.providerOptions as GeminiRealtimeProviderOptions + + config.outputModalities + const liveConfig: LiveConnectConfig = { - responseModalities, + responseModalities: [Modality.AUDIO], tools: toolsConfig ? [{ functionDeclarations: toolsConfig }] : undefined, @@ -116,14 +124,23 @@ async function createWebSocketConnection( prebuiltVoiceConfig: { voiceName: config.voice } - } + }, + languageCode }, maxOutputTokens: config.maxOutputTokens !== 'inf' ? config.maxOutputTokens : undefined, systemInstruction: config.instructions, temperature: config.temperature, - ...config.providerOptions + contextWindowCompression, + proactivity, + enableAffectiveDialog, + thinkingConfig, }; + if (config.outputModalities?.includes("text")) { + liveConfig.inputAudioTranscription = {} + liveConfig.outputAudioTranscription = {} + } + // Audio context let audioContext: AudioContext | null = null let inputAnalyser: AnalyserNode | null = null @@ -218,6 +235,10 @@ async function createWebSocketConnection( const inputTranscription = content?.inputTranscription; const outputTranscription = content?.outputTranscription; + if (response.goAway) { + emit("go_away", { timeLeft: response.goAway.timeLeft }) + } + if (response.data) { // TODO: Decode chunk and play using an `AudioWorklet` or // buffer them into an AudioContext @@ -479,9 +500,9 @@ async function createWebSocketConnection( sendImage(imageData: string, mimeType: string) { // Only accepts raw image data, not URLs session.sendRealtimeInput({ - media: { + video: { data: imageData, - mimeType: mimeType, + mimeType: mimeType } }) currentMode = 'thinking' diff --git a/packages/typescript/ai-gemini/src/realtime/index.ts b/packages/typescript/ai-gemini/src/realtime/index.ts index be743cb47..1291c083c 100644 --- a/packages/typescript/ai-gemini/src/realtime/index.ts +++ b/packages/typescript/ai-gemini/src/realtime/index.ts @@ -7,6 +7,7 @@ export { geminiRealtime } from './adapter' // Types export type { GeminiRealtimeModel, + GeminiRealtimeVoice, GeminiRealtimeTokenOptions, GeminiRealtimeOptions, } from './types' diff --git a/packages/typescript/ai-gemini/src/realtime/token.ts b/packages/typescript/ai-gemini/src/realtime/token.ts index edeb43f9c..5d6ac18f7 100644 --- a/packages/typescript/ai-gemini/src/realtime/token.ts +++ b/packages/typescript/ai-gemini/src/realtime/token.ts @@ -39,7 +39,7 @@ export function geminiRealtimeToken( provider: 'gemini', async generateToken(): Promise { const model: GeminiRealtimeModel = - options.model ?? 'gemini-live-2.5-flash-native-audio' + options.model ?? 'gemini-3.1-flash-live-preview' const token = await client.authTokens.create({ config: { diff --git a/packages/typescript/ai-gemini/src/realtime/types.ts b/packages/typescript/ai-gemini/src/realtime/types.ts index c9ea619db..cb89d29d1 100644 --- a/packages/typescript/ai-gemini/src/realtime/types.ts +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -1,3 +1,4 @@ +import type { ContextWindowCompressionConfig, ProactivityConfig, ThinkingConfig } from "@google/genai"; /** * Gemini realtime voice options @@ -60,6 +61,13 @@ export interface GeminiRealtimeTokenOptions { } /** - * Gemini Realtime session response from the API + * Gemini Realtime provider options */ -export interface GeminiRealtimeSessionResponse {} + +export interface GeminiRealtimeProviderOptions { + languageCode?: string + contextWindowCompression?: ContextWindowCompressionConfig + proactivity?: ProactivityConfig + enableAffectiveDialog?: boolean, + thinkingConfig?: ThinkingConfig +} \ No newline at end of file diff --git a/packages/typescript/ai/src/realtime/types.ts b/packages/typescript/ai/src/realtime/types.ts index 62c08b8f4..9310fb660 100644 --- a/packages/typescript/ai/src/realtime/types.ts +++ b/packages/typescript/ai/src/realtime/types.ts @@ -245,6 +245,7 @@ export type RealtimeEvent = | 'message_complete' | 'interrupted' | 'error' + | 'go_away' // Event that signals that the current connection will soon be terminated /** * Event payloads for realtime events @@ -262,6 +263,7 @@ export interface RealtimeEventPayloads { message_complete: { message: RealtimeMessage } interrupted: { messageId?: string } error: { error: Error } + go_away: { timeLeft: string } } /** From 9287a6265764a9db0e05bdc1bb2861a40513954b Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Fri, 17 Apr 2026 10:10:11 +0300 Subject: [PATCH 08/16] refactored event emitter and media handling in gemini adapter --- .../ai-elevenlabs/src/realtime/adapter.ts | 30 +- packages/typescript/ai-gemini/src/index.ts | 1 - .../ai-gemini/src/realtime/adapter.ts | 399 ++++-------------- .../ai-gemini/src/realtime/media-handler.ts | 344 +++++++++++++++ .../ai-openai/src/realtime/adapter.ts | 30 +- packages/typescript/ai/src/index.ts | 2 +- .../ai/src/realtime/event-emitter.ts | 32 ++ packages/typescript/ai/src/realtime/index.ts | 2 + packages/typescript/ai/src/realtime/types.ts | 8 +- 9 files changed, 465 insertions(+), 383 deletions(-) create mode 100644 packages/typescript/ai-gemini/src/realtime/media-handler.ts create mode 100644 packages/typescript/ai/src/realtime/event-emitter.ts diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index 4a341a4ad..490238067 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -1,4 +1,5 @@ import { Conversation } from '@11labs/client' +import { createRealtimeEventEmitter } from '@tanstack/ai' import type { AnyClientTool, AudioVisualization, @@ -55,7 +56,7 @@ async function createElevenLabsConnection( token: RealtimeToken, clientToolDefs?: ReadonlyArray, ): Promise { - const eventHandlers = new Map>>() + const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() let conversation: Awaited< ReturnType > | null = null @@ -65,19 +66,6 @@ async function createElevenLabsConnection( const emptyFrequencyData = new Uint8Array(128) const emptyTimeDomainData = new Uint8Array(128).fill(128) - // Helper to emit events - function emit( - event: TEvent, - payload: Parameters>[0], - ) { - const handlers = eventHandlers.get(event) - if (handlers) { - for (const handler of handlers) { - handler(payload) - } - } - } - function generateMessageId(): string { return `el-msg-${Date.now()}-${++messageIdCounter}` } @@ -223,19 +211,7 @@ async function createElevenLabsConnection( emit('interrupted', {}) }, - on( - event: TEvent, - handler: RealtimeEventHandler, - ): () => void { - if (!eventHandlers.has(event)) { - eventHandlers.set(event, new Set()) - } - eventHandlers.get(event)!.add(handler) - - return () => { - eventHandlers.get(event)?.delete(handler) - } - }, + on: realtimeEventEmitterOn, getAudioVisualization(): AudioVisualization { return { diff --git a/packages/typescript/ai-gemini/src/index.ts b/packages/typescript/ai-gemini/src/index.ts index 75ae77e54..92f9a0975 100644 --- a/packages/typescript/ai-gemini/src/index.ts +++ b/packages/typescript/ai-gemini/src/index.ts @@ -94,5 +94,4 @@ export type { GeminiRealtimeOptions, GeminiRealtimeTokenOptions, GeminiRealtimeVoice, - GeminiRealtimeEvent } from './realtime/index' \ No newline at end of file diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index eb9ac3032..b549458d0 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -1,52 +1,20 @@ import { GoogleGenAI, Modality } from '@google/genai' import { convertSchemaToJsonSchema, + createRealtimeEventEmitter, } from "@tanstack/ai" +import { MediaHandler } from './media-handler' import type { AudioVisualization, - RealtimeEvent, - RealtimeEventHandler, RealtimeMessage, RealtimeMode, RealtimeSessionConfig, - RealtimeToken + RealtimeToken, } from '@tanstack/ai' -import type { LiveConnectConfig } from '@google/genai' +import type { LiveConnectConfig, LiveServerSessionResumptionUpdate } from '@google/genai' import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { GeminiRealtimeOptions, GeminiRealtimeProviderOptions } from './types' -const textEncoder = new TextEncoder() -const workletCode = ` -class PCMProcessor extends AudioWorkletProcessor { - constructor() { - super(); - this.bufferSize = 4096; - this.buffer = new Float32Array(this.bufferSize); - this.bufferIndex = 0; - } - - process(inputs, outputs, parameters) { - const input = inputs[0]; - if (!input || !input.length) return true; - - const channelData = input[0]; - - for (let i = 0; i < channelData.length; i++) { - this.buffer[this.bufferIndex++] = channelData[i]; - - if (this.bufferIndex >= this.bufferSize) { - this.port.postMessage(this.buffer); - this.bufferIndex = 0; - } - } - - return true; - } -} - -registerProcessor("pcm-processor", PCMProcessor); -` - /** * Creates a Gemini realtime adapter for client-side use. * @@ -88,8 +56,10 @@ async function createWebSocketConnection( config: RealtimeSessionConfig, tools?: ReadonlyArray, ): Promise { + + const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() + const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' - const eventHandlers = new Map>>() const toolsConfig = tools ? tools.map((t) => ({ @@ -110,9 +80,7 @@ async function createWebSocketConnection( proactivity, enableAffectiveDialog, thinkingConfig - } = config.providerOptions as GeminiRealtimeProviderOptions - - config.outputModalities + } = (config.providerOptions ?? {}) as GeminiRealtimeProviderOptions const liveConfig: LiveConnectConfig = { responseModalities: [Modality.AUDIO], @@ -141,82 +109,23 @@ async function createWebSocketConnection( liveConfig.outputAudioTranscription = {} } - // Audio context - let audioContext: AudioContext | null = null - let inputAnalyser: AnalyserNode | null = null - let outputAnalyser: AnalyserNode | null = null - let inputSource: MediaStreamAudioSourceNode | null = null - let localStream: MediaStream | null = null - - // Audio element for playback (more reliable than AudioContext.destination) - let nextPlayTime = 0 - let scheduledSources: Array = [] - // let audioElement: HTMLAudioElement | null = null + const mediaHandler = new MediaHandler() // Current state let currentMode: RealtimeMode = 'idle' let currentMessageId: string | null = null let messageIdCounter = 0 - - // Empty arrays for when visualization isn't available - // frequencyBinCount = fftSize / 2 = 1024 - const emptyFrequencyData = new Uint8Array(1024) - const emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence - - // Helper to emit events (defined early so it can be used during setup) - function emit( - event: TEvent, - payload: Parameters>[0], - ) { - const handlers = eventHandlers.get(event) - if (handlers) { - for (const handler of handlers) { - handler(payload) - } - } - } + let sessionResumptionUpdate: LiveServerSessionResumptionUpdate | null = null function generateMessageId(): string { return `gemini-msg-${Date.now()}-${++messageIdCounter}` } - function downsampleBuffer(buffer: Float32Array, sampleRate: number, outSampleRate: number) { - if (outSampleRate === sampleRate) return buffer; - const ratio = sampleRate / outSampleRate; - const newLength = Math.round(buffer.length / ratio); - const result = new Float32Array(newLength); - let offsetResult = 0; - let offsetBuffer = 0; - while (offsetResult < result.length) { - const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio); - let accum = 0, - count = 0; - for ( - let i = offsetBuffer; - i < nextOffsetBuffer && i < buffer.length; - i++ - ) { - accum += buffer[i]!; - count++; - } - result[offsetResult] = accum / count; - offsetResult++; - offsetBuffer = nextOffsetBuffer; - } - return result; - } - - function convertFloat32ToInt16(buffer: Float32Array) { - let l = buffer.length; - const buf = new Int16Array(l); - while (l--) { - buf[l] = Math.min(1, Math.max(-1, buffer[l]!)) * 0x7fff; - } - return buf.toString(); - } - const ai = new GoogleGenAI({ - apiKey: token.token + apiKey: token.token, + httpOptions: { + apiVersion: 'v1alpha' + } }); const session = await ai.live.connect({ @@ -239,21 +148,26 @@ async function createWebSocketConnection( emit("go_away", { timeLeft: response.goAway.timeLeft }) } - if (response.data) { - // TODO: Decode chunk and play using an `AudioWorklet` or - // buffer them into an AudioContext + // TODO: implement session resumption + if (response.sessionResumptionUpdate) { + sessionResumptionUpdate = response.sessionResumptionUpdate + } - playIncomingAudioChunk(textEncoder.encode(response.data).buffer) + // TODO: Handle usage metadata + if (response.usageMetadata) { + } - if (currentMode !== 'speaking') { - currentMode = 'speaking' - emit('mode_change', { mode: 'speaking' }) - } + // Handle interruption by the model + if (response.serverContent?.interrupted) { + mediaHandler.stopAudioPlayback() + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + emit('interrupted', { messageId: currentMessageId ?? undefined }) } + // Handle input transcription if ( - inputTranscription && - inputTranscription.text != undefined && + inputTranscription?.text && inputTranscription.finished != undefined ) { if (inputTranscription.finished && currentMode !== 'thinking') { @@ -268,9 +182,9 @@ async function createWebSocketConnection( }) } + // Handle output transcription if ( - outputTranscription && - outputTranscription.text != undefined && + outputTranscription?.text && outputTranscription.finished != undefined ) { emit('transcript', { @@ -280,8 +194,8 @@ async function createWebSocketConnection( }) } + // Handle tool calls if (response.toolCall?.functionCalls) { - for (const fc of response.toolCall.functionCalls) { if (!fc.id || !fc.name) { continue; @@ -294,6 +208,22 @@ async function createWebSocketConnection( } } + // Play audio as it comes + if (response.serverContent?.modelTurn?.parts) { + for (const part of response.serverContent.modelTurn.parts) { + if (part.inlineData?.data) { + const audioData = mediaHandler.convertBase64ToArrayBuffer(part.inlineData.data); + mediaHandler.playAudio(audioData) + + if (currentMode !== 'speaking') { + currentMode = 'speaking' + emit('mode_change', { mode: 'speaking' }) + } + } + } + } + + // Handle turn complete if (response.serverContent?.turnComplete) { currentMode = 'listening' emit('mode_change', { mode: 'listening' }) @@ -307,18 +237,20 @@ async function createWebSocketConnection( parts: [] } - for (const item of response.serverContent.modelTurn.parts || []) { - if (item.text) { + for (const part of response.serverContent.modelTurn.parts || []) { + console.log(part) + + if (part.inlineData?.data && outputTranscription?.finished && outputTranscription.text) { message.parts.push({ - type: 'audio', - transcript: item.text + type: "audio", + transcript: outputTranscription.text, + audioData: mediaHandler.convertBase64ToArrayBuffer(part.inlineData.data), }) } } emit('message_complete', { message }) } - } }, onerror(event) { @@ -330,137 +262,20 @@ async function createWebSocketConnection( }); // Request microphone access - try { - localStream = await navigator.mediaDevices.getUserMedia({ + mediaHandler.startAudio((data) => { + session.sendRealtimeInput({ audio: { - echoCancellation: true, - noiseSuppression: true, - sampleRate: 24000, - }, - }) - } catch (error) { - throw new Error( - `Microphone access required for realtime voice: ${error instanceof Error ? error.message : error}`, - ) - } - - // Set up audio analysis now that we have the stream - await setupAudioAnalysis(localStream) - - // Set up audio analysis for input - async function setupAudioAnalysis(stream: MediaStream) { - if (!audioContext) { - // Best to specify Gemini's 16kHz here if possible for the whole context - audioContext = new AudioContext() - - const blob = new Blob([workletCode], { type: 'application/javascript' }) - const workletUrl = URL.createObjectURL(blob) - - await audioContext.audioWorklet.addModule(workletUrl) - } - - // Resume AudioContext if suspended (browsers require user interaction) - if (audioContext.state === 'suspended') { - audioContext.resume().catch(() => { - // Ignore - visualization just won't work - }) - } - - // 1. Setup Input (Microphone) Analyser - inputAnalyser = audioContext.createAnalyser() - inputAnalyser.fftSize = 2048 // Larger size for more accurate level detection - inputAnalyser.smoothingTimeConstant = 0.3 - - inputSource = audioContext.createMediaStreamSource(stream) - inputSource.connect(inputAnalyser) - - // 2. Setup Output (Gemini) Analyser - outputAnalyser = audioContext.createAnalyser() - outputAnalyser.fftSize = 2048 - outputAnalyser.smoothingTimeConstant = 0.3 - - // Connect output analyser directly to speakers - outputAnalyser.connect(audioContext.destination) - - const source = audioContext.createMediaStreamSource( - stream - ); - const audioWorkletNode = new AudioWorkletNode( - audioContext, - "pcm-processor" - ); - - audioWorkletNode.port.onmessage = (event) => { - if (currentMode === 'listening') { - const downsampled = downsampleBuffer( - event.data, - audioContext!.sampleRate, - 16000 - ); - const pcm16 = convertFloat32ToInt16(downsampled); - session.sendRealtimeInput({ - audio: { - data: pcm16, - mimeType: 'audio/pcm;rate=16000' - } - }) + data: Buffer.from(data).toString("base64"), + mimeType: 'audio/pcm;rate=16000' } - }; - - source.connect(audioWorkletNode); - } - - // Play incoming audio chunk from WebSocket connection - function playIncomingAudioChunk(arrayBuffer: ArrayBuffer) { - if (!audioContext) return - if (audioContext.state === 'suspended') { - audioContext.resume().catch(() => { - // Ignore - visualization just won't work - }) - } - - const pcmData = new Int16Array(arrayBuffer) - const float32Data = new Float32Array(pcmData.length) - for (let i = 0; i < pcmData.length; i++) { - float32Data[i] = pcmData[i]! / 32768.0; - } - - const buffer = audioContext.createBuffer(1, float32Data.length, 24000); - buffer.getChannelData(0).set(float32Data); - - const source = audioContext.createBufferSource(); - source.buffer = buffer; - if (outputAnalyser) { - source.connect(outputAnalyser) - } else { - source.connect(audioContext.destination); - } + }) + }) - const now = audioContext.currentTime; - nextPlayTime = Math.max(now, nextPlayTime); - source.start(nextPlayTime); - nextPlayTime += buffer.duration; - - scheduledSources.push(source); - source.onended = () => { - const idx = scheduledSources.indexOf(source); - if (idx > -1) scheduledSources.splice(idx, 1); - }; - } + await mediaHandler.setupInputAudioAnalysis() const connection: RealtimeConnection = { async disconnect() { - if (localStream) { - for (const track of localStream.getTracks()) { - track.stop() - } - localStream = null - } - - if (audioContext) { - await audioContext.close() - audioContext = null - } + mediaHandler.stopAudio() await session.close(); @@ -471,22 +286,14 @@ async function createWebSocketConnection( async startAudioCapture() { // Audio capture is established during connection setup // This method enables the tracks and signals listening mode - if (localStream) { - for (const track of localStream.getAudioTracks()) { - track.enabled = true - } - } + mediaHandler.startAudioCapture() currentMode = 'listening' emit('mode_change', { mode: 'listening' }) }, stopAudioCapture() { // Disable tracks rather than stopping them to allow re-enabling - if (localStream) { - for (const track of localStream.getAudioTracks()) { - track.enabled = false - } - } + mediaHandler.stopAudioCapture() currentMode = 'idle' emit('mode_change', { mode: 'idle' }) }, @@ -526,96 +333,36 @@ async function createWebSocketConnection( }, interrupt() { - scheduledSources.forEach((s) => { - try { - s.stop(); - } catch (e) { } - }); - scheduledSources = []; - if (audioContext) { - nextPlayTime = audioContext.currentTime; - } - + mediaHandler.stopAudioPlayback() currentMode = 'listening' emit('mode_change', { mode: 'listening' }) emit('interrupted', { messageId: currentMessageId ?? undefined }) }, - - on( - event: TEvent, - handler: RealtimeEventHandler - ): () => void { - if (!eventHandlers.has(event)) { - eventHandlers.set(event, new Set()); - } - eventHandlers.get(event)!.add(handler) - - return () => { - eventHandlers.get(event)!.delete(handler) - } - }, - + on: realtimeEventEmitterOn, getAudioVisualization(): AudioVisualization { - // Helper to calculate audio level from time domain data - // Uses peak amplitude which is more responsive for voice audio meters - function calculateLevel(analyser: AnalyserNode): number { - const data = new Uint8Array(analyser.fftSize) - analyser.getByteTimeDomainData(data) - - // Find peak deviation from center (128 is silence) - // This is more responsive than RMS for voice level meters - let maxDeviation = 0 - for (const sample of data) { - const deviation = Math.abs(sample - 128) - if (deviation > maxDeviation) { - maxDeviation = deviation - } - } - - // Normalize to 0-1 range (max deviation is 128) - // Scale by 1.5x so that ~66% amplitude reads as full scale - // This provides good visual feedback without pegging too early - const normalized = maxDeviation / 128 - return Math.min(1, normalized * 1.5) - } - return { get inputLevel() { - if (!inputAnalyser) return 0 - return calculateLevel(inputAnalyser) + return mediaHandler.inputLevel }, get outputLevel() { - if (!outputAnalyser) return 0 - return calculateLevel(outputAnalyser) + return mediaHandler.outputLevel }, getInputFrequencyData() { - if (!inputAnalyser) return emptyFrequencyData - const data = new Uint8Array(inputAnalyser.frequencyBinCount) - inputAnalyser.getByteFrequencyData(data) - return data + return mediaHandler.inputFrequencyData }, getOutputFrequencyData() { - if (!outputAnalyser) return emptyFrequencyData - const data = new Uint8Array(outputAnalyser.frequencyBinCount) - outputAnalyser.getByteFrequencyData(data) - return data + return mediaHandler.outputFrequencyData }, getInputTimeDomainData() { - if (!inputAnalyser) return emptyTimeDomainData - const data = new Uint8Array(inputAnalyser.fftSize) - inputAnalyser.getByteTimeDomainData(data) - return data + return mediaHandler.inputTimeDomainData }, getOutputTimeDomainData() { - if (!outputAnalyser) return emptyTimeDomainData - const data = new Uint8Array(outputAnalyser.fftSize) - outputAnalyser.getByteTimeDomainData(data) - return data + return mediaHandler.outputTimeDomainData }, get inputSampleRate() { diff --git a/packages/typescript/ai-gemini/src/realtime/media-handler.ts b/packages/typescript/ai-gemini/src/realtime/media-handler.ts new file mode 100644 index 000000000..994dc1f72 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/media-handler.ts @@ -0,0 +1,344 @@ +const workletCode = ` +class PCMProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.bufferSize = 4096; + this.buffer = new Float32Array(this.bufferSize); + this.bufferIndex = 0; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + if (!input || !input.length) return true; + + const channelData = input[0]; + + for (let i = 0; i < channelData.length; i++) { + this.buffer[this.bufferIndex++] = channelData[i]; + + if (this.bufferIndex >= this.bufferSize) { + this.port.postMessage(this.buffer); + this.bufferIndex = 0; + } + } + + return true; + } +} + +registerProcessor("pcm-processor", PCMProcessor); +` +const workletBlob = new Blob([workletCode], { type: 'application/javascript' }) +const workletUrl = URL.createObjectURL(workletBlob) + +export class MediaHandler { + + private audioContext: AudioContext | null = null + private mediaStream: MediaStream | null = null + + private nextStartTime = 0 + private scheduledSources: Array = [] + + // Analyzers + private inputAnalyser: AnalyserNode | null = null + private outputAnalyser: AnalyserNode | null = null + private inputSource: MediaStreamAudioSourceNode | null = null + private outputGainNode: GainNode | null = null + + private audioWorkletNode: AudioWorkletNode | null = null + + // Empty arrays for when visualization isn't available + // frequencyBinCount = fftSize / 2 = 1024 + private emptyFrequencyData = new Uint8Array(1024) + private emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence + + public isRecording = false + + async initializeAudio() { + if (!this.audioContext) { + this.audioContext = new AudioContext() + await this.audioContext.audioWorklet.addModule(workletUrl) + } + + if (this.audioContext.state === 'suspended') { + await this.audioContext.resume().catch(() => {}) + } + } + + async setupInputAudioAnalysis() { + await this.initializeAudio() + if (!this.audioContext || !this.mediaStream) return + + this.inputAnalyser = this.audioContext.createAnalyser() + this.inputAnalyser.fftSize = 2048 // Larger size for more accurate level detection + this.inputAnalyser.smoothingTimeConstant = 0.3 + + this.inputSource = this.audioContext.createMediaStreamSource(this.mediaStream) + this.inputSource.connect(this.inputAnalyser) + } + + async setupOutputAudioAnalysis() { + await this.initializeAudio() + if (!this.audioContext) return + + this.outputAnalyser = this.audioContext.createAnalyser() + this.outputAnalyser.fftSize = 2048 + this.outputAnalyser.smoothingTimeConstant = 0.3 + + // Create a gain node as a routing hub for all playback sources + this.outputGainNode = this.audioContext.createGain() + this.outputGainNode.gain.value = 1 + + // Route: outputGainNode → outputAnalyser → destination + this.outputGainNode.connect(this.outputAnalyser) + this.outputAnalyser.connect(this.audioContext.destination) + } + + async startAudio(onAudioData: (audioData: ArrayBuffer) => void) { + await this.initializeAudio() + if (!this.audioContext) return + + try { + this.mediaStream = await navigator.mediaDevices.getUserMedia({ + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 24000, + }, + }) + + const source = this.audioContext.createMediaStreamSource( + this.mediaStream + ); + + this.audioWorkletNode = new AudioWorkletNode( + this.audioContext, + "pcm-processor" + ); + + this.audioWorkletNode.port.onmessage = (event) => { + if (this.isRecording) { + const downsampled = this.downsampleBuffer( + event.data as Float32Array, + this.audioContext!.sampleRate, + 16000 + ); + const pcm16 = this.convertFloat32ToInt16(downsampled); + onAudioData(pcm16); + } + }; + + source.connect(this.audioWorkletNode); + + // Mute local feedback + const muteGain = this.audioContext.createGain(); + muteGain.gain.value = 0; + this.audioWorkletNode.connect(muteGain); + muteGain.connect(this.audioContext.destination); + + this.isRecording = true + } catch (error) { + throw new Error( + `Error starting audio: ${error instanceof Error ? error.message : error}`, + ) + } + } + + stopAudio() { + this.isRecording = false + if (this.mediaStream) { + this.mediaStream.getTracks().forEach((t) => t.stop()); + this.mediaStream = null; + } + if (this.audioWorkletNode) { + this.audioWorkletNode.disconnect(); + this.audioWorkletNode = null; + } + } + + startAudioCapture() { + if (this.mediaStream) { + for (const track of this.mediaStream.getAudioTracks()) { + track.enabled = true + } + } + this.isRecording = true + } + + stopAudioCapture() { + if (this.mediaStream) { + for (const track of this.mediaStream.getAudioTracks()) { + track.enabled = false + } + } + this.isRecording = false + } + + playAudio(arrayBuffer: ArrayBuffer) { + if (!this.audioContext) return; + if (this.audioContext.state === "suspended") { + this.audioContext.resume(); + } + + const pcmData = new Int16Array(arrayBuffer); + const float32Data = new Float32Array(pcmData.length); + for (let i = 0; i < pcmData.length; i++) { + float32Data[i] = pcmData[i]! / 32768.0; + } + + const buffer = this.audioContext.createBuffer(1, float32Data.length, 24000); + buffer.getChannelData(0).set(float32Data); + + const source = this.audioContext.createBufferSource(); + source.buffer = buffer; + + // Route through output analyser if available, otherwise direct to destination + const outputTarget = this.outputGainNode ?? this.audioContext.destination; + source.connect(outputTarget); + + const now = this.audioContext.currentTime; + this.nextStartTime = Math.max(now, this.nextStartTime); + source.start(this.nextStartTime); + this.nextStartTime += buffer.duration; + + this.scheduledSources.push(source); + source.onended = () => { + const idx = this.scheduledSources.indexOf(source); + if (idx > -1) this.scheduledSources.splice(idx, 1); + }; + } + + stopAudioPlayback() { + this.scheduledSources.forEach((s) => { + try { + s.stop(); + } catch (e) { } + }); + this.scheduledSources = []; + if (this.audioContext) { + this.nextStartTime = this.audioContext.currentTime; + } + } + + /** + * Returns the current input (microphone) audio level as a normalized value [0, 1]. + */ + getInputLevel(): number { + if (!this.inputAnalyser) return 0 + + const data = new Uint8Array(this.inputAnalyser.frequencyBinCount) + this.inputAnalyser.getByteFrequencyData(data) + + let sum = 0 + for (const i of data) { + sum += i + } + return sum / (data.length * 255) + } + + // Helper to calculate audio level from time domain data + // Uses peak amplitude which is more responsive for voice audio meters + calculateLevel(analyser: AnalyserNode): number { + const data = new Uint8Array(analyser.fftSize) + analyser.getByteTimeDomainData(data) + + // Find peak deviation from center (128 is silence) + // This is more responsive than RMS for voice level meters + let maxDeviation = 0 + for (const sample of data) { + const deviation = Math.abs(sample - 128) + if (deviation > maxDeviation) { + maxDeviation = deviation + } + } + + // Normalize to 0-1 range (max deviation is 128) + // Scale by 1.5x so that ~66% amplitude reads as full scale + // This provides good visual feedback without pegging too early + const normalized = maxDeviation / 128 + return Math.min(1, normalized * 1.5) + } + + get inputLevel() { + if (!this.inputAnalyser) return 0 + return this.calculateLevel(this.inputAnalyser) + } + + get outputLevel() { + if (!this.outputAnalyser) return 0 + return this.calculateLevel(this.outputAnalyser) + } + + get inputFrequencyData() { + if (!this.inputAnalyser) return this.emptyFrequencyData + const data = new Uint8Array(this.inputAnalyser.frequencyBinCount) + this.inputAnalyser.getByteFrequencyData(data) + return data + } + + get outputFrequencyData() { + if (!this.outputAnalyser) return this.emptyFrequencyData + const data = new Uint8Array(this.outputAnalyser.frequencyBinCount) + this.outputAnalyser.getByteFrequencyData(data) + return data + } + + get inputTimeDomainData() { + if (!this.inputAnalyser) return this.emptyTimeDomainData + const data = new Uint8Array(this.inputAnalyser.fftSize) + this.inputAnalyser.getByteTimeDomainData(data) + return data + } + + get outputTimeDomainData() { + if (!this.outputAnalyser) return this.emptyTimeDomainData + const data = new Uint8Array(this.outputAnalyser.fftSize) + this.outputAnalyser.getByteTimeDomainData(data) + return data + } + + // Utils + downsampleBuffer(buffer: Float32Array, sampleRate: number, outSampleRate: number) { + if (outSampleRate === sampleRate) return buffer; + const ratio = sampleRate / outSampleRate; + const newLength = Math.round(buffer.length / ratio); + const result = new Float32Array(newLength); + let offsetResult = 0; + let offsetBuffer = 0; + while (offsetResult < result.length) { + const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio); + let accum = 0, + count = 0; + for ( + let i = offsetBuffer; + i < nextOffsetBuffer && i < buffer.length; + i++ + ) { + accum += buffer[i]!; + count++; + } + result[offsetResult] = accum / count; + offsetResult++; + offsetBuffer = nextOffsetBuffer; + } + return result; + } + + convertFloat32ToInt16(buffer: Float32Array): ArrayBuffer { + let l = buffer.length; + const buf = new Int16Array(l); + while (l--) { + buf[l] = Math.min(1, Math.max(-1, buffer[l]!)) * 0x7fff; + } + return buf.buffer; + } + + convertBase64ToArrayBuffer(base64: string): ArrayBuffer { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; + } +} \ No newline at end of file diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index d10128e70..29b1ad53d 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -1,3 +1,4 @@ +import { createRealtimeEventEmitter } from '@tanstack/ai' import type { AnyClientTool, AudioVisualization, @@ -61,7 +62,7 @@ async function createWebRTCConnection( token: RealtimeToken, ): Promise { const model = token.config.model ?? 'gpt-4o-realtime-preview' - const eventHandlers = new Map>>() + const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() // WebRTC peer connection const pc = new RTCPeerConnection() @@ -89,19 +90,6 @@ async function createWebRTCConnection( const emptyFrequencyData = new Uint8Array(1024) const emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence - // Helper to emit events (defined early so it can be used during setup) - function emit( - event: TEvent, - payload: Parameters>[0], - ) { - const handlers = eventHandlers.get(event) - if (handlers) { - for (const handler of handlers) { - handler(payload) - } - } - } - // Set up data channel for bidirectional communication dataChannel = pc.createDataChannel('oai-events') @@ -588,19 +576,7 @@ async function createWebRTCConnection( emit('interrupted', { messageId: currentMessageId ?? undefined }) }, - on( - event: TEvent, - handler: RealtimeEventHandler, - ): () => void { - if (!eventHandlers.has(event)) { - eventHandlers.set(event, new Set()) - } - eventHandlers.get(event)!.add(handler) - - return () => { - eventHandlers.get(event)?.delete(handler) - } - }, + on: realtimeEventEmitterOn, getAudioVisualization(): AudioVisualization { // Helper to calculate audio level from time domain data diff --git a/packages/typescript/ai/src/index.ts b/packages/typescript/ai/src/index.ts index 0d1f7aeef..5b0b861dd 100644 --- a/packages/typescript/ai/src/index.ts +++ b/packages/typescript/ai/src/index.ts @@ -94,7 +94,7 @@ export * from './types' export { detectImageMimeType } from './utils' // Realtime -export { realtimeToken } from './realtime/index' +export { realtimeToken, createRealtimeEventEmitter } from './realtime/index' export type { RealtimeToken, RealtimeTokenAdapter, diff --git a/packages/typescript/ai/src/realtime/event-emitter.ts b/packages/typescript/ai/src/realtime/event-emitter.ts new file mode 100644 index 000000000..922cfb1e3 --- /dev/null +++ b/packages/typescript/ai/src/realtime/event-emitter.ts @@ -0,0 +1,32 @@ +import type { RealtimeEvent, RealtimeEventHandler } from "./types"; + +export function createRealtimeEventEmitter() { + const eventHandlers = new Map>>() + + return { + emit( + event: TEvent, + payload: Parameters>[0], + ) { + const handlers = eventHandlers.get(event) + if (handlers) { + for (const handler of handlers) { + handler(payload) + } + } + }, + on( + event: TEvent, + handler: RealtimeEventHandler, + ): () => void { + if (!eventHandlers.has(event)) { + eventHandlers.set(event, new Set()) + } + eventHandlers.get(event)!.add(handler) + + return () => { + eventHandlers.get(event)!.delete(handler) + } + } + } +} \ No newline at end of file diff --git a/packages/typescript/ai/src/realtime/index.ts b/packages/typescript/ai/src/realtime/index.ts index 74c450c1d..ea5ab41d1 100644 --- a/packages/typescript/ai/src/realtime/index.ts +++ b/packages/typescript/ai/src/realtime/index.ts @@ -1,5 +1,7 @@ import type { RealtimeToken, RealtimeTokenOptions } from './types' +export { createRealtimeEventEmitter } from './event-emitter' + // Re-export all types export * from './types' diff --git a/packages/typescript/ai/src/realtime/types.ts b/packages/typescript/ai/src/realtime/types.ts index 9310fb660..7934460d4 100644 --- a/packages/typescript/ai/src/realtime/types.ts +++ b/packages/typescript/ai/src/realtime/types.ts @@ -246,6 +246,7 @@ export type RealtimeEvent = | 'interrupted' | 'error' | 'go_away' // Event that signals that the current connection will soon be terminated + | 'usage' /** * Event payloads for realtime events @@ -263,7 +264,12 @@ export interface RealtimeEventPayloads { message_complete: { message: RealtimeMessage } interrupted: { messageId?: string } error: { error: Error } - go_away: { timeLeft: string } + go_away: { timeLeft?: string } + usage: { + promptTokens: number + completionTokens: number + totalTokens: number + } } /** From 97500d30c7781a429e36c4c1a30d0d0c1adba852 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Fri, 17 Apr 2026 10:54:21 +0300 Subject: [PATCH 09/16] added go_away & usage event handlers in ai-client --- packages/typescript/ai-client/src/realtime-client.ts | 12 ++++++++++++ packages/typescript/ai-client/src/realtime-types.ts | 3 +++ .../typescript/ai-gemini/src/realtime/adapter.ts | 10 ++++++++-- packages/typescript/ai/src/realtime/types.ts | 8 +++----- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index 861c88e52..78beff8ff 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -494,6 +494,18 @@ export class RealtimeClient { this.options.onError?.(error) }), ) + + this.unsubscribers.push( + this.connection.on('go_away', ({ timeLeft }) => { + this.options.onGoAway?.(timeLeft) + }), + ) + + this.unsubscribers.push( + this.connection.on('usage', (usage) => { + this.options.onUsage?.(usage) + }), + ) } private applySessionConfig(): void { diff --git a/packages/typescript/ai-client/src/realtime-types.ts b/packages/typescript/ai-client/src/realtime-types.ts index c33dd8b14..9955b1789 100644 --- a/packages/typescript/ai-client/src/realtime-types.ts +++ b/packages/typescript/ai-client/src/realtime-types.ts @@ -8,6 +8,7 @@ import type { RealtimeSessionConfig, RealtimeStatus, RealtimeToken, + UsageInfo, } from '@tanstack/ai' // ============================================================================ @@ -162,6 +163,8 @@ export interface RealtimeClientOptions { onConnect?: () => void onDisconnect?: () => void onInterrupted?: () => void + onUsage?: (usage: UsageInfo) => void + onGoAway?: (timeLeft?: string) => void } // ============================================================================ diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index b549458d0..f5831fb3b 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -153,8 +153,14 @@ async function createWebSocketConnection( sessionResumptionUpdate = response.sessionResumptionUpdate } - // TODO: Handle usage metadata - if (response.usageMetadata) { + // Handle token usage + const { totalTokenCount, promptTokenCount, responseTokenCount, responseTokensDetails } = response.usageMetadata ?? {} + if (totalTokenCount && promptTokenCount && responseTokenCount) { + emit("usage", { + completionTokens: responseTokenCount, + promptTokens: promptTokenCount, + totalTokens: totalTokenCount, + }) } // Handle interruption by the model diff --git a/packages/typescript/ai/src/realtime/types.ts b/packages/typescript/ai/src/realtime/types.ts index 7934460d4..f0dcbf9d8 100644 --- a/packages/typescript/ai/src/realtime/types.ts +++ b/packages/typescript/ai/src/realtime/types.ts @@ -2,6 +2,8 @@ // Token Types // ============================================================================ +import type { UsageInfo } from "../activities/chat/middleware" + /** * Voice activity detection configuration */ @@ -265,11 +267,7 @@ export interface RealtimeEventPayloads { interrupted: { messageId?: string } error: { error: Error } go_away: { timeLeft?: string } - usage: { - promptTokens: number - completionTokens: number - totalTokens: number - } + usage: UsageInfo } /** From b796ebcecd8f947f2358c29e3c572ead8eab7d90 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Fri, 17 Apr 2026 12:45:23 +0300 Subject: [PATCH 10/16] bug fixes --- .../ai-gemini/src/realtime/adapter.ts | 20 ++++++------- .../ai-gemini/src/realtime/media-handler.ts | 29 ++++++++----------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index f5831fb3b..36291eb82 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -154,12 +154,11 @@ async function createWebSocketConnection( } // Handle token usage - const { totalTokenCount, promptTokenCount, responseTokenCount, responseTokensDetails } = response.usageMetadata ?? {} - if (totalTokenCount && promptTokenCount && responseTokenCount) { + if (response.usageMetadata) { emit("usage", { - completionTokens: responseTokenCount, - promptTokens: promptTokenCount, - totalTokens: totalTokenCount, + completionTokens: response.usageMetadata.responseTokenCount ?? 0, + promptTokens: response.usageMetadata.promptTokenCount ?? 0, + totalTokens: response.usageMetadata.totalTokenCount ?? 0, }) } @@ -234,7 +233,7 @@ async function createWebSocketConnection( currentMode = 'listening' emit('mode_change', { mode: 'listening' }) - if (response.serverContent.modelTurn?.role == 'model') { + if (response.serverContent.modelTurn?.role === 'model') { currentMessageId = generateMessageId() const message: RealtimeMessage = { id: currentMessageId, @@ -268,16 +267,17 @@ async function createWebSocketConnection( }); // Request microphone access - mediaHandler.startAudio((data) => { + await mediaHandler.startAudio((data) => { session.sendRealtimeInput({ audio: { - data: Buffer.from(data).toString("base64"), + data: mediaHandler.convertArrayBufferToBase64(data), mimeType: 'audio/pcm;rate=16000' } }) }) await mediaHandler.setupInputAudioAnalysis() + await mediaHandler.setupOutputAudioAnalysis() const connection: RealtimeConnection = { async disconnect() { @@ -324,12 +324,12 @@ async function createWebSocketConnection( sendToolResult(callId: string, result: string) { session.sendToolResponse({ - functionResponses: { + functionResponses: [{ id: callId, response: { result } - } + }] }) }, diff --git a/packages/typescript/ai-gemini/src/realtime/media-handler.ts b/packages/typescript/ai-gemini/src/realtime/media-handler.ts index 994dc1f72..67ea744d9 100644 --- a/packages/typescript/ai-gemini/src/realtime/media-handler.ts +++ b/packages/typescript/ai-gemini/src/realtime/media-handler.ts @@ -28,8 +28,6 @@ class PCMProcessor extends AudioWorkletProcessor { registerProcessor("pcm-processor", PCMProcessor); ` -const workletBlob = new Blob([workletCode], { type: 'application/javascript' }) -const workletUrl = URL.createObjectURL(workletBlob) export class MediaHandler { @@ -54,10 +52,17 @@ export class MediaHandler { public isRecording = false + private workletUrl: string | null = null + + constructor() { + const workletBlob = new Blob([workletCode], { type: 'application/javascript' }) + this.workletUrl = URL.createObjectURL(workletBlob) + } + async initializeAudio() { if (!this.audioContext) { this.audioContext = new AudioContext() - await this.audioContext.audioWorklet.addModule(workletUrl) + await this.audioContext.audioWorklet.addModule(this.workletUrl!) } if (this.audioContext.state === 'suspended') { @@ -220,21 +225,7 @@ export class MediaHandler { } } - /** - * Returns the current input (microphone) audio level as a normalized value [0, 1]. - */ - getInputLevel(): number { - if (!this.inputAnalyser) return 0 - - const data = new Uint8Array(this.inputAnalyser.frequencyBinCount) - this.inputAnalyser.getByteFrequencyData(data) - let sum = 0 - for (const i of data) { - sum += i - } - return sum / (data.length * 255) - } // Helper to calculate audio level from time domain data // Uses peak amplitude which is more responsive for voice audio meters @@ -341,4 +332,8 @@ export class MediaHandler { } return bytes.buffer; } + + convertArrayBufferToBase64(arrayBuffer: ArrayBuffer): string { + return btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) + } } \ No newline at end of file From 18f247943b6a57ebb3beb4e56ea7877b197068ce Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sun, 19 Apr 2026 12:47:16 +0300 Subject: [PATCH 11/16] refactor: Gemini realtime adapter, Gemini realtime client and audio capture & playback, add support for dynamic token updates and session resumption --- .../ai-client/src/realtime-client.ts | 1 + .../ai-client/src/realtime-types.ts | 2 + .../ai-elevenlabs/src/realtime/adapter.ts | 2 - .../ai-gemini/src/realtime/adapter.ts | 356 +++++--------- .../ai-gemini/src/realtime/client.ts | 455 ++++++++++++++++++ .../ai-gemini/src/realtime/media-handler.ts | 339 ------------- .../ai-gemini/src/realtime/utils.ts | 450 +++++++++++++++++ .../ai-openai/src/realtime/adapter.ts | 2 - 8 files changed, 1032 insertions(+), 575 deletions(-) create mode 100644 packages/typescript/ai-gemini/src/realtime/client.ts delete mode 100644 packages/typescript/ai-gemini/src/realtime/media-handler.ts create mode 100644 packages/typescript/ai-gemini/src/realtime/utils.ts diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index 78beff8ff..f2b468c7c 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -374,6 +374,7 @@ export class RealtimeClient { try { this.token = await this.options.getToken() this.scheduleTokenRefresh() + this.connection?.updateToken?.(this.token) // Note: Some providers may require reconnection with new token // This is handled by the adapter implementation } catch (error) { diff --git a/packages/typescript/ai-client/src/realtime-types.ts b/packages/typescript/ai-client/src/realtime-types.ts index 9955b1789..fde8f188d 100644 --- a/packages/typescript/ai-client/src/realtime-types.ts +++ b/packages/typescript/ai-client/src/realtime-types.ts @@ -66,6 +66,8 @@ export interface RealtimeConnection { // Session management /** Update session configuration */ updateSession: (config: Partial) => void + /** Update token */ + updateToken?: (token: RealtimeToken) => void /** Interrupt the current response */ interrupt: () => void diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index 490238067..cac5061e8 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -3,8 +3,6 @@ import { createRealtimeEventEmitter } from '@tanstack/ai' import type { AnyClientTool, AudioVisualization, - RealtimeEvent, - RealtimeEventHandler, RealtimeMessage, RealtimeMode, RealtimeSessionConfig, diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index 36291eb82..25df26b94 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -1,9 +1,9 @@ -import { GoogleGenAI, Modality } from '@google/genai' import { - convertSchemaToJsonSchema, createRealtimeEventEmitter, } from "@tanstack/ai" -import { MediaHandler } from './media-handler' +import { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils' +import { GeminiLiveClient } from './client' +import type { LiveResponse } from './client' import type { AudioVisualization, RealtimeMessage, @@ -11,9 +11,8 @@ import type { RealtimeSessionConfig, RealtimeToken, } from '@tanstack/ai' -import type { LiveConnectConfig, LiveServerSessionResumptionUpdate } from '@google/genai' import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' -import type { GeminiRealtimeOptions, GeminiRealtimeProviderOptions } from './types' +import type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types' /** * Creates a Gemini realtime adapter for client-side use. @@ -59,287 +58,180 @@ async function createWebSocketConnection( const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() - const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio' - - const toolsConfig = tools - ? tools.map((t) => ({ - name: t.name, - description: t.description, - inputSchema: t.inputSchema - ? convertSchemaToJsonSchema(t.inputSchema) - : undefined, - outputSchema: t.outputSchema - ? convertSchemaToJsonSchema(t.outputSchema) - : undefined, - })) - : undefined - - const { - languageCode, - contextWindowCompression, - proactivity, - enableAffectiveDialog, - thinkingConfig - } = (config.providerOptions ?? {}) as GeminiRealtimeProviderOptions - - const liveConfig: LiveConnectConfig = { - responseModalities: [Modality.AUDIO], - tools: toolsConfig ? [{ - functionDeclarations: toolsConfig - }] : undefined, - speechConfig: { - voiceConfig: { - prebuiltVoiceConfig: { - voiceName: config.voice - } - }, - languageCode - }, - maxOutputTokens: config.maxOutputTokens !== 'inf' ? config.maxOutputTokens : undefined, - systemInstruction: config.instructions, - temperature: config.temperature, - contextWindowCompression, - proactivity, - enableAffectiveDialog, - thinkingConfig, - }; - - if (config.outputModalities?.includes("text")) { - liveConfig.inputAudioTranscription = {} - liveConfig.outputAudioTranscription = {} - } - - const mediaHandler = new MediaHandler() + const model = (token.config.model ?? 'gemini-3.1-flash-live-preview') as GeminiRealtimeModel // Current state let currentMode: RealtimeMode = 'idle' let currentMessageId: string | null = null let messageIdCounter = 0 - let sessionResumptionUpdate: LiveServerSessionResumptionUpdate | null = null function generateMessageId(): string { return `gemini-msg-${Date.now()}-${++messageIdCounter}` } - const ai = new GoogleGenAI({ - apiKey: token.token, - httpOptions: { - apiVersion: 'v1alpha' - } - }); - - const session = await ai.live.connect({ - model: model, - config: liveConfig, - callbacks: { - onopen() { - emit("status_change", { status: "connected" }) - }, - onclose() { - emit("status_change", { status: "idle" }) - }, - onmessage(response) { - - const content = response.serverContent; - const inputTranscription = content?.inputTranscription; - const outputTranscription = content?.outputTranscription; + const client = new GeminiLiveClient(token.token, model, tools) + const audioStreamer = new AudioStreamer(client) + const audioPlayer = new AudioPlayer() + await audioPlayer.init() - if (response.goAway) { - emit("go_away", { timeLeft: response.goAway.timeLeft }) - } - - // TODO: implement session resumption - if (response.sessionResumptionUpdate) { - sessionResumptionUpdate = response.sessionResumptionUpdate - } + client.connect() - // Handle token usage - if (response.usageMetadata) { - emit("usage", { - completionTokens: response.usageMetadata.responseTokenCount ?? 0, - promptTokens: response.usageMetadata.promptTokenCount ?? 0, - totalTokens: response.usageMetadata.totalTokenCount ?? 0, - }) - } - - // Handle interruption by the model - if (response.serverContent?.interrupted) { - mediaHandler.stopAudioPlayback() - currentMode = 'listening' - emit('mode_change', { mode: 'listening' }) - emit('interrupted', { messageId: currentMessageId ?? undefined }) - } + audioStreamer.start() - // Handle input transcription - if ( - inputTranscription?.text && - inputTranscription.finished != undefined - ) { - if (inputTranscription.finished && currentMode !== 'thinking') { - currentMode = 'thinking' - emit('mode_change', { mode: 'thinking' }) - } + let message: RealtimeMessage = { + id: '', + role: 'assistant', + timestamp: 0, + parts: [] + } - emit('transcript', { - isFinal: inputTranscription.finished, - transcript: inputTranscription.text, - role: 'user', - }) + client.onReceiveResponse = (response: LiveResponse) => { + switch (response.type) { + case 'text': + message.parts.push({ + type: 'text', + content: response.data, + }) + break; + case 'audio': + message.parts.push({ + type: 'audio', + transcript: response.data.transcript, + audioData: base64ToArrayBuffer(response.data.audioData) + }) + if (currentMode !== 'speaking') { + currentMode = 'speaking' + emit('mode_change', { mode: 'speaking' }) } - - // Handle output transcription - if ( - outputTranscription?.text && - outputTranscription.finished != undefined - ) { - emit('transcript', { - isFinal: outputTranscription.finished, - transcript: outputTranscription.text, - role: 'assistant', - }) + audioPlayer.play(response.data.audioData) + break; + case 'go_away': + emit("go_away", { timeLeft: response.data.timeLeft }) + break; + case 'usage_metadata': + emit("usage", { + completionTokens: response.data.responseTokenCount ?? 0, + promptTokens: response.data.promptTokenCount ?? 0, + totalTokens: response.data.totalTokenCount ?? 0, + }) + break; + case 'input_transcription': + if (response.data.finished && currentMode !== 'thinking') { + currentMode = 'thinking' + emit('mode_change', { mode: 'thinking' }) } - - // Handle tool calls - if (response.toolCall?.functionCalls) { - for (const fc of response.toolCall.functionCalls) { - if (!fc.id || !fc.name) { - continue; - } + emit('transcript', { + isFinal: response.data.finished, + transcript: response.data.text, + role: 'user', + }) + break; + case 'output_transcription': + emit('transcript', { + isFinal: response.data.finished, + transcript: response.data.text, + role: 'assistant', + }) + break; + case 'interrupted': + audioPlayer.interrupt() + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + emit('interrupted', { messageId: currentMessageId ?? undefined }) + break; + case 'tool_call': + for (const tool of response.data) { + if (tool.id && tool.name) { emit('tool_call', { - toolCallId: fc.id, - input: fc.args, - toolName: fc.name + toolCallId: tool.id, + input: tool.args, + toolName: tool.name }) } } - - // Play audio as it comes - if (response.serverContent?.modelTurn?.parts) { - for (const part of response.serverContent.modelTurn.parts) { - if (part.inlineData?.data) { - const audioData = mediaHandler.convertBase64ToArrayBuffer(part.inlineData.data); - mediaHandler.playAudio(audioData) - - if (currentMode !== 'speaking') { - currentMode = 'speaking' - emit('mode_change', { mode: 'speaking' }) - } - } - } - } - - // Handle turn complete - if (response.serverContent?.turnComplete) { - currentMode = 'listening' - emit('mode_change', { mode: 'listening' }) - - if (response.serverContent.modelTurn?.role === 'model') { - currentMessageId = generateMessageId() - const message: RealtimeMessage = { - id: currentMessageId, - role: 'assistant', - timestamp: Date.now(), - parts: [] - } - - for (const part of response.serverContent.modelTurn.parts || []) { - console.log(part) - - if (part.inlineData?.data && outputTranscription?.finished && outputTranscription.text) { - message.parts.push({ - type: "audio", - transcript: outputTranscription.text, - audioData: mediaHandler.convertBase64ToArrayBuffer(part.inlineData.data), - }) - } - } - - emit('message_complete', { message }) - } + break; + case 'turn_complete': + currentMessageId = generateMessageId() + message.id = currentMessageId + message.timestamp = Date.now() + + emit('message_complete', { message }) + message = { + id: '', + role: 'assistant', + timestamp: 0, + parts: [] } - }, - onerror(event) { - emit("error", { - error: new Error(event.message) + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + break; + case 'setup_complete': + emit('status_change', { status: 'connected' }) + break; + case 'error': + emit('error', { + error: new Error(response.data) }) - }, + break; } - }); - - // Request microphone access - await mediaHandler.startAudio((data) => { - session.sendRealtimeInput({ - audio: { - data: mediaHandler.convertArrayBufferToBase64(data), - mimeType: 'audio/pcm;rate=16000' - } - }) - }) + } - await mediaHandler.setupInputAudioAnalysis() - await mediaHandler.setupOutputAudioAnalysis() const connection: RealtimeConnection = { async disconnect() { - mediaHandler.stopAudio() - - await session.close(); - + audioStreamer.stop() + audioPlayer.destroy() + client.disconnect() currentMode = 'idle' emit('status_change', { status: 'idle' }) }, async startAudioCapture() { // Audio capture is established during connection setup - // This method enables the tracks and signals listening mode - mediaHandler.startAudioCapture() + audioStreamer.startAudioCapture() currentMode = 'listening' emit('mode_change', { mode: 'listening' }) }, stopAudioCapture() { - // Disable tracks rather than stopping them to allow re-enabling - mediaHandler.stopAudioCapture() + audioStreamer.stopAudioCapture() currentMode = 'idle' emit('mode_change', { mode: 'idle' }) }, sendText(text: string) { - session.sendRealtimeInput({ text }) + client.sendTextMessage(text) currentMode = 'thinking' emit('mode_change', { mode: 'thinking' }) }, sendImage(imageData: string, mimeType: string) { - // Only accepts raw image data, not URLs - session.sendRealtimeInput({ - video: { - data: imageData, - mimeType: mimeType - } - }) + client.sendImageMessage(imageData, mimeType) currentMode = 'thinking' emit('mode_change', { mode: 'thinking' }) }, sendToolResult(callId: string, result: string) { - session.sendToolResponse({ - functionResponses: [{ + client.sendToolResponse([{ id: callId, response: { result } - }] - }) + }]) + }, + + updateSession(config) { + client.updateSession(config) + emit('status_change', { status: 'reconnecting' }) }, - updateSession() { - // No equivalent of updateSession() exists dynamically as it does in OpenAI - // for updating system instructions, tools, etc mid-session. + updateToken(token) { + client.updateToken(token) + emit('status_change', { status: 'reconnecting' }) }, interrupt() { - mediaHandler.stopAudioPlayback() + audioPlayer.interrupt() currentMode = 'listening' emit('mode_change', { mode: 'listening' }) emit('interrupted', { messageId: currentMessageId ?? undefined }) @@ -348,35 +240,35 @@ async function createWebSocketConnection( getAudioVisualization(): AudioVisualization { return { get inputLevel() { - return mediaHandler.inputLevel + return audioStreamer.inputLevel }, get outputLevel() { - return mediaHandler.outputLevel + return audioPlayer.outputLevel }, getInputFrequencyData() { - return mediaHandler.inputFrequencyData + return audioStreamer.inputFrequencyData }, getOutputFrequencyData() { - return mediaHandler.outputFrequencyData + return audioPlayer.outputFrequencyData }, getInputTimeDomainData() { - return mediaHandler.inputTimeDomainData + return audioStreamer.inputTimeDomainData }, getOutputTimeDomainData() { - return mediaHandler.outputTimeDomainData + return audioPlayer.outputTimeDomainData }, get inputSampleRate() { - return 24000 + return audioStreamer.inputSampleRate }, get outputSampleRate() { - return 24000 + return audioPlayer.outputSampleRate }, } }, diff --git a/packages/typescript/ai-gemini/src/realtime/client.ts b/packages/typescript/ai-gemini/src/realtime/client.ts new file mode 100644 index 000000000..27038c291 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -0,0 +1,455 @@ +import { ActivityHandling, EndSensitivity, Modality, StartSensitivity, TurnCoverage } from "@google/genai"; +import type { ContextWindowCompressionConfig, FunctionCall, FunctionDeclaration, FunctionResponse, LiveClientMessage, LiveServerGoAway, LiveServerMessage, LiveServerSessionResumptionUpdate, UsageMetadata } from "@google/genai"; +import type { AnyClientTool, RealtimeSessionConfig, RealtimeToken } from "@tanstack/ai"; +import type { GeminiRealtimeModel, GeminiRealtimeVoice } from "./types"; + +interface LiveResponsePayloads { + text: string, + audio: { audioData: string, transcript: string }, + setup_complete: string, + interrupted: string, + turn_complete: string, + tool_call: Array, + session_resumption_update: LiveServerSessionResumptionUpdate, + go_away: LiveServerGoAway, + usage_metadata: UsageMetadata, + error: string, + input_transcription: { text: string, finished: boolean }, + output_transcription: { text: string, finished: boolean }, +} + +export type MultimodalLiveResponseType = keyof LiveResponsePayloads + +export type LiveResponse = { + [K in MultimodalLiveResponseType]: { + type: K, + data: LiveResponsePayloads[K], + endOfTurn: boolean, + } +}[MultimodalLiveResponseType] + +/** + * Parses response messages from the Gemini Live API + */ +/** + * Parses ALL response types from a single server message. + * The server can now bundle multiple fields (e.g. audio + transcription) + * in the same message. Returns an array of response objects. + */ +function parseResponseMessages(data: LiveServerMessage) { + const responses: Array = []; + const serverContent = data.serverContent; + const parts = serverContent?.modelTurn?.parts; + + try { + // Setup complete (exclusive — no other fields expected) + if (data.setupComplete) { + console.log("🏁 SETUP COMPLETE response", data); + responses.push({ type: "setup_complete", data: "", endOfTurn: false }); + return responses; + } + + // Tool call (exclusive) + if (data.toolCall?.functionCalls) { + console.log("🎯 🛠️ TOOL CALL response", data.toolCall); + responses.push({ type: "tool_call", data: data.toolCall.functionCalls, endOfTurn: false }); + return responses; + } + + if (data.sessionResumptionUpdate) { + responses.push({ type: "session_resumption_update", data: data.sessionResumptionUpdate, endOfTurn: false }) + } + + if (data.goAway) { + responses.push({ type: "go_away", data: data.goAway, endOfTurn: false }) + } + + if (data.usageMetadata) { + responses.push({ type: "usage_metadata", data: data.usageMetadata, endOfTurn: false }) + } + + // Audio data from model turn parts + if (parts?.length) { + for (const part of parts) { + if (part.inlineData?.data) { + responses.push({ + type: "audio", + data: { + audioData: part.inlineData.data, + // The transcription is independent to the model turn which means it doesn’t imply any ordering between transcription and model turn. + transcript: "", + }, + endOfTurn: false + }); + } else if (part.text) { + console.log("💬 TEXT response", part.text); + responses.push({ type: "text", data: part.text, endOfTurn: false }); + } + } + } + + // Transcriptions — checked independently, NOT in else-if with audio + if (serverContent?.inputTranscription) { + responses.push({ + type: "input_transcription", + data: { + text: serverContent.inputTranscription.text || "", + finished: serverContent.inputTranscription.finished || false, + }, + endOfTurn: false, + }); + } + + if (serverContent?.outputTranscription) { + responses.push({ + type: "output_transcription", + data: { + text: serverContent.outputTranscription.text || "", + finished: serverContent.outputTranscription.finished || false, + }, + endOfTurn: false, + }); + } + + // Interrupted + if (serverContent?.interrupted) { + console.log("🗣️ INTERRUPTED response"); + responses.push({ type: "interrupted", data: "", endOfTurn: false }); + } + + // Turn complete + if (serverContent?.turnComplete) { + console.log("🏁 TURN COMPLETE response"); + responses.push({ type: "turn_complete", data: "", endOfTurn: true }); + } + } catch (err) { + console.log("⚠️ Error parsing response data: ", err, data); + } + + return responses; +} + +export class GeminiLiveClient { + + private token: string | null = null + private model: GeminiRealtimeModel | null = null + + private responseModalities: Array = [Modality.AUDIO]; + private systemInstructions = ""; + private googleGrounding = false; + private voiceName: GeminiRealtimeVoice = "Puck"; // Default voice + private temperature = 1.0; // Default temperature + private inputAudioTranscription = false; + private outputAudioTranscription = false; + private contextWindowCompression: ContextWindowCompressionConfig | undefined = undefined; + private proactiveAudio = false; + private enableAffectiveDialog = false; + + private maxOutputTokens: number | undefined = undefined; + private functions: Array = []; + private functionsMap = new Map(); + + // Automatic activity detection settings with defaults + private automaticActivityDetection = { + disabled: false, + silence_duration_ms: 2000, + prefix_padding_ms: 500, + end_of_speech_sensitivity: EndSensitivity.END_SENSITIVITY_UNSPECIFIED, + start_of_speech_sensitivity: StartSensitivity.START_SENSITIVITY_UNSPECIFIED, + }; + + private activityHandling = ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED; + + public connected = false + private webSocket: WebSocket | null = null + // Last resumable session update + private lastResumptionUpdate: LiveServerSessionResumptionUpdate | null = null + private setupComplete = false + + public onReceiveResponse: (response: LiveResponse) => void = () => { } + public onOpen: () => void = () => { } + public onClose: () => void = () => { } + public onError: (error: string) => void = () => { } + + constructor(token: string, model: GeminiRealtimeModel, tools?: ReadonlyArray ) { + this.token = token + this.model = model + + if (tools) { + tools.forEach(tool => { + this.functions.push(tool) + this.functionsMap.set(tool.name, tool) + }) + } + } + + getFunctionDefinitions(): Array { + return this.functions.map(f => ({ + name: f.name, + description: f.description, + parametersJsonSchema: f.inputSchema, + responseJsonSchema: f.outputSchema, + })) + } + + /** + * Connection management + */ + connect() { + const promise = new Promise((resolve, reject) => { + this.webSocket = new WebSocket(`wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained?access_token=${this.token}`) + + this.webSocket.onclose = (event) => { + console.log('realtime websocket close:', event) + this.connected = false + this.setupComplete = false; + this.onClose() + } + + this.webSocket.onerror = (event) => { + console.error('realtime websocket error:', event) + this.connected = false + this.setupComplete = false; + this.onError('Connection error') + reject(event) + } + + this.webSocket.onopen = (event) => { + console.log('realtime websocket open:', event) + this.connected = true + this.onOpen() + resolve(this.webSocket) + } + + this.webSocket.onmessage = this.onReceiveMessage.bind(this) + }) + + return promise + } + + disconnect() { + if (this.webSocket) { + this.webSocket.close(); + this.connected = false; + this.setupComplete = false; + } + } + + /** + * Session management + */ + sendInitialSetupMessage(resume = false) { + const tools = this.getFunctionDefinitions() + + const sessionSetupMessage: LiveClientMessage = { + setup: { + model: `models/${this.model}`, + generationConfig: { + responseModalities: this.responseModalities, + temperature: this.temperature, + speechConfig: { + voiceConfig: { + prebuiltVoiceConfig: { + voiceName: this.voiceName + } + } + }, + enableAffectiveDialog: this.enableAffectiveDialog, + maxOutputTokens: this.maxOutputTokens, + }, + sessionResumption: { + transparent: true + }, + // TODO: add context window compression + contextWindowCompression: this.contextWindowCompression, + proactivity: { + proactiveAudio: this.proactiveAudio + }, + systemInstruction: { parts: [{ text: this.systemInstructions }] }, + tools: [{ functionDeclarations: tools }], + realtimeInputConfig: { + automaticActivityDetection: { + disabled: this.automaticActivityDetection.disabled, + silenceDurationMs: this.automaticActivityDetection.silence_duration_ms, + prefixPaddingMs: this.automaticActivityDetection.prefix_padding_ms, + endOfSpeechSensitivity: this.automaticActivityDetection.end_of_speech_sensitivity, + startOfSpeechSensitivity: this.automaticActivityDetection.start_of_speech_sensitivity, + }, + activityHandling: this.activityHandling, + turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY, + } + } + } + + if (this.inputAudioTranscription) { + sessionSetupMessage.setup!.inputAudioTranscription = {} + } + + if (this.outputAudioTranscription) { + sessionSetupMessage.setup!.outputAudioTranscription = {} + } + + if (this.googleGrounding) { + // Currently can't have both Google Search with custom tools. + console.warn( + "Google Grounding enabled, removing custom function calls if any." + ); + sessionSetupMessage.setup!.tools = [{ googleSearch: {} }]; + } + + if (resume) { + sessionSetupMessage.setup!.sessionResumption = { + handle: this.lastResumptionUpdate?.newHandle, + transparent: true + } + } + + console.log(sessionSetupMessage) + this.sendMessage(sessionSetupMessage) + } + + async restartSession(resume = false) { + this.disconnect() + await this.connect() + this.sendInitialSetupMessage(resume) + } + + updateToken(token: RealtimeToken) { + this.token = token.token + + if (token.config.model && this.model != token.config.model) { + this.model = token.config.model as GeminiRealtimeModel + this.restartSession() // Restart session completely with new model + } else { + this.restartSession(true) + } + } + + async updateSession(config: Partial) { + // model can only be set during initial setup + if (config.model && !this.setupComplete) { + this.model = config.model as GeminiRealtimeModel + } + + if (config.instructions) { + this.systemInstructions = config.instructions + } + + if (config.maxOutputTokens) { + this.maxOutputTokens = typeof config.maxOutputTokens === 'number' ? config.maxOutputTokens : undefined + } + + if (config.temperature) { + this.temperature = config.temperature + } + + if (config.voice) { + this.voiceName = config.voice as GeminiRealtimeVoice + } + + if (config.providerOptions?.googleGrounding) { + this.googleGrounding = config.providerOptions.googleGrounding + } + + if (config.providerOptions?.proactivity) { + this.proactiveAudio = config.providerOptions.proactivity + } + + if (config.providerOptions?.enableAffectiveDialog) { + this.enableAffectiveDialog = config.providerOptions.enableAffectiveDialog + } + + if (config.providerOptions?.contextWindowCompression) { + this.contextWindowCompression = config.providerOptions.contextWindowCompression + } + + const includeTranscription = config.outputModalities?.includes("text") || false + this.inputAudioTranscription = includeTranscription + this.outputAudioTranscription = includeTranscription + + if (!this.setupComplete) { + this.sendInitialSetupMessage() + } else { + return this.restartSession(true) + } + } + + /** + * Message transmission & receiving + */ + sendMessage(message: LiveClientMessage) { + if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.webSocket.send(JSON.stringify(message)); + } + } + + async onReceiveMessage(messageEvent: MessageEvent) { + let jsonData; + if (messageEvent.data instanceof Blob) { + jsonData = await messageEvent.data.text(); + } else if (messageEvent.data instanceof ArrayBuffer) { + jsonData = new TextDecoder().decode(messageEvent.data); + } else { + jsonData = messageEvent.data; + } + + try { + const messageData = JSON.parse(jsonData); + // Parse all response types from this message (audio + transcription can coexist) + const responses = parseResponseMessages(messageData); + for (const response of responses) { + if ( + response.type === "session_resumption_update" && + response.data.resumable + ) { + this.lastResumptionUpdate = response.data + } + if (response.type === "go_away") { + // TODO: refresh token and resume session + } + if (response.type === "setup_complete") { + this.setupComplete = true + } + this.onReceiveResponse(response); + } + } catch (err) { + console.error("Error parsing JSON message:", err, jsonData); + } + } + + sendRealtimeInputMessage(data: string, mimeType: string) { + const blob = { mimeType, data }; + + if (mimeType.startsWith("audio/")) { + this.sendMessage({ realtimeInput: { audio: blob } }) + } else if (mimeType.startsWith("image/") || mimeType.startsWith("video/")) { + this.sendMessage({ realtimeInput: { video: blob } }) + } + } + + sendAudioMessage(base64PCM: string) { + this.sendRealtimeInputMessage(base64PCM, "audio/pcm") + } + + sendImageMessage(base64: string, mimeType = 'image/jpeg') { + this.sendRealtimeInputMessage(base64, mimeType) + } + + sendTextMessage(text: string) { + const message: LiveClientMessage = { + realtimeInput: { + text + } + } + this.sendMessage(message) + } + + sendToolResponse(functionResponses: Array) { + const message: LiveClientMessage = { + toolResponse: { + functionResponses: functionResponses + } + } + this.sendMessage(message) + } +} \ No newline at end of file diff --git a/packages/typescript/ai-gemini/src/realtime/media-handler.ts b/packages/typescript/ai-gemini/src/realtime/media-handler.ts deleted file mode 100644 index 67ea744d9..000000000 --- a/packages/typescript/ai-gemini/src/realtime/media-handler.ts +++ /dev/null @@ -1,339 +0,0 @@ -const workletCode = ` -class PCMProcessor extends AudioWorkletProcessor { - constructor() { - super(); - this.bufferSize = 4096; - this.buffer = new Float32Array(this.bufferSize); - this.bufferIndex = 0; - } - - process(inputs, outputs, parameters) { - const input = inputs[0]; - if (!input || !input.length) return true; - - const channelData = input[0]; - - for (let i = 0; i < channelData.length; i++) { - this.buffer[this.bufferIndex++] = channelData[i]; - - if (this.bufferIndex >= this.bufferSize) { - this.port.postMessage(this.buffer); - this.bufferIndex = 0; - } - } - - return true; - } -} - -registerProcessor("pcm-processor", PCMProcessor); -` - -export class MediaHandler { - - private audioContext: AudioContext | null = null - private mediaStream: MediaStream | null = null - - private nextStartTime = 0 - private scheduledSources: Array = [] - - // Analyzers - private inputAnalyser: AnalyserNode | null = null - private outputAnalyser: AnalyserNode | null = null - private inputSource: MediaStreamAudioSourceNode | null = null - private outputGainNode: GainNode | null = null - - private audioWorkletNode: AudioWorkletNode | null = null - - // Empty arrays for when visualization isn't available - // frequencyBinCount = fftSize / 2 = 1024 - private emptyFrequencyData = new Uint8Array(1024) - private emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence - - public isRecording = false - - private workletUrl: string | null = null - - constructor() { - const workletBlob = new Blob([workletCode], { type: 'application/javascript' }) - this.workletUrl = URL.createObjectURL(workletBlob) - } - - async initializeAudio() { - if (!this.audioContext) { - this.audioContext = new AudioContext() - await this.audioContext.audioWorklet.addModule(this.workletUrl!) - } - - if (this.audioContext.state === 'suspended') { - await this.audioContext.resume().catch(() => {}) - } - } - - async setupInputAudioAnalysis() { - await this.initializeAudio() - if (!this.audioContext || !this.mediaStream) return - - this.inputAnalyser = this.audioContext.createAnalyser() - this.inputAnalyser.fftSize = 2048 // Larger size for more accurate level detection - this.inputAnalyser.smoothingTimeConstant = 0.3 - - this.inputSource = this.audioContext.createMediaStreamSource(this.mediaStream) - this.inputSource.connect(this.inputAnalyser) - } - - async setupOutputAudioAnalysis() { - await this.initializeAudio() - if (!this.audioContext) return - - this.outputAnalyser = this.audioContext.createAnalyser() - this.outputAnalyser.fftSize = 2048 - this.outputAnalyser.smoothingTimeConstant = 0.3 - - // Create a gain node as a routing hub for all playback sources - this.outputGainNode = this.audioContext.createGain() - this.outputGainNode.gain.value = 1 - - // Route: outputGainNode → outputAnalyser → destination - this.outputGainNode.connect(this.outputAnalyser) - this.outputAnalyser.connect(this.audioContext.destination) - } - - async startAudio(onAudioData: (audioData: ArrayBuffer) => void) { - await this.initializeAudio() - if (!this.audioContext) return - - try { - this.mediaStream = await navigator.mediaDevices.getUserMedia({ - audio: { - echoCancellation: true, - noiseSuppression: true, - sampleRate: 24000, - }, - }) - - const source = this.audioContext.createMediaStreamSource( - this.mediaStream - ); - - this.audioWorkletNode = new AudioWorkletNode( - this.audioContext, - "pcm-processor" - ); - - this.audioWorkletNode.port.onmessage = (event) => { - if (this.isRecording) { - const downsampled = this.downsampleBuffer( - event.data as Float32Array, - this.audioContext!.sampleRate, - 16000 - ); - const pcm16 = this.convertFloat32ToInt16(downsampled); - onAudioData(pcm16); - } - }; - - source.connect(this.audioWorkletNode); - - // Mute local feedback - const muteGain = this.audioContext.createGain(); - muteGain.gain.value = 0; - this.audioWorkletNode.connect(muteGain); - muteGain.connect(this.audioContext.destination); - - this.isRecording = true - } catch (error) { - throw new Error( - `Error starting audio: ${error instanceof Error ? error.message : error}`, - ) - } - } - - stopAudio() { - this.isRecording = false - if (this.mediaStream) { - this.mediaStream.getTracks().forEach((t) => t.stop()); - this.mediaStream = null; - } - if (this.audioWorkletNode) { - this.audioWorkletNode.disconnect(); - this.audioWorkletNode = null; - } - } - - startAudioCapture() { - if (this.mediaStream) { - for (const track of this.mediaStream.getAudioTracks()) { - track.enabled = true - } - } - this.isRecording = true - } - - stopAudioCapture() { - if (this.mediaStream) { - for (const track of this.mediaStream.getAudioTracks()) { - track.enabled = false - } - } - this.isRecording = false - } - - playAudio(arrayBuffer: ArrayBuffer) { - if (!this.audioContext) return; - if (this.audioContext.state === "suspended") { - this.audioContext.resume(); - } - - const pcmData = new Int16Array(arrayBuffer); - const float32Data = new Float32Array(pcmData.length); - for (let i = 0; i < pcmData.length; i++) { - float32Data[i] = pcmData[i]! / 32768.0; - } - - const buffer = this.audioContext.createBuffer(1, float32Data.length, 24000); - buffer.getChannelData(0).set(float32Data); - - const source = this.audioContext.createBufferSource(); - source.buffer = buffer; - - // Route through output analyser if available, otherwise direct to destination - const outputTarget = this.outputGainNode ?? this.audioContext.destination; - source.connect(outputTarget); - - const now = this.audioContext.currentTime; - this.nextStartTime = Math.max(now, this.nextStartTime); - source.start(this.nextStartTime); - this.nextStartTime += buffer.duration; - - this.scheduledSources.push(source); - source.onended = () => { - const idx = this.scheduledSources.indexOf(source); - if (idx > -1) this.scheduledSources.splice(idx, 1); - }; - } - - stopAudioPlayback() { - this.scheduledSources.forEach((s) => { - try { - s.stop(); - } catch (e) { } - }); - this.scheduledSources = []; - if (this.audioContext) { - this.nextStartTime = this.audioContext.currentTime; - } - } - - - - // Helper to calculate audio level from time domain data - // Uses peak amplitude which is more responsive for voice audio meters - calculateLevel(analyser: AnalyserNode): number { - const data = new Uint8Array(analyser.fftSize) - analyser.getByteTimeDomainData(data) - - // Find peak deviation from center (128 is silence) - // This is more responsive than RMS for voice level meters - let maxDeviation = 0 - for (const sample of data) { - const deviation = Math.abs(sample - 128) - if (deviation > maxDeviation) { - maxDeviation = deviation - } - } - - // Normalize to 0-1 range (max deviation is 128) - // Scale by 1.5x so that ~66% amplitude reads as full scale - // This provides good visual feedback without pegging too early - const normalized = maxDeviation / 128 - return Math.min(1, normalized * 1.5) - } - - get inputLevel() { - if (!this.inputAnalyser) return 0 - return this.calculateLevel(this.inputAnalyser) - } - - get outputLevel() { - if (!this.outputAnalyser) return 0 - return this.calculateLevel(this.outputAnalyser) - } - - get inputFrequencyData() { - if (!this.inputAnalyser) return this.emptyFrequencyData - const data = new Uint8Array(this.inputAnalyser.frequencyBinCount) - this.inputAnalyser.getByteFrequencyData(data) - return data - } - - get outputFrequencyData() { - if (!this.outputAnalyser) return this.emptyFrequencyData - const data = new Uint8Array(this.outputAnalyser.frequencyBinCount) - this.outputAnalyser.getByteFrequencyData(data) - return data - } - - get inputTimeDomainData() { - if (!this.inputAnalyser) return this.emptyTimeDomainData - const data = new Uint8Array(this.inputAnalyser.fftSize) - this.inputAnalyser.getByteTimeDomainData(data) - return data - } - - get outputTimeDomainData() { - if (!this.outputAnalyser) return this.emptyTimeDomainData - const data = new Uint8Array(this.outputAnalyser.fftSize) - this.outputAnalyser.getByteTimeDomainData(data) - return data - } - - // Utils - downsampleBuffer(buffer: Float32Array, sampleRate: number, outSampleRate: number) { - if (outSampleRate === sampleRate) return buffer; - const ratio = sampleRate / outSampleRate; - const newLength = Math.round(buffer.length / ratio); - const result = new Float32Array(newLength); - let offsetResult = 0; - let offsetBuffer = 0; - while (offsetResult < result.length) { - const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio); - let accum = 0, - count = 0; - for ( - let i = offsetBuffer; - i < nextOffsetBuffer && i < buffer.length; - i++ - ) { - accum += buffer[i]!; - count++; - } - result[offsetResult] = accum / count; - offsetResult++; - offsetBuffer = nextOffsetBuffer; - } - return result; - } - - convertFloat32ToInt16(buffer: Float32Array): ArrayBuffer { - let l = buffer.length; - const buf = new Int16Array(l); - while (l--) { - buf[l] = Math.min(1, Math.max(-1, buffer[l]!)) * 0x7fff; - } - return buf.buffer; - } - - convertBase64ToArrayBuffer(base64: string): ArrayBuffer { - const binaryString = atob(base64); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes.buffer; - } - - convertArrayBufferToBase64(arrayBuffer: ArrayBuffer): string { - return btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) - } -} \ No newline at end of file diff --git a/packages/typescript/ai-gemini/src/realtime/utils.ts b/packages/typescript/ai-gemini/src/realtime/utils.ts new file mode 100644 index 000000000..204c0ab23 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/utils.ts @@ -0,0 +1,450 @@ +import type { GeminiLiveClient } from "./client" + +/** + * Audio Worklet Processor for capturing and processing audio + */ +const captureWorkletCode = ` +class AudioCaptureProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.bufferSize = 512; // 32ms at 16kHz — per Gemini best practices (20-40ms chunks) + this.buffer = new Float32Array(this.bufferSize); + this.bufferIndex = 0; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + + if (input && input.length > 0) { + const inputChannel = input[0]; + + // Buffer the incoming audio + for (let i = 0; i < inputChannel.length; i++) { + this.buffer[this.bufferIndex++] = inputChannel[i]; + + // When buffer is full, send it to main thread + if (this.bufferIndex >= this.bufferSize) { + // Send the buffered audio to the main thread + this.port.postMessage({ + type: "audio", + data: this.buffer.slice(), + }); + + // Reset buffer + this.bufferIndex = 0; + } + } + } + + // Return true to keep the processor alive + return true; + } +} + +// Register the processor +registerProcessor("audio-capture-processor", AudioCaptureProcessor);` + +/** + * Audio Playback Worklet Processor for playing PCM audio. + * Uses an offset tracker instead of slice() to avoid allocations + * on the real-time audio thread. + */ +const playbackWorkletCode = ` +class PCMProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.audioQueue = []; + this.currentOffset = 0; // Track position in current buffer (avoids slice()) + + this.port.onmessage = (event) => { + if (event.data === "interrupt") { + // Clear the queue on interrupt + this.audioQueue = []; + this.currentOffset = 0; + } else if (event.data instanceof Float32Array) { + // Add audio data to the queue + this.audioQueue.push(event.data); + } + }; + } + + process(inputs, outputs, parameters) { + const output = outputs[0]; + if (output.length === 0) return true; + + const channel = output[0]; + let outputIndex = 0; + + // Fill the output buffer from the queue + while (outputIndex < channel.length && this.audioQueue.length > 0) { + const currentBuffer = this.audioQueue[0]; + + if (!currentBuffer || currentBuffer.length === 0) { + this.audioQueue.shift(); + this.currentOffset = 0; + continue; + } + + const remainingOutput = channel.length - outputIndex; + const remainingBuffer = currentBuffer.length - this.currentOffset; + const copyLength = Math.min(remainingOutput, remainingBuffer); + + // Copy audio data to output using offset (no slice allocation) + for (let i = 0; i < copyLength; i++) { + channel[outputIndex++] = currentBuffer[this.currentOffset++]; + } + + // If we've consumed the entire buffer, move to the next one + if (this.currentOffset >= currentBuffer.length) { + this.audioQueue.shift(); + this.currentOffset = 0; + } + } + + // Fill remaining output with silence + while (outputIndex < channel.length) { + channel[outputIndex++] = 0; + } + + return true; + } +} + +registerProcessor("pcm-processor", PCMProcessor);` + +function calculateLevel(analyser: AnalyserNode): number { + const data = new Uint8Array(analyser.fftSize) + analyser.getByteTimeDomainData(data) + + // Find peak deviation from center (128 is silence) + // This is more responsive than RMS for voice level meters + let maxDeviation = 0 + for (const sample of data) { + const deviation = Math.abs(sample - 128) + if (deviation > maxDeviation) { + maxDeviation = deviation + } + } + + // Normalize to 0-1 range (max deviation is 128) + // Scale by 1.5x so that ~66% amplitude reads as full scale + // This provides good visual feedback without pegging too early + const normalized = maxDeviation / 128 + return Math.min(1, normalized * 1.5) +} + +export function base64ToArrayBuffer(base64: string): ArrayBuffer { + const binary = atob(base64) + const bytes = Uint8Array.from(binary, char => char.charCodeAt(0)) + return bytes.buffer +} + +// Empty arrays for when visualization isn't available +// frequencyBinCount = fftSize / 2 = 1024 +const emptyFrequencyData = new Uint8Array(1024) +const emptyTimeDomainData = new Uint8Array(2048).fill(128) // 128 is silence + +export class AudioStreamer { + private audioContext: AudioContext | null = null + private audioWorklet: AudioWorkletNode | null = null + private mediaStream: MediaStream | null = null + private analyser: AnalyserNode | null = null + private isStreaming = false + private sampleRate = 16000 + private client: GeminiLiveClient | null = null + + constructor(client: GeminiLiveClient) { + this.client = client + } + + get inputLevel() { + if (!this.analyser) return 0 + return calculateLevel(this.analyser) + } + + get inputFrequencyData() { + if (!this.analyser) return emptyFrequencyData + const data = new Uint8Array(this.analyser.frequencyBinCount) + this.analyser.getByteFrequencyData(data) + return data + } + + get inputTimeDomainData() { + if (!this.analyser) return emptyTimeDomainData + const data = new Uint8Array(this.analyser.fftSize) + this.analyser.getByteTimeDomainData(data) + return data + } + + get inputSampleRate() { + return this.sampleRate + } + + async start() { + try { + const audioConstraints: MediaTrackConstraints = { + sampleRate: this.sampleRate, + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true, + } + + // Get microphone access + this.mediaStream = await navigator.mediaDevices.getUserMedia({ + audio: audioConstraints + }) + + // Check if native AGC is active + const track = this.mediaStream.getAudioTracks()[0]; + const settings = track?.getSettings(); + + if (settings?.autoGainControl) { + console.warn("Native AGC not supported.") + } + + // Create audio context + this.audioContext = new AudioContext({ + sampleRate: this.sampleRate + }) + + if (this.audioContext.state === 'suspended') { + await this.audioContext.resume().catch(() => { }) + } + + const workletBlob = new Blob([captureWorkletCode], { type: 'application/javascript' }) + const workletUrl = URL.createObjectURL(workletBlob) + + // Load the audio worklet module + await this.audioContext.audioWorklet.addModule(workletUrl) + + // Create the audio worklet node + this.audioWorklet = new AudioWorkletNode( + this.audioContext, + "audio-capture-processor" + ) + + // Set up message handling from the worklet + this.audioWorklet.port.onmessage = (event) => { + if (!this.isStreaming) return; + + if (event.data.type === "audio") { + const inputData = event.data.data; + const pcmData = this.convertToPCM16(inputData); + const base64Audio = this.arrayBufferToBase64(pcmData); + + // Send to Gemini + if (this.client && this.client.connected) { + this.client.sendAudioMessage(base64Audio); + } + } + }; + + // Create analyser for volume detection + this.analyser = this.audioContext.createAnalyser(); + this.analyser.fftSize = 2048 // Larger size for more accurate level detection + this.analyser.smoothingTimeConstant = 0.3 + + // Connect the audio graph + const source = this.audioContext.createMediaStreamSource( + this.mediaStream + ); + source.connect(this.analyser); + this.analyser.connect(this.audioWorklet); + + // Start streaming + this.isStreaming = true + console.log("Audio streaming started"); + } catch (error) { + console.error("Failed to start audio streaming:", error); + throw error; + } + } + + stop() { + this.isStreaming = false; + + if (this.audioWorklet) { + this.audioWorklet.disconnect(); + this.audioWorklet.port.close(); + this.audioWorklet = null; + } + + if (this.audioContext) { + this.audioContext.close(); + this.audioContext = null; + } + + if (this.mediaStream) { + this.mediaStream.getTracks().forEach((track) => track.stop()); + this.mediaStream = null; + } + + console.log("Audio streaming stopped"); + } + + startAudioCapture() { + if (this.mediaStream) { + for (const track of this.mediaStream.getAudioTracks()) { + track.enabled = true + } + } + this.isStreaming = true + } + + stopAudioCapture() { + if (this.mediaStream) { + // Disable tracks rather than stopping them to allow re-enabling + for (const track of this.mediaStream.getAudioTracks()) { + track.enabled = false + } + } + this.isStreaming = false + } + + private convertToPCM16(float32Array: Float32Array): ArrayBuffer { + const int16Array = new Int16Array(float32Array.length); + for (let i = 0; i < float32Array.length; i++) { + const sample = Math.max(-1, Math.min(1, float32Array[i]!)); + int16Array[i] = sample * 0x7fff; + } + return int16Array.buffer; + } + + private arrayBufferToBase64(buffer: ArrayBuffer): string { + const bytes = new Uint8Array(buffer) + const binary = String.fromCharCode(...bytes) + return btoa(binary) + } +} + +export class AudioPlayer { + private audioContext: AudioContext | null = null + private workletNode: AudioWorkletNode | null = null + private gainNode: GainNode | null = null + private analyser: AnalyserNode | null = null + private isInitialized = false + private volume = 1.0 + private sampleRate = 24000 + + get outputLevel() { + if (!this.analyser) return 0 + return calculateLevel(this.analyser) + } + + get outputFrequencyData() { + if (!this.analyser) return emptyFrequencyData + const data = new Uint8Array(this.analyser.frequencyBinCount) + this.analyser.getByteFrequencyData(data) + return data + } + + get outputTimeDomainData() { + if (!this.analyser) return emptyTimeDomainData + const data = new Uint8Array(this.analyser.fftSize) + this.analyser.getByteTimeDomainData(data) + return data + } + + get outputSampleRate() { + return this.sampleRate + } + + async init() { + if (this.isInitialized) return; + + try { + // Create audio context at 24kHz to match Gemini + this.audioContext = new AudioContext({ + sampleRate: this.sampleRate, + }); + + const workletBlob = new Blob([playbackWorkletCode], { type: 'application/javascript' }) + const workletUrl = URL.createObjectURL(workletBlob) + + // Load the audio worklet module + await this.audioContext.audioWorklet.addModule(workletUrl) + + // Create worklet node + this.workletNode = new AudioWorkletNode( + this.audioContext, + "pcm-processor" + ) + + // Create gain node for volume control + this.gainNode = this.audioContext.createGain(); + this.gainNode.gain.value = this.volume; + + // Create analyser for volume detection + this.analyser = this.audioContext.createAnalyser() + this.analyser.fftSize = 2048 // Larger size for more accurate level detection + this.analyser.smoothingTimeConstant = 0.3 + + // Connect nodes + this.workletNode.connect(this.gainNode); + this.gainNode.connect(this.analyser); + this.analyser.connect(this.audioContext.destination); + + this.isInitialized = true; + console.log("Audio player initialized"); + } catch (error) { + console.error("Failed to initialize audio player:", error); + throw error; + } + } + + async play(base64Audio: string) { + if (!this.isInitialized) { + await this.init(); + } + + try { + // Resume audio context if suspended + if (this.audioContext?.state === "suspended") { + await this.audioContext.resume(); + } + + // Efficient base64 → binary decode + const binaryString = atob(base64Audio); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // Convert PCM16 LE to Float32 + const inputArray = new Int16Array(bytes.buffer); + const float32Data = new Float32Array(inputArray.length); + for (let i = 0; i < inputArray.length; i++) { + float32Data[i] = inputArray[i]! / 32768; + } + + // Send to worklet for playback + this.workletNode?.port.postMessage(float32Data); + } catch (error) { + console.error("Error playing audio chunk:", error); + throw error; + } + } + + /* Interrupt playback */ + interrupt() { + if (this.workletNode) { + this.workletNode.port.postMessage("interrupt"); + } + } + + setVolume(volume: number) { + this.volume = Math.max(0, Math.min(1, volume)); + if (this.gainNode) { + this.gainNode.gain.value = this.volume; + } + } + + destroy() { + if (this.audioContext) { + this.audioContext.close(); + this.audioContext = null; + } + this.isInitialized = false; + } +} \ No newline at end of file diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index 29b1ad53d..0e36513ea 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -2,8 +2,6 @@ import { createRealtimeEventEmitter } from '@tanstack/ai' import type { AnyClientTool, AudioVisualization, - RealtimeEvent, - RealtimeEventHandler, RealtimeMessage, RealtimeMode, RealtimeSessionConfig, From 0214c99f745da033ae65cff0e2719a4f7c11bbf0 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sun, 19 Apr 2026 12:57:50 +0300 Subject: [PATCH 12/16] renamed providerOptions to modelOptions --- .../ai-client/src/realtime-client.ts | 24 ++----------------- .../ai-client/src/realtime-types.ts | 4 ++-- .../ai-elevenlabs/src/realtime/adapter.ts | 4 ++-- .../ai-gemini/src/realtime/adapter.ts | 6 ++--- .../ai-gemini/src/realtime/client.ts | 16 ++++++------- .../ai-openai/src/realtime/adapter.ts | 4 ++-- packages/typescript/ai/src/realtime/types.ts | 2 +- 7 files changed, 20 insertions(+), 40 deletions(-) diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index f2b468c7c..ea6b55c12 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -101,29 +101,9 @@ export class RealtimeClient { ? Array.from(this.clientTools.values()) : undefined - const { - instructions, - voice, - vadMode, - outputModalities, - temperature, - maxOutputTokens, - semanticEagerness, - providerOptions, - } = this.options - this.connection = await this.options.adapter.connect( - this.token, - { - instructions, - voice, - vadMode, - outputModalities, - temperature, - maxOutputTokens, - semanticEagerness, - providerOptions, - }, + this.token, + this.options, toolsList, ) diff --git a/packages/typescript/ai-client/src/realtime-types.ts b/packages/typescript/ai-client/src/realtime-types.ts index fde8f188d..1160e9cc6 100644 --- a/packages/typescript/ai-client/src/realtime-types.ts +++ b/packages/typescript/ai-client/src/realtime-types.ts @@ -31,7 +31,7 @@ export interface RealtimeAdapter { */ connect: ( token: RealtimeToken, - config: RealtimeSessionConfig, + config: RealtimeClientOptions, clientTools?: ReadonlyArray, ) => Promise } @@ -155,7 +155,7 @@ export interface RealtimeClientOptions { /** * Provider-specific options */ - providerOptions?: Record + modelOptions?: Record // Callbacks onStatusChange?: (status: RealtimeStatus) => void diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index cac5061e8..a3fd6cd06 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -9,7 +9,7 @@ import type { RealtimeStatus, RealtimeToken, } from '@tanstack/ai' -import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' +import type { RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' import type { ElevenLabsRealtimeOptions } from './types' /** @@ -39,7 +39,7 @@ export function elevenlabsRealtime( async connect( token: RealtimeToken, - _config: RealtimeSessionConfig, + config: RealtimeClientOptions, clientToolDefs?: ReadonlyArray, ): Promise { return createElevenLabsConnection(token, clientToolDefs) diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index 25df26b94..fdfc535d1 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -11,7 +11,7 @@ import type { RealtimeSessionConfig, RealtimeToken, } from '@tanstack/ai' -import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' +import type { AnyClientTool, RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' import type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types' /** @@ -39,7 +39,7 @@ export function geminiRealtime( connect( token: RealtimeToken, - config: RealtimeSessionConfig, + config: RealtimeClientOptions, clientTools?: ReadonlyArray, ): Promise { return createWebSocketConnection(token, config, clientTools) @@ -52,7 +52,7 @@ export function geminiRealtime( */ async function createWebSocketConnection( token: RealtimeToken, - config: RealtimeSessionConfig, + config: RealtimeClientOptions, tools?: ReadonlyArray, ): Promise { diff --git a/packages/typescript/ai-gemini/src/realtime/client.ts b/packages/typescript/ai-gemini/src/realtime/client.ts index 27038c291..2f8831048 100644 --- a/packages/typescript/ai-gemini/src/realtime/client.ts +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -347,20 +347,20 @@ export class GeminiLiveClient { this.voiceName = config.voice as GeminiRealtimeVoice } - if (config.providerOptions?.googleGrounding) { - this.googleGrounding = config.providerOptions.googleGrounding + if (config.modelOptions?.googleGrounding) { + this.googleGrounding = config.modelOptions.googleGrounding } - if (config.providerOptions?.proactivity) { - this.proactiveAudio = config.providerOptions.proactivity + if (config.modelOptions?.proactivity) { + this.proactiveAudio = config.modelOptions.proactivity } - if (config.providerOptions?.enableAffectiveDialog) { - this.enableAffectiveDialog = config.providerOptions.enableAffectiveDialog + if (config.modelOptions?.enableAffectiveDialog) { + this.enableAffectiveDialog = config.modelOptions.enableAffectiveDialog } - if (config.providerOptions?.contextWindowCompression) { - this.contextWindowCompression = config.providerOptions.contextWindowCompression + if (config.modelOptions?.contextWindowCompression) { + this.contextWindowCompression = config.modelOptions.contextWindowCompression } const includeTranscription = config.outputModalities?.includes("text") || false diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index 0e36513ea..2627beddd 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -8,7 +8,7 @@ import type { RealtimeStatus, RealtimeToken } from '@tanstack/ai' -import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' +import type { RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' import type { OpenAIRealtimeOptions } from './types' const OPENAI_REALTIME_URL = 'https://api.openai.com/v1/realtime' @@ -42,7 +42,7 @@ export function openaiRealtime( async connect( token: RealtimeToken, - _config: RealtimeSessionConfig, + config: RealtimeClientOptions, _clientTools?: ReadonlyArray, ): Promise { if (connectionMode === 'webrtc') { diff --git a/packages/typescript/ai/src/realtime/types.ts b/packages/typescript/ai/src/realtime/types.ts index f0dcbf9d8..0cffbdc89 100644 --- a/packages/typescript/ai/src/realtime/types.ts +++ b/packages/typescript/ai/src/realtime/types.ts @@ -52,7 +52,7 @@ export interface RealtimeSessionConfig { /** Eagerness level for semantic VAD ('low', 'medium', 'high') */ semanticEagerness?: 'low' | 'medium' | 'high' /** Provider-specific options */ - providerOptions?: Record + modelOptions?: Record } /** From a1ebb6691e957a30b0022566b052ea1960ddc0a9 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Wed, 22 Apr 2026 21:37:19 +0300 Subject: [PATCH 13/16] minor changes --- examples/ts-react-chat/.env.example | 4 ++ .../ts-react-chat/src/lib/use-realtime.ts | 12 +++-- .../ts-react-chat/src/routes/realtime.tsx | 2 +- .../ai-elevenlabs/src/realtime/token.ts | 2 +- .../ai-gemini/src/realtime/adapter.ts | 16 +++--- .../ai-gemini/src/realtime/client.ts | 52 +++++++++++++------ .../ai-gemini/src/realtime/utils.ts | 4 +- .../typescript/ai-react/src/realtime-types.ts | 4 ++ 8 files changed, 61 insertions(+), 35 deletions(-) diff --git a/examples/ts-react-chat/.env.example b/examples/ts-react-chat/.env.example index 2bdb43f49..178fc6d97 100644 --- a/examples/ts-react-chat/.env.example +++ b/examples/ts-react-chat/.env.example @@ -2,6 +2,10 @@ # Get yours at: https://platform.openai.com/api-keys OPENAI_API_KEY=sk-... +# Gemini API Key +# Get yours at: https://aistudio.google.com/api-keys +GEMINI_API_KEY=... + # ElevenLabs API Key (for realtime voice) # Get yours at: https://elevenlabs.io/app/settings/api-keys ELEVENLABS_API_KEY=xi-... diff --git a/examples/ts-react-chat/src/lib/use-realtime.ts b/examples/ts-react-chat/src/lib/use-realtime.ts index 2ac96c6b5..c3b360c6f 100644 --- a/examples/ts-react-chat/src/lib/use-realtime.ts +++ b/examples/ts-react-chat/src/lib/use-realtime.ts @@ -1,14 +1,14 @@ -import { createServerFn } from '@tanstack/react-start' import { realtimeToken } from '@tanstack/ai' -import { useRealtimeChat } from '@tanstack/ai-react' -import { openaiRealtime, openaiRealtimeToken } from '@tanstack/ai-openai' import { elevenlabsRealtime, elevenlabsRealtimeToken, } from '@tanstack/ai-elevenlabs' import { geminiRealtime, geminiRealtimeToken } from '@tanstack/ai-gemini' -import type { GeminiRealtimeVoice } from "@tanstack/ai-gemini" +import { openaiRealtime, openaiRealtimeToken } from '@tanstack/ai-openai' +import { useRealtimeChat } from '@tanstack/ai-react' +import { createServerFn } from '@tanstack/react-start' import type { OpenAIRealtimeVoice } from "@tanstack/ai-openai" +import type { GeminiRealtimeVoice } from "@tanstack/ai-gemini" import { realtimeClientTools } from '@/lib/realtime-tools' type Provider = 'openai' | 'elevenlabs' | 'gemini' @@ -29,7 +29,9 @@ const getRealtimeTokenFn = createServerFn({ method: 'POST' }) if (data.provider === 'gemini') { return realtimeToken({ - adapter: geminiRealtimeToken(), + adapter: geminiRealtimeToken({ + model: 'gemini-3.1-flash-live-preview', + }), }) } diff --git a/examples/ts-react-chat/src/routes/realtime.tsx b/examples/ts-react-chat/src/routes/realtime.tsx index 9b95cec80..560a71963 100644 --- a/examples/ts-react-chat/src/routes/realtime.tsx +++ b/examples/ts-react-chat/src/routes/realtime.tsx @@ -17,8 +17,8 @@ type Provider = 'openai' | 'elevenlabs' | 'gemini' type OutputMode = 'audio+text' | 'text-only' | 'audio-only' const PROVIDER_OPTIONS: Array<{ value: Provider; label: string }> = [ - { value: 'openai', label: 'OpenAI Realtime' }, { value: 'gemini', label: 'Google Gemini' }, + { value: 'openai', label: 'OpenAI Realtime' }, { value: 'elevenlabs', label: 'ElevenLabs' }, ] diff --git a/packages/typescript/ai-elevenlabs/src/realtime/token.ts b/packages/typescript/ai-elevenlabs/src/realtime/token.ts index 030d0c9a9..14e975da7 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/token.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/token.ts @@ -91,7 +91,7 @@ export function elevenlabsRealtimeToken( config: { voice: overrides?.voiceId, instructions: overrides?.systemPrompt, - providerOptions: { + modelOptions: { agentId, firstMessage: overrides?.firstMessage, language: overrides?.language, diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index fdfc535d1..f93f595de 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -8,7 +8,6 @@ import type { AudioVisualization, RealtimeMessage, RealtimeMode, - RealtimeSessionConfig, RealtimeToken, } from '@tanstack/ai' import type { AnyClientTool, RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' @@ -70,13 +69,6 @@ async function createWebSocketConnection( } const client = new GeminiLiveClient(token.token, model, tools) - const audioStreamer = new AudioStreamer(client) - const audioPlayer = new AudioPlayer() - await audioPlayer.init() - - client.connect() - - audioStreamer.start() let message: RealtimeMessage = { id: '', @@ -140,7 +132,7 @@ async function createWebSocketConnection( emit('interrupted', { messageId: currentMessageId ?? undefined }) break; case 'tool_call': - for (const tool of response.data) { + for (const tool of response.data.functionCalls || []) { if (tool.id && tool.name) { emit('tool_call', { toolCallId: tool.id, @@ -176,6 +168,12 @@ async function createWebSocketConnection( } } + await client.connect() + + const audioStreamer = new AudioStreamer(client) + const audioPlayer = new AudioPlayer() + await audioPlayer.init() + await audioStreamer.start() const connection: RealtimeConnection = { async disconnect() { diff --git a/packages/typescript/ai-gemini/src/realtime/client.ts b/packages/typescript/ai-gemini/src/realtime/client.ts index 2f8831048..5cdf9271c 100644 --- a/packages/typescript/ai-gemini/src/realtime/client.ts +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -1,5 +1,6 @@ +import { convertSchemaToJsonSchema } from "@tanstack/ai"; import { ActivityHandling, EndSensitivity, Modality, StartSensitivity, TurnCoverage } from "@google/genai"; -import type { ContextWindowCompressionConfig, FunctionCall, FunctionDeclaration, FunctionResponse, LiveClientMessage, LiveServerGoAway, LiveServerMessage, LiveServerSessionResumptionUpdate, UsageMetadata } from "@google/genai"; +import type { ContextWindowCompressionConfig, FunctionDeclaration, FunctionResponse, LiveClientMessage, LiveServerGoAway, LiveServerMessage, LiveServerSessionResumptionUpdate, LiveServerToolCall, UsageMetadata } from "@google/genai"; import type { AnyClientTool, RealtimeSessionConfig, RealtimeToken } from "@tanstack/ai"; import type { GeminiRealtimeModel, GeminiRealtimeVoice } from "./types"; @@ -9,7 +10,7 @@ interface LiveResponsePayloads { setup_complete: string, interrupted: string, turn_complete: string, - tool_call: Array, + tool_call: LiveServerToolCall, session_resumption_update: LiveServerSessionResumptionUpdate, go_away: LiveServerGoAway, usage_metadata: UsageMetadata, @@ -50,9 +51,9 @@ function parseResponseMessages(data: LiveServerMessage) { } // Tool call (exclusive) - if (data.toolCall?.functionCalls) { + if (data.toolCall) { console.log("🎯 🛠️ TOOL CALL response", data.toolCall); - responses.push({ type: "tool_call", data: data.toolCall.functionCalls, endOfTurn: false }); + responses.push({ type: "tool_call", data: data.toolCall, endOfTurn: false }); return responses; } @@ -160,11 +161,11 @@ export class GeminiLiveClient { private activityHandling = ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED; - public connected = false private webSocket: WebSocket | null = null // Last resumable session update private lastResumptionUpdate: LiveServerSessionResumptionUpdate | null = null private setupComplete = false + private connected = false public onReceiveResponse: (response: LiveResponse) => void = () => { } public onOpen: () => void = () => { } @@ -183,13 +184,12 @@ export class GeminiLiveClient { } } - getFunctionDefinitions(): Array { - return this.functions.map(f => ({ - name: f.name, - description: f.description, - parametersJsonSchema: f.inputSchema, - responseJsonSchema: f.outputSchema, - })) + get isConnected() { + return this.connected + } + + get isSetupCompelete() { + return this.setupComplete } /** @@ -204,6 +204,7 @@ export class GeminiLiveClient { this.connected = false this.setupComplete = false; this.onClose() + reject(new Error("Closed before connecting")) } this.webSocket.onerror = (event) => { @@ -211,7 +212,7 @@ export class GeminiLiveClient { this.connected = false this.setupComplete = false; this.onError('Connection error') - reject(event) + reject('Connection error') } this.webSocket.onopen = (event) => { @@ -238,6 +239,14 @@ export class GeminiLiveClient { /** * Session management */ + getFunctionDefinitions(): Array { + return this.functions.map(f => ({ + name: f.name, + description: f.description, + parameters: convertSchemaToJsonSchema(f.inputSchema) as any, + })) + } + sendInitialSetupMessage(resume = false) { const tools = this.getFunctionDefinitions() @@ -260,7 +269,6 @@ export class GeminiLiveClient { sessionResumption: { transparent: true }, - // TODO: add context window compression contextWindowCompression: this.contextWindowCompression, proactivity: { proactiveAudio: this.proactiveAudio @@ -277,7 +285,7 @@ export class GeminiLiveClient { }, activityHandling: this.activityHandling, turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY, - } + }, } } @@ -304,17 +312,19 @@ export class GeminiLiveClient { } } - console.log(sessionSetupMessage) + console.log("FINAL SETUP JSON:", JSON.stringify(sessionSetupMessage, null, 2)); this.sendMessage(sessionSetupMessage) } async restartSession(resume = false) { + console.log("RESTARTING SESSION") this.disconnect() await this.connect() this.sendInitialSetupMessage(resume) } updateToken(token: RealtimeToken) { + console.log("UPDATING TOKEN") this.token = token.token if (token.config.model && this.model != token.config.model) { @@ -335,6 +345,14 @@ export class GeminiLiveClient { this.systemInstructions = config.instructions } + if (config.tools !== undefined) { + this.functions = config.tools as Array + this.functionsMap.clear() + config.tools.forEach(tool => { + this.functionsMap.set(tool.name, tool as any) + }) + } + if (config.maxOutputTokens) { this.maxOutputTokens = typeof config.maxOutputTokens === 'number' ? config.maxOutputTokens : undefined } @@ -447,7 +465,7 @@ export class GeminiLiveClient { sendToolResponse(functionResponses: Array) { const message: LiveClientMessage = { toolResponse: { - functionResponses: functionResponses + functionResponses } } this.sendMessage(message) diff --git a/packages/typescript/ai-gemini/src/realtime/utils.ts b/packages/typescript/ai-gemini/src/realtime/utils.ts index 204c0ab23..a2824e49a 100644 --- a/packages/typescript/ai-gemini/src/realtime/utils.ts +++ b/packages/typescript/ai-gemini/src/realtime/utils.ts @@ -232,8 +232,8 @@ export class AudioStreamer { const pcmData = this.convertToPCM16(inputData); const base64Audio = this.arrayBufferToBase64(pcmData); - // Send to Gemini - if (this.client && this.client.connected) { + // Send to Gemini only if after setup complete + if (this.client?.isSetupCompelete) { this.client.sendAudioMessage(base64Audio); } } diff --git a/packages/typescript/ai-react/src/realtime-types.ts b/packages/typescript/ai-react/src/realtime-types.ts index bad512d29..a164344d3 100644 --- a/packages/typescript/ai-react/src/realtime-types.ts +++ b/packages/typescript/ai-react/src/realtime-types.ts @@ -4,6 +4,7 @@ import type { RealtimeMode, RealtimeStatus, RealtimeToken, + UsageInfo, } from '@tanstack/ai' import type { RealtimeAdapter } from '@tanstack/ai-client' @@ -79,6 +80,9 @@ export interface UseRealtimeChatOptions { onMessage?: (message: RealtimeMessage) => void onModeChange?: (mode: RealtimeMode) => void onInterrupted?: () => void + onUsage?: (usage: UsageInfo) => void + onGoAway?: (go_away: { timeLeft?: string }) => void + onStatusChange?: (status: RealtimeStatus) => void } /** From 4cae54fa863a561f6ea96a384dbdbb0774a0b155 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Thu, 23 Apr 2026 23:01:34 +0300 Subject: [PATCH 14/16] fixed liveConnectConstraints in geminiRealtimeToken overriding GeminiLiveClient setup message --- .../ts-react-chat/src/lib/use-realtime.ts | 4 +-- .../ts-react-chat/src/routes/realtime.tsx | 2 +- .../ai-client/src/realtime-client.ts | 1 - .../ai-client/src/realtime-types.ts | 1 - .../ai-elevenlabs/src/realtime/adapter.ts | 3 +-- .../ai-gemini/src/realtime/adapter.ts | 18 ++++++++----- .../ai-gemini/src/realtime/client.ts | 12 +++++---- .../ai-gemini/src/realtime/token.ts | 25 ++++++------------- .../ai-gemini/src/realtime/types.ts | 14 ++++++++--- .../ai-openai/src/realtime/adapter.ts | 3 +-- 10 files changed, 41 insertions(+), 42 deletions(-) diff --git a/examples/ts-react-chat/src/lib/use-realtime.ts b/examples/ts-react-chat/src/lib/use-realtime.ts index c3b360c6f..41a17798c 100644 --- a/examples/ts-react-chat/src/lib/use-realtime.ts +++ b/examples/ts-react-chat/src/lib/use-realtime.ts @@ -29,9 +29,7 @@ const getRealtimeTokenFn = createServerFn({ method: 'POST' }) if (data.provider === 'gemini') { return realtimeToken({ - adapter: geminiRealtimeToken({ - model: 'gemini-3.1-flash-live-preview', - }), + adapter: geminiRealtimeToken(), }) } diff --git a/examples/ts-react-chat/src/routes/realtime.tsx b/examples/ts-react-chat/src/routes/realtime.tsx index 560a71963..1d2e4a527 100644 --- a/examples/ts-react-chat/src/routes/realtime.tsx +++ b/examples/ts-react-chat/src/routes/realtime.tsx @@ -44,7 +44,7 @@ function outputModeToModalities( } function RealtimePage() { - const [provider, setProvider] = useState('openai') + const [provider, setProvider] = useState('gemini') const [agentId, setAgentId] = useState('') const [textInput, setTextInput] = useState('') const [outputMode, setOutputMode] = useState('audio+text') diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index ea6b55c12..7a0b9d86f 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -103,7 +103,6 @@ export class RealtimeClient { this.connection = await this.options.adapter.connect( this.token, - this.options, toolsList, ) diff --git a/packages/typescript/ai-client/src/realtime-types.ts b/packages/typescript/ai-client/src/realtime-types.ts index 1160e9cc6..c81f662de 100644 --- a/packages/typescript/ai-client/src/realtime-types.ts +++ b/packages/typescript/ai-client/src/realtime-types.ts @@ -31,7 +31,6 @@ export interface RealtimeAdapter { */ connect: ( token: RealtimeToken, - config: RealtimeClientOptions, clientTools?: ReadonlyArray, ) => Promise } diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index a3fd6cd06..157a4623a 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -9,7 +9,7 @@ import type { RealtimeStatus, RealtimeToken, } from '@tanstack/ai' -import type { RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' +import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { ElevenLabsRealtimeOptions } from './types' /** @@ -39,7 +39,6 @@ export function elevenlabsRealtime( async connect( token: RealtimeToken, - config: RealtimeClientOptions, clientToolDefs?: ReadonlyArray, ): Promise { return createElevenLabsConnection(token, clientToolDefs) diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index f93f595de..ebe67b4e3 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -10,7 +10,7 @@ import type { RealtimeMode, RealtimeToken, } from '@tanstack/ai' -import type { AnyClientTool, RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' +import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types' /** @@ -38,10 +38,9 @@ export function geminiRealtime( connect( token: RealtimeToken, - config: RealtimeClientOptions, clientTools?: ReadonlyArray, ): Promise { - return createWebSocketConnection(token, config, clientTools) + return createWebSocketConnection(token, options.model, clientTools) }, } } @@ -51,14 +50,12 @@ export function geminiRealtime( */ async function createWebSocketConnection( token: RealtimeToken, - config: RealtimeClientOptions, + model: GeminiRealtimeModel = 'gemini-3.1-flash-live-preview', tools?: ReadonlyArray, ): Promise { const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() - const model = (token.config.model ?? 'gemini-3.1-flash-live-preview') as GeminiRealtimeModel - // Current state let currentMode: RealtimeMode = 'idle' let currentMessageId: string | null = null @@ -77,6 +74,15 @@ async function createWebSocketConnection( parts: [] } + client.onClose = () => { + emit('status_change', { status: 'idle' }) + } + + client.onError = (error) => { + emit('error', { error }) + emit('status_change', { status: 'error' }) + } + client.onReceiveResponse = (response: LiveResponse) => { switch (response.type) { case 'text': diff --git a/packages/typescript/ai-gemini/src/realtime/client.ts b/packages/typescript/ai-gemini/src/realtime/client.ts index 5cdf9271c..554231022 100644 --- a/packages/typescript/ai-gemini/src/realtime/client.ts +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -170,7 +170,7 @@ export class GeminiLiveClient { public onReceiveResponse: (response: LiveResponse) => void = () => { } public onOpen: () => void = () => { } public onClose: () => void = () => { } - public onError: (error: string) => void = () => { } + public onError: (error: Error) => void = () => { } constructor(token: string, model: GeminiRealtimeModel, tools?: ReadonlyArray ) { this.token = token @@ -211,8 +211,9 @@ export class GeminiLiveClient { console.error('realtime websocket error:', event) this.connected = false this.setupComplete = false; - this.onError('Connection error') - reject('Connection error') + const error = new Error('Connection error') + this.onError(error) + reject(error) } this.webSocket.onopen = (event) => { @@ -243,7 +244,8 @@ export class GeminiLiveClient { return this.functions.map(f => ({ name: f.name, description: f.description, - parameters: convertSchemaToJsonSchema(f.inputSchema) as any, + parametersJsonSchema: convertSchemaToJsonSchema(f.inputSchema), + outputJsonSchema: convertSchemaToJsonSchema(f.outputSchema), })) } @@ -345,7 +347,7 @@ export class GeminiLiveClient { this.systemInstructions = config.instructions } - if (config.tools !== undefined) { + if (config.tools) { this.functions = config.tools as Array this.functionsMap.clear() config.tools.forEach(tool => { diff --git a/packages/typescript/ai-gemini/src/realtime/token.ts b/packages/typescript/ai-gemini/src/realtime/token.ts index 5d6ac18f7..fa39698b6 100644 --- a/packages/typescript/ai-gemini/src/realtime/token.ts +++ b/packages/typescript/ai-gemini/src/realtime/token.ts @@ -1,7 +1,7 @@ -import { GoogleGenAI, Modality } from '@google/genai' +import { GoogleGenAI } from '@google/genai' import { getGeminiApiKeyFromEnv } from '../utils' import type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai' -import type { GeminiRealtimeModel, GeminiRealtimeTokenOptions } from './types' +import type { GeminiRealtimeTokenOptions } from './types' /** * Creates a Google Gemini realtime token adapter. @@ -18,7 +18,10 @@ import type { GeminiRealtimeModel, GeminiRealtimeTokenOptions } from './types' * * const token = await realtimeToken({ * adapter: geminiRealtimeToken({ - * model: 'gemini-live-2.5-flash-native-audio', + * // Optional: constraint model config by token + * liveConnectConstraints: { + * model: 'gemini-live-2.5-flash-native-audio', + * }, * }), * }) * ``` @@ -38,21 +41,11 @@ export function geminiRealtimeToken( return { provider: 'gemini', async generateToken(): Promise { - const model: GeminiRealtimeModel = - options.model ?? 'gemini-3.1-flash-live-preview' - const token = await client.authTokens.create({ config: { uses: 1, // The default expireTime: new Date(expireTime).toISOString(), - liveConnectConstraints: { - model, - config: { - sessionResumption: {}, - maxOutputTokens: options.maxOutputTokens, - responseModalities: [Modality.AUDIO], - }, - }, + liveConnectConstraints: options.liveConnectConstraints, httpOptions: { apiVersion: 'v1alpha', }, @@ -68,9 +61,7 @@ export function geminiRealtimeToken( token: token.name, expiresAt: expireTime, config: { - model, - maxOutputTokens: options.maxOutputTokens, - outputModalities: ['audio'], + model: options.liveConnectConstraints?.model, }, } }, diff --git a/packages/typescript/ai-gemini/src/realtime/types.ts b/packages/typescript/ai-gemini/src/realtime/types.ts index cb89d29d1..7ed7c2138 100644 --- a/packages/typescript/ai-gemini/src/realtime/types.ts +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -1,4 +1,4 @@ -import type { ContextWindowCompressionConfig, ProactivityConfig, ThinkingConfig } from "@google/genai"; +import type { ContextWindowCompressionConfig, LiveConnectConstraints, ProactivityConfig, ThinkingConfig } from "@google/genai"; /** * Gemini realtime voice options @@ -48,22 +48,28 @@ export type GeminiRealtimeModel = export interface GeminiRealtimeOptions { /** Connection mode (default: 'websocket' in browser) */ connectionMode?: 'websocket' + model?: GeminiRealtimeModel } /** * Options for the Gemini realtime token adapter */ +export interface StrictLiveConnectionConstraints extends Omit { + model?: GeminiRealtimeModel +} + export interface GeminiRealtimeTokenOptions { /** Model to use (default: 'gemini-live-2.5-flash-native-audio') */ - model?: GeminiRealtimeModel expiresAt?: number - maxOutputTokens?: number + /** + * NOTE: Adding liveConnectConstraints will cause the model to ignore any config passed in the WebSocket setup message. + */ + liveConnectConstraints?: StrictLiveConnectionConstraints } /** * Gemini Realtime provider options */ - export interface GeminiRealtimeProviderOptions { languageCode?: string contextWindowCompression?: ContextWindowCompressionConfig diff --git a/packages/typescript/ai-openai/src/realtime/adapter.ts b/packages/typescript/ai-openai/src/realtime/adapter.ts index 2627beddd..7924f1e18 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -8,7 +8,7 @@ import type { RealtimeStatus, RealtimeToken } from '@tanstack/ai' -import type { RealtimeAdapter, RealtimeClientOptions, RealtimeConnection } from '@tanstack/ai-client' +import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { OpenAIRealtimeOptions } from './types' const OPENAI_REALTIME_URL = 'https://api.openai.com/v1/realtime' @@ -42,7 +42,6 @@ export function openaiRealtime( async connect( token: RealtimeToken, - config: RealtimeClientOptions, _clientTools?: ReadonlyArray, ): Promise { if (connectionMode === 'webrtc') { From 7745d91537fa3f7dce86c4514b1b666efd1ce9cf Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sun, 26 Apr 2026 11:41:36 +0300 Subject: [PATCH 15/16] fixed message handling and bugs --- .../ai-client/src/realtime-client.ts | 30 + packages/typescript/ai-gemini/package.json | 6 +- .../ai-gemini/src/realtime/adapter.ts | 16 +- .../ai-gemini/src/realtime/client.ts | 61 +- .../ai-gemini/src/realtime/token.ts | 2 +- .../ai-gemini/src/realtime/types.ts | 14 +- .../typescript/ai-react/src/realtime-types.ts | 8 +- .../ai-react/src/use-realtime-chat.ts | 21 +- pnpm-lock.yaml | 1193 ++++++++--------- 9 files changed, 670 insertions(+), 681 deletions(-) diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index 7a0b9d86f..82ed354e4 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -4,6 +4,7 @@ import type { AudioVisualization, RealtimeMessage, RealtimeMode, + RealtimeSessionConfig, RealtimeStatus, RealtimeToken, } from '@tanstack/ai' @@ -282,6 +283,35 @@ export class RealtimeClient { return this.connection?.getAudioVisualization() ?? null } + /** + * Update the session configuration. + * This applies changes to the active connection and persists them for future reconnections. + */ + updateSession(config: Partial): void { + // Update local options so future connections use the updated config + const sessionKeys: Array = [ + 'instructions', + 'voice', + 'vadMode', + 'tools', + 'outputModalities', + 'temperature', + 'maxOutputTokens', + 'semanticEagerness', + 'modelOptions', + ] + + for (const key of sessionKeys) { + if (key in config) { + (this.options as any)[key] = (config as any)[key] + } + } + + if (this.connection) { + this.applySessionConfig() + } + } + // ============================================================================ // State Subscription // ============================================================================ diff --git a/packages/typescript/ai-gemini/package.json b/packages/typescript/ai-gemini/package.json index a8a4a1953..6f2a128d5 100644 --- a/packages/typescript/ai-gemini/package.json +++ b/packages/typescript/ai-gemini/package.json @@ -39,9 +39,6 @@ "tanstack", "adapter" ], - "dependencies": { - "@google/genai": "^1.46.0" - }, "peerDependencies": { "@tanstack/ai": "workspace:^", "@tanstack/ai-client": "workspace:^" @@ -51,5 +48,8 @@ "@tanstack/ai-client": "workspace:*", "@vitest/coverage-v8": "4.0.14", "vite": "^7.3.1" + }, + "dependencies": { + "@google/genai": "^1.50.1" } } diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index ebe67b4e3..f83dade52 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -74,13 +74,17 @@ async function createWebSocketConnection( parts: [] } + let pendingAssistantResponse = '' + client.onClose = () => { emit('status_change', { status: 'idle' }) + emit('mode_change', { mode: 'idle' }) } client.onError = (error) => { emit('error', { error }) emit('status_change', { status: 'error' }) + emit('mode_change', { mode: 'idle' }) } client.onReceiveResponse = (response: LiveResponse) => { @@ -119,15 +123,16 @@ async function createWebSocketConnection( emit('mode_change', { mode: 'thinking' }) } emit('transcript', { - isFinal: response.data.finished, + isFinal: true, transcript: response.data.text, role: 'user', }) break; case 'output_transcription': + pendingAssistantResponse += response.data.text emit('transcript', { isFinal: response.data.finished, - transcript: response.data.text, + transcript: pendingAssistantResponse, role: 'assistant', }) break; @@ -152,8 +157,13 @@ async function createWebSocketConnection( currentMessageId = generateMessageId() message.id = currentMessageId message.timestamp = Date.now() - + message.parts.push({ + type: "text", + content: pendingAssistantResponse + }) emit('message_complete', { message }) + + pendingAssistantResponse = '' message = { id: '', role: 'assistant', diff --git a/packages/typescript/ai-gemini/src/realtime/client.ts b/packages/typescript/ai-gemini/src/realtime/client.ts index 554231022..7d4a73b11 100644 --- a/packages/typescript/ai-gemini/src/realtime/client.ts +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -5,18 +5,19 @@ import type { AnyClientTool, RealtimeSessionConfig, RealtimeToken } from "@tanst import type { GeminiRealtimeModel, GeminiRealtimeVoice } from "./types"; interface LiveResponsePayloads { - text: string, - audio: { audioData: string, transcript: string }, - setup_complete: string, - interrupted: string, - turn_complete: string, - tool_call: LiveServerToolCall, - session_resumption_update: LiveServerSessionResumptionUpdate, - go_away: LiveServerGoAway, - usage_metadata: UsageMetadata, - error: string, - input_transcription: { text: string, finished: boolean }, - output_transcription: { text: string, finished: boolean }, + text: string + thought: string + audio: { audioData: string, transcript: string } + setup_complete: string + interrupted: string + turn_complete: string + tool_call: LiveServerToolCall + session_resumption_update: LiveServerSessionResumptionUpdate + go_away: LiveServerGoAway + usage_metadata: UsageMetadata + error: string + input_transcription: { text: string, finished: boolean } + output_transcription: { text: string, finished: boolean } } export type MultimodalLiveResponseType = keyof LiveResponsePayloads @@ -83,8 +84,13 @@ function parseResponseMessages(data: LiveServerMessage) { endOfTurn: false }); } else if (part.text) { - console.log("💬 TEXT response", part.text); - responses.push({ type: "text", data: part.text, endOfTurn: false }); + if (part.thought) { + console.log("💬 THOUGHT response", part.text); + responses.push({ type: "thought", data: part.text, endOfTurn: false }); + } else { + console.log("💬 TEXT response", part.text); + responses.push({ type: "text", data: part.text, endOfTurn: false }); + } } } } @@ -138,8 +144,8 @@ export class GeminiLiveClient { private responseModalities: Array = [Modality.AUDIO]; private systemInstructions = ""; private googleGrounding = false; - private voiceName: GeminiRealtimeVoice = "Puck"; // Default voice - private temperature = 1.0; // Default temperature + private voiceName: GeminiRealtimeVoice = "Puck"; + private temperature = 1.0; private inputAudioTranscription = false; private outputAudioTranscription = false; private contextWindowCompression: ContextWindowCompressionConfig | undefined = undefined; @@ -150,7 +156,6 @@ export class GeminiLiveClient { private functions: Array = []; private functionsMap = new Map(); - // Automatic activity detection settings with defaults private automaticActivityDetection = { disabled: false, silence_duration_ms: 2000, @@ -162,7 +167,6 @@ export class GeminiLiveClient { private activityHandling = ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED; private webSocket: WebSocket | null = null - // Last resumable session update private lastResumptionUpdate: LiveServerSessionResumptionUpdate | null = null private setupComplete = false private connected = false @@ -269,7 +273,8 @@ export class GeminiLiveClient { maxOutputTokens: this.maxOutputTokens, }, sessionResumption: { - transparent: true + transparent: true, + handle: resume ? this.lastResumptionUpdate?.newHandle : undefined }, contextWindowCompression: this.contextWindowCompression, proactivity: { @@ -307,13 +312,6 @@ export class GeminiLiveClient { sessionSetupMessage.setup!.tools = [{ googleSearch: {} }]; } - if (resume) { - sessionSetupMessage.setup!.sessionResumption = { - handle: this.lastResumptionUpdate?.newHandle, - transparent: true - } - } - console.log("FINAL SETUP JSON:", JSON.stringify(sessionSetupMessage, null, 2)); this.sendMessage(sessionSetupMessage) } @@ -399,6 +397,7 @@ export class GeminiLiveClient { */ sendMessage(message: LiveClientMessage) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + // TODO: add message buffering this.webSocket.send(JSON.stringify(message)); } } @@ -418,14 +417,8 @@ export class GeminiLiveClient { // Parse all response types from this message (audio + transcription can coexist) const responses = parseResponseMessages(messageData); for (const response of responses) { - if ( - response.type === "session_resumption_update" && - response.data.resumable - ) { - this.lastResumptionUpdate = response.data - } - if (response.type === "go_away") { - // TODO: refresh token and resume session + if (response.type === "session_resumption_update" && response.data.resumable) { + this.lastResumptionUpdate = response.data; } if (response.type === "setup_complete") { this.setupComplete = true diff --git a/packages/typescript/ai-gemini/src/realtime/token.ts b/packages/typescript/ai-gemini/src/realtime/token.ts index fa39698b6..ab5a49ca8 100644 --- a/packages/typescript/ai-gemini/src/realtime/token.ts +++ b/packages/typescript/ai-gemini/src/realtime/token.ts @@ -43,7 +43,7 @@ export function geminiRealtimeToken( async generateToken(): Promise { const token = await client.authTokens.create({ config: { - uses: 1, // The default + uses: options.uses ?? 1, expireTime: new Date(expireTime).toISOString(), liveConnectConstraints: options.liveConnectConstraints, httpOptions: { diff --git a/packages/typescript/ai-gemini/src/realtime/types.ts b/packages/typescript/ai-gemini/src/realtime/types.ts index 7ed7c2138..4911b1d00 100644 --- a/packages/typescript/ai-gemini/src/realtime/types.ts +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -51,18 +51,20 @@ export interface GeminiRealtimeOptions { model?: GeminiRealtimeModel } -/** - * Options for the Gemini realtime token adapter - */ export interface StrictLiveConnectionConstraints extends Omit { model?: GeminiRealtimeModel } +/** + * Options for the Gemini realtime token adapter + */ export interface GeminiRealtimeTokenOptions { - /** Model to use (default: 'gemini-live-2.5-flash-native-audio') */ expiresAt?: number - /** - * NOTE: Adding liveConnectConstraints will cause the model to ignore any config passed in the WebSocket setup message. + uses?: number + /** + * Config for LiveConnectConstraints for Auth Token creation. + * + * NOTE: Adding liveConnectConstraints will cause the API to ignore any config passed later to WebSocket. */ liveConnectConstraints?: StrictLiveConnectionConstraints } diff --git a/packages/typescript/ai-react/src/realtime-types.ts b/packages/typescript/ai-react/src/realtime-types.ts index a164344d3..74ac1c10f 100644 --- a/packages/typescript/ai-react/src/realtime-types.ts +++ b/packages/typescript/ai-react/src/realtime-types.ts @@ -81,7 +81,7 @@ export interface UseRealtimeChatOptions { onModeChange?: (mode: RealtimeMode) => void onInterrupted?: () => void onUsage?: (usage: UsageInfo) => void - onGoAway?: (go_away: { timeLeft?: string }) => void + onGoAway?: (timeLeft?: string) => void onStatusChange?: (status: RealtimeStatus) => void } @@ -139,9 +139,13 @@ export interface UseRealtimeChatReturn { /** Get time domain data for output waveform */ getOutputTimeDomainData: () => Uint8Array - // VAD control + // Session control /** Current VAD mode */ vadMode: 'server' | 'semantic' | 'manual' /** Change VAD mode at runtime */ setVADMode: (mode: 'server' | 'semantic' | 'manual') => void + /** Current max output tokens */ + maxOutputTokens: number | 'inf' + /** Change max output tokens at runtime */ + setMaxOutputTokens: (maxOutputTokens: number | 'inf') => void } diff --git a/packages/typescript/ai-react/src/use-realtime-chat.ts b/packages/typescript/ai-react/src/use-realtime-chat.ts index 63272821f..107e2f638 100644 --- a/packages/typescript/ai-react/src/use-realtime-chat.ts +++ b/packages/typescript/ai-react/src/use-realtime-chat.ts @@ -73,6 +73,7 @@ export function useRealtimeChat( const [vadMode, setVADModeState] = useState<'server' | 'semantic' | 'manual'>( options.vadMode ?? 'server', ) + const [maxOutputTokens, setMaxOutputTokensState] = useState(options.maxOutputTokens ?? 'inf') // Refs const clientRef = useRef(null) @@ -107,6 +108,12 @@ export function useRealtimeChat( setMessages((prev) => [...prev, message]) optionsRef.current.onMessage?.(message) }, + onUsage(usage) { + optionsRef.current.onUsage?.(usage) + }, + onGoAway(timeLeft) { + optionsRef.current.onGoAway?.(timeLeft) + }, onError: (err) => { setError(err) optionsRef.current.onError?.(err) @@ -233,7 +240,15 @@ export function useRealtimeChat( const setVADMode = useCallback( (newMode: 'server' | 'semantic' | 'manual') => { setVADModeState(newMode) - // TODO: Update session config if connected + clientRef.current?.updateSession({ vadMode: newMode }) + }, + [], + ) + + const setMaxOutputTokens = useCallback( + (newMaxOutputTokens: number | 'inf') => { + setMaxOutputTokensState(newMaxOutputTokens) + clientRef.current?.updateSession({ maxOutputTokens: newMaxOutputTokens }) }, [], ) @@ -270,7 +285,9 @@ export function useRealtimeChat( getInputTimeDomainData, getOutputTimeDomainData, - // VAD control + // Session control + maxOutputTokens, + setMaxOutputTokens, vadMode, setVADMode, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8c40f0cf..36d7f76c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,7 +33,7 @@ importers: version: 0.3.1(typescript@5.9.3) '@tanstack/vite-config': specifier: 0.4.1 - version: 0.4.1(@types/node@24.10.3)(rollup@4.57.1)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 0.4.1(@types/node@24.10.3)(rollup@4.60.2)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@types/node': specifier: ^24.10.1 version: 24.10.3 @@ -84,7 +84,7 @@ importers: version: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: ^4.0.14 - version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) examples/php-slim: devDependencies: @@ -178,7 +178,7 @@ importers: version: 5.1.2(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) typescript: specifier: 5.9.3 version: 5.9.3 @@ -187,7 +187,7 @@ importers: version: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: ^4.0.14 - version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) web-vitals: specifier: ^5.1.0 version: 5.1.0 @@ -323,7 +323,7 @@ importers: version: 5.1.2(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) typescript: specifier: 5.9.3 version: 5.9.3 @@ -332,7 +332,7 @@ importers: version: 7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: ^4.0.14 - version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) web-vitals: specifier: ^5.1.0 version: 5.1.0 @@ -365,7 +365,7 @@ importers: version: 0.561.0(react@19.2.3) nitro: specifier: 3.0.1-alpha.2 - version: 3.0.1-alpha.2(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.4)(rolldown@1.0.0-beta.53)(rollup@4.57.1)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 3.0.1-alpha.2(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.4)(rolldown@1.0.0-beta.53)(rollup@4.60.2)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) react: specifier: ^19.2.3 version: 19.2.3 @@ -450,7 +450,7 @@ importers: version: 1.141.1(@tanstack/router-core@1.159.4)(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(csstype@3.2.3)(solid-js@1.9.10) '@tanstack/solid-router-ssr-query': specifier: ^1.139.10 - version: 1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(eslint@9.39.2(jiti@2.6.1))(solid-js@1.9.10)(typescript@5.9.3) + version: 1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(eslint@9.39.4(jiti@2.6.1))(solid-js@1.9.10)(typescript@5.9.3) '@tanstack/solid-start': specifier: ^1.139.10 version: 1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.4(srvx@0.11.2))(solid-js@1.9.10)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -499,7 +499,7 @@ importers: version: 24.10.3 jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) typescript: specifier: 5.9.3 version: 5.9.3 @@ -511,7 +511,7 @@ importers: version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: ^4.0.14 - version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) web-vitals: specifier: ^5.1.0 version: 5.1.0 @@ -575,7 +575,7 @@ importers: version: 5.45.10 svelte-check: specifier: ^4.2.0 - version: 4.3.4(picomatch@4.0.3)(svelte@5.45.10)(typescript@5.9.3) + version: 4.3.4(picomatch@4.0.4)(svelte@5.45.10)(typescript@5.9.3) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -639,7 +639,7 @@ importers: version: 6.0.3(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) autoprefixer: specifier: ^10.4.21 - version: 10.4.22(postcss@8.5.6) + version: 10.4.22(postcss@8.5.10) concurrently: specifier: ^9.1.2 version: 9.2.1 @@ -689,7 +689,7 @@ importers: version: 1.1.0 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) zod: specifier: ^4.2.0 version: 4.2.1 @@ -705,7 +705,7 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) zod: specifier: ^4.2.0 version: 4.2.1 @@ -721,7 +721,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -752,16 +752,16 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) tsup: specifier: ^8.5.1 - version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -783,7 +783,7 @@ importers: version: link:../ai-client '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) packages/typescript/ai-event-client: dependencies: @@ -796,7 +796,7 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) packages/typescript/ai-fal: dependencies: @@ -809,7 +809,7 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -817,8 +817,8 @@ importers: packages/typescript/ai-gemini: dependencies: '@google/genai': - specifier: ^1.46.0 - version: 1.46.0 + specifier: ^1.50.1 + version: 1.50.1 devDependencies: '@tanstack/ai': specifier: workspace:* @@ -828,7 +828,7 @@ importers: version: link:../ai-client '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.3.1 version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -847,7 +847,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -866,7 +866,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -882,7 +882,7 @@ importers: version: link:../ai '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -901,7 +901,7 @@ importers: version: link:../ai-client '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -920,7 +920,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -939,10 +939,10 @@ importers: version: 3.2.4(preact@10.28.2) '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) preact: specifier: ^10.26.9 version: 10.28.2 @@ -967,10 +967,10 @@ importers: version: 19.2.7 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) react: specifier: ^19.2.3 version: 19.2.3 @@ -1007,7 +1007,7 @@ importers: version: 19.2.7 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) react: specifier: ^19.2.3 version: 19.2.3 @@ -1035,10 +1035,10 @@ importers: version: 24.10.3 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) solid-js: specifier: ^1.9.10 version: 1.9.10 @@ -1050,7 +1050,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.14 - version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) packages/typescript/ai-solid-ui: dependencies: @@ -1078,7 +1078,7 @@ importers: version: link:../ai-solid '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) solid-js: specifier: ^1.9.10 version: 1.9.10 @@ -1106,16 +1106,16 @@ importers: version: 24.10.3 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) svelte: specifier: ^5.20.0 version: 5.45.10 svelte-check: specifier: ^4.2.0 - version: 4.3.4(picomatch@4.0.3)(svelte@5.45.10)(typescript@5.9.3) + version: 4.3.4(picomatch@4.0.4)(svelte@5.45.10)(typescript@5.9.3) typescript: specifier: 5.9.3 version: 5.9.3 @@ -1137,13 +1137,13 @@ importers: version: 24.10.3 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@vue/test-utils': specifier: ^2.4.6 version: 2.4.6 jsdom: specifier: ^27.2.0 - version: 27.3.0(postcss@8.5.6) + version: 27.3.0(postcss@8.5.10) tsdown: specifier: ^0.17.0-beta.6 version: 0.17.3(oxc-resolver@11.15.0)(publint@0.3.16)(typescript@5.9.3) @@ -1152,7 +1152,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.14 - version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vue: specifier: ^3.5.25 version: 3.5.25(typescript@5.9.3) @@ -1183,7 +1183,7 @@ importers: version: 6.0.3(vite@7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3)) '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -1208,7 +1208,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) vite: specifier: ^7.2.7 version: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -1227,7 +1227,7 @@ importers: version: 19.2.7 '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) react: specifier: ^19.2.3 version: 19.2.3 @@ -1281,7 +1281,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.14 - version: 4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) zod: specifier: ^4.2.0 version: 4.2.1 @@ -1367,7 +1367,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: 4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.0.14(vitest@4.1.5) solid-js: specifier: ^1.9.10 version: 1.9.10 @@ -1829,12 +1829,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.2': - resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -1853,12 +1847,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.2': - resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.3': resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} @@ -1877,12 +1865,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.2': - resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.3': resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} @@ -1901,12 +1883,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.2': - resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.3': resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} @@ -1925,12 +1901,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.2': - resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.3': resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} @@ -1949,12 +1919,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.2': - resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.3': resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} @@ -1973,12 +1937,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.2': - resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.3': resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} @@ -1997,12 +1955,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.2': - resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} @@ -2021,12 +1973,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.2': - resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.3': resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} @@ -2045,12 +1991,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.2': - resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.3': resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} @@ -2069,12 +2009,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.2': - resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.3': resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} @@ -2093,12 +2027,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.2': - resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.3': resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} @@ -2117,12 +2045,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.2': - resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.3': resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} @@ -2141,12 +2063,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.2': - resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.3': resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} @@ -2165,12 +2081,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.2': - resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.3': resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} @@ -2189,12 +2099,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.2': - resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.3': resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} @@ -2213,12 +2117,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.2': - resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.3': resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} @@ -2231,12 +2129,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.2': - resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} @@ -2255,12 +2147,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.2': - resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} @@ -2273,12 +2159,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.2': - resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} @@ -2297,12 +2177,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.2': - resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} @@ -2315,12 +2189,6 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.2': - resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} @@ -2339,12 +2207,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.2': - resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.3': resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} @@ -2363,12 +2225,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.2': - resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.3': resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} @@ -2387,12 +2243,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.2': - resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.3': resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} @@ -2411,12 +2261,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.2': - resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.3': resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} @@ -2467,8 +2311,8 @@ packages: resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': @@ -2490,8 +2334,8 @@ packages: '@gerrit0/mini-shiki@3.19.0': resolution: {integrity: sha512-ZSlWfLvr8Nl0T4iA3FF/8VH8HivYF82xQts2DY0tJxZd4wtXJ8AA0nmdW9lmO4hlrh3f9xNwEPtOgqETPqKwDA==} - '@google/genai@1.46.0': - resolution: {integrity: sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==} + '@google/genai@1.50.1': + resolution: {integrity: sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -2503,10 +2347,22 @@ packages: resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + '@humanfs/node@0.16.7': resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -2662,25 +2518,21 @@ packages: resolution: {integrity: sha512-K6l/qa1rUM1saFlcT/KnJfhRtLyPkpYCxWGNYaMQ3gEFozPCHYdAJUQ+sKS8kVyWt2anAWx2XkmXUaz04OB8BQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@nx/nx-linux-arm64-musl@22.1.2': resolution: {integrity: sha512-vZUAUsaop5fdcyWpYzED+hWTKOuDtwG9DNNYUlII0dZhSA8kZwmXoYmrCGeMe5nQX9tF4pNzF+oddC/E169Z6g==} cpu: [arm64] os: [linux] - libc: [musl] '@nx/nx-linux-x64-gnu@22.1.2': resolution: {integrity: sha512-+NiA5uNh1cdpk2k984NlfIxRXaO0Bu0S4qCvWWKmL/150f31qJ/eHN6rd78/Re2qKO1NDoyDZLW6jqRXIm/GgA==} cpu: [x64] os: [linux] - libc: [glibc] '@nx/nx-linux-x64-musl@22.1.2': resolution: {integrity: sha512-8O7dXems/Of/biCKeuGMh3nmbS2PNvaL8R4xQzaBl94XitzFMxVFjjoTST7y3Ksmsa5Wrbzwyh+kHOMoIMlVpA==} cpu: [x64] os: [linux] - libc: [musl] '@nx/nx-win32-arm64-msvc@22.1.2': resolution: {integrity: sha512-/Wt3kdj5BksswSWL4N8tef6B+d5r0LbdEPqZimx3AqDMC9H1YkVuwwdBWFGOh+ldj/N8adRuZKjEMQfa/oqPGg==} @@ -2777,56 +2629,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxc-minify/binding-linux-arm64-musl@0.110.0': resolution: {integrity: sha512-53GjCVY8kvymk9P6qNDh6zyblcehF5QHstq9QgCjv13ONGRnSHjeds0PxIwiihD7h295bxsWs84DN39syLPH4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxc-minify/binding-linux-ppc64-gnu@0.110.0': resolution: {integrity: sha512-li8XcN81dxbJDMBESnTgGhoiAQ+CNIdM0QGscZ4duVPjCry1RpX+5FJySFbGqG3pk4s9ZzlL/vtQtbRzZIZOzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxc-minify/binding-linux-riscv64-gnu@0.110.0': resolution: {integrity: sha512-SweKfsnLKShu6UFV8mwuj1d1wmlNoL/FlAxPUzwjEBgwiT2HQkY24KnjBH+TIA+//1O83kzmWKvvs4OuEhdIEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxc-minify/binding-linux-riscv64-musl@0.110.0': resolution: {integrity: sha512-oH8G4aFMP8XyTsEpdANC5PQyHgSeGlopHZuW1rpyYcaErg5YaK0vXjQ4EM5HVvPm+feBV24JjxgakTnZoF3aOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxc-minify/binding-linux-s390x-gnu@0.110.0': resolution: {integrity: sha512-W9na+Vza7XVUlpf8wMt4QBfH35KeTENEmnpPUq3NSlbQHz8lSlSvhAafvo43NcKvHAXV3ckD/mUf2VkqSdbklg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxc-minify/binding-linux-x64-gnu@0.110.0': resolution: {integrity: sha512-XJdA4mmmXOjJxSRgNJXsDP7Xe8h3gQhmb56hUcCrvq5d+h5UcEi2pR8rxsdIrS8QmkLuBA3eHkGK8E27D7DTgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxc-minify/binding-linux-x64-musl@0.110.0': resolution: {integrity: sha512-QqzvALuOTtSckI8x467R4GNArzYDb/yEh6aNzLoeaY1O7vfT7SPDwlOEcchaTznutpeS9Dy8gUS/AfqtUHaufw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxc-minify/binding-openharmony-arm64@0.110.0': resolution: {integrity: sha512-gAMssLs2Q3+uhLZxanh1DF+27Kaug3cf4PXb9AB7XK81DR+LVcKySXaoGYoOs20Co0fFSphd6rRzKge2qDK3dA==} @@ -2899,49 +2743,41 @@ packages: resolution: {integrity: sha512-SVjjjtMW66Mza76PBGJLqB0KKyFTBnxmtDXLJPbL6ZPGSctcXVmujz7/WAc0rb9m2oV0cHQTtVjnq6orQnI/jg==} cpu: [arm64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.15.0': resolution: {integrity: sha512-JDv2/AycPF2qgzEiDeMJCcSzKNDm3KxNg0KKWipoKEMDFqfM7LxNwwSVyAOGmrYlE4l3dg290hOMsr9xG7jv9g==} cpu: [arm64] os: [linux] - libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.15.0': resolution: {integrity: sha512-zbu9FhvBLW4KJxo7ElFvZWbSt4vP685Qc/Gyk/Ns3g2gR9qh2qWXouH8PWySy+Ko/qJ42+HJCLg+ZNcxikERfg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.15.0': resolution: {integrity: sha512-Kfleehe6B09C2qCnyIU01xLFqFXCHI4ylzkicfX/89j+gNHh9xyNdpEvit88Kq6i5tTGdavVnM6DQfOE2qNtlg==} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.15.0': resolution: {integrity: sha512-J7LPiEt27Tpm8P+qURDwNc8q45+n+mWgyys4/V6r5A8v5gDentHRGUx3iVk5NxdKhgoGulrzQocPTZVosq25Eg==} cpu: [riscv64] os: [linux] - libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.15.0': resolution: {integrity: sha512-+8/d2tAScPjVJNyqa7GPGnqleTB/XW9dZJQ2D/oIM3wpH3TG+DaFEXBbk4QFJ9K9AUGBhvQvWU2mQyhK/yYn3Q==} cpu: [s390x] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.15.0': resolution: {integrity: sha512-xtvSzH7Nr5MCZI2FKImmOdTl9kzuQ51RPyLh451tvD2qnkg3BaqI9Ox78bTk57YJhlXPuxWSOL5aZhKAc9J6qg==} cpu: [x64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.15.0': resolution: {integrity: sha512-14YL1zuXj06+/tqsuUZuzL0T425WA/I4nSVN1kBXeC5WHxem6lQ+2HGvG+crjeJEqHgZUT62YIgj88W+8E7eyg==} cpu: [x64] os: [linux] - libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.15.0': resolution: {integrity: sha512-/7Qli+1Wk93coxnrQaU8ySlICYN8HsgyIrzqjgIkQEpI//9eUeaeIHZptNl2fMvBGeXa7k2QgLbRNaBRgpnvMw==} @@ -3015,56 +2851,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxc-transform/binding-linux-arm64-musl@0.110.0': resolution: {integrity: sha512-e5JN94/oy+wevk76q+LMr+2klTTcO60uXa+Wkq558Ms7mdF2TvkKFI++d/JeiuIwJLTi/BxQ4qdT5FWcsHM/ug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxc-transform/binding-linux-ppc64-gnu@0.110.0': resolution: {integrity: sha512-Y3/Tnnz1GvDpmv8FXBIKtdZPsdZklOEPdrL6NHrN5i2u54BOkybFaDSptgWF53wOrJlTrcmAVSE6fRKK9XCM2Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxc-transform/binding-linux-riscv64-gnu@0.110.0': resolution: {integrity: sha512-Y0E35iA9/v9jlkNcP6tMJ+ZFOS0rLsWDqG6rU9z+X2R3fBFJBO9UARIK6ngx8upxk81y1TFR2CmBFhupfYdH6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxc-transform/binding-linux-riscv64-musl@0.110.0': resolution: {integrity: sha512-JOUSYFfHjBUs7xp2FHmZHb8eTYD/oEu0NklS6JgUauqnoXZHiTLPLVW2o2uVCqldnabYHcomuwI2iqVFYJNhTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxc-transform/binding-linux-s390x-gnu@0.110.0': resolution: {integrity: sha512-7blgoXF9D3Ngzb7eun23pNrHJpoV/TtE6LObwlZ3Nmb4oZ6Z+yMvBVaoW68NarbmvNGfZ95zrOjgm6cVETLYBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxc-transform/binding-linux-x64-gnu@0.110.0': resolution: {integrity: sha512-YQ2joGWCVDZVEU2cD/r/w49hVjDm/Qu1BvC/7zs8LvprzdLS/HyMXGF2oA0puw0b+AqgYaz3bhwKB2xexHyITQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxc-transform/binding-linux-x64-musl@0.110.0': resolution: {integrity: sha512-fkjr5qE632ULmNgvFXWDR/8668WxERz3tU7TQFp6JebPBneColitjSkdx6VKNVXEoMmQnOvBIGeP5tUNT384oA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxc-transform/binding-openharmony-arm64@0.110.0': resolution: {integrity: sha512-HWH9Zj+lMrdSTqFRCZsvDWMz7OnMjbdGsm3xURXWfRZpuaz0bVvyuZNDQXc4FyyhRDsemICaJbU1bgeIpUJDGw==} @@ -3124,42 +2952,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-wasm@2.3.0': resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} @@ -3288,28 +3110,24 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': resolution: {integrity: sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==} @@ -3443,6 +3261,11 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.53.3': resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} cpu: [arm64] @@ -3453,6 +3276,11 @@ packages: cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.53.3': resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} cpu: [arm64] @@ -3463,6 +3291,11 @@ packages: cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.53.3': resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} cpu: [x64] @@ -3473,6 +3306,11 @@ packages: cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-freebsd-arm64@4.53.3': resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} cpu: [arm64] @@ -3483,6 +3321,11 @@ packages: cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.53.3': resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} cpu: [x64] @@ -3493,155 +3336,206 @@ packages: cpu: [x64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.57.1': resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] - libc: [musl] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] - libc: [musl] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - libc: [musl] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] - libc: [musl] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] - libc: [musl] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] - libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] - libc: [musl] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} cpu: [x64] os: [openbsd] + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} cpu: [arm64] @@ -3652,6 +3546,11 @@ packages: cpu: [arm64] os: [openharmony] + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.53.3': resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} cpu: [arm64] @@ -3662,6 +3561,11 @@ packages: cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.53.3': resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} cpu: [ia32] @@ -3672,6 +3576,11 @@ packages: cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-x64-gnu@4.53.3': resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} cpu: [x64] @@ -3682,6 +3591,11 @@ packages: cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.53.3': resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} cpu: [x64] @@ -3692,6 +3606,11 @@ packages: cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + '@rushstack/node-core-library@5.7.0': resolution: {integrity: sha512-Ff9Cz/YlWu9ce4dmqNBZpA45AEya04XaBFIjV7xTVeEf+y/kTjEasmozqFELXlNG4ROdevss75JrrZ5WgufDkQ==} peerDependencies: @@ -3927,28 +3846,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -4768,49 +4683,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4874,17 +4781,17 @@ packages: '@vitest/browser': optional: true - '@vitest/expect@4.0.14': - resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} - '@vitest/expect@4.0.15': resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} '@vitest/expect@4.0.18': resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - '@vitest/mocker@4.0.14': - resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + + '@vitest/mocker@4.0.15': + resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -4894,8 +4801,8 @@ packages: vite: optional: true - '@vitest/mocker@4.0.15': - resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -4905,11 +4812,11 @@ packages: vite: optional: true - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true @@ -4925,8 +4832,8 @@ packages: '@vitest/pretty-format@4.0.18': resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - '@vitest/runner@4.0.14': - resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} '@vitest/runner@4.0.15': resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} @@ -4934,8 +4841,8 @@ packages: '@vitest/runner@4.0.18': resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - '@vitest/snapshot@4.0.14': - resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} '@vitest/snapshot@4.0.15': resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} @@ -4943,8 +4850,8 @@ packages: '@vitest/snapshot@4.0.18': resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - '@vitest/spy@4.0.14': - resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} '@vitest/spy@4.0.15': resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} @@ -4952,6 +4859,9 @@ packages: '@vitest/spy@4.0.18': resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/utils@4.0.14': resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} @@ -4961,6 +4871,9 @@ packages: '@vitest/utils@4.0.18': resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@volar/language-core@2.4.15': resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} @@ -5098,8 +5011,8 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} @@ -5308,6 +5221,9 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -5571,6 +5487,9 @@ packages: cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + cookie-es@2.0.1: + resolution: {integrity: sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -5911,6 +5830,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -5935,11 +5857,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.3: resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} @@ -6042,8 +5959,8 @@ packages: jiti: optional: true - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -7028,28 +6945,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -7844,6 +7757,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -7893,6 +7810,10 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -7956,8 +7877,8 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - protobufjs@7.5.4: - resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + protobufjs@7.5.5: + resolution: {integrity: sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==} engines: {node: '>=12.0.0'} proxy-addr@2.0.7: @@ -8204,6 +8125,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} @@ -8515,6 +8441,9 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -8683,10 +8612,18 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} @@ -9361,18 +9298,18 @@ packages: vite: optional: true - vitest@4.0.14: - resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + vitest@4.0.15: + resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.14 - '@vitest/browser-preview': 4.0.14 - '@vitest/browser-webdriverio': 4.0.14 - '@vitest/ui': 4.0.14 + '@vitest/browser-playwright': 4.0.15 + '@vitest/browser-preview': 4.0.15 + '@vitest/browser-webdriverio': 4.0.15 + '@vitest/ui': 4.0.15 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -9395,18 +9332,18 @@ packages: jsdom: optional: true - vitest@4.0.15: - resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.15 - '@vitest/browser-preview': 4.0.15 - '@vitest/browser-webdriverio': 4.0.15 - '@vitest/ui': 4.0.15 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -9429,20 +9366,23 @@ packages: jsdom: optional: true - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -9456,6 +9396,10 @@ packages: optional: true '@vitest/browser-webdriverio': optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true '@vitest/ui': optional: true happy-dom: @@ -10146,9 +10090,9 @@ snapshots: dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.6)': + '@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.10)': dependencies: - postcss: 8.5.6 + postcss: 8.5.10 '@csstools/css-tokenizer@3.0.4': {} @@ -10178,9 +10122,6 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/aix-ppc64@0.27.2': - optional: true - '@esbuild/aix-ppc64@0.27.3': optional: true @@ -10190,9 +10131,6 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm64@0.27.2': - optional: true - '@esbuild/android-arm64@0.27.3': optional: true @@ -10202,9 +10140,6 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-arm@0.27.2': - optional: true - '@esbuild/android-arm@0.27.3': optional: true @@ -10214,9 +10149,6 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/android-x64@0.27.2': - optional: true - '@esbuild/android-x64@0.27.3': optional: true @@ -10226,9 +10158,6 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.27.2': - optional: true - '@esbuild/darwin-arm64@0.27.3': optional: true @@ -10238,9 +10167,6 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/darwin-x64@0.27.2': - optional: true - '@esbuild/darwin-x64@0.27.3': optional: true @@ -10250,9 +10176,6 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.27.2': - optional: true - '@esbuild/freebsd-arm64@0.27.3': optional: true @@ -10262,9 +10185,6 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.27.2': - optional: true - '@esbuild/freebsd-x64@0.27.3': optional: true @@ -10274,9 +10194,6 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm64@0.27.2': - optional: true - '@esbuild/linux-arm64@0.27.3': optional: true @@ -10286,9 +10203,6 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-arm@0.27.2': - optional: true - '@esbuild/linux-arm@0.27.3': optional: true @@ -10298,9 +10212,6 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-ia32@0.27.2': - optional: true - '@esbuild/linux-ia32@0.27.3': optional: true @@ -10310,9 +10221,6 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-loong64@0.27.2': - optional: true - '@esbuild/linux-loong64@0.27.3': optional: true @@ -10322,9 +10230,6 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-mips64el@0.27.2': - optional: true - '@esbuild/linux-mips64el@0.27.3': optional: true @@ -10334,9 +10239,6 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.27.2': - optional: true - '@esbuild/linux-ppc64@0.27.3': optional: true @@ -10346,9 +10248,6 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.27.2': - optional: true - '@esbuild/linux-riscv64@0.27.3': optional: true @@ -10358,9 +10257,6 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-s390x@0.27.2': - optional: true - '@esbuild/linux-s390x@0.27.3': optional: true @@ -10370,18 +10266,12 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/linux-x64@0.27.2': - optional: true - '@esbuild/linux-x64@0.27.3': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.27.2': - optional: true - '@esbuild/netbsd-arm64@0.27.3': optional: true @@ -10391,18 +10281,12 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.27.2': - optional: true - '@esbuild/netbsd-x64@0.27.3': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.27.2': - optional: true - '@esbuild/openbsd-arm64@0.27.3': optional: true @@ -10412,18 +10296,12 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.27.2': - optional: true - '@esbuild/openbsd-x64@0.27.3': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.27.2': - optional: true - '@esbuild/openharmony-arm64@0.27.3': optional: true @@ -10433,9 +10311,6 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.27.2': - optional: true - '@esbuild/sunos-x64@0.27.3': optional: true @@ -10445,9 +10320,6 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.27.2': - optional: true - '@esbuild/win32-arm64@0.27.3': optional: true @@ -10457,9 +10329,6 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-ia32@0.27.2': - optional: true - '@esbuild/win32-ia32@0.27.3': optional: true @@ -10469,9 +10338,6 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.27.2': - optional: true - '@esbuild/win32-x64@0.27.3': optional: true @@ -10480,14 +10346,14 @@ snapshots: eslint: 9.39.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.4(jiti@2.6.1))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -10532,7 +10398,7 @@ snapshots: '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.14.0 + ajv: 6.15.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 @@ -10546,7 +10412,7 @@ snapshots: '@eslint/js@9.39.1': {} - '@eslint/js@9.39.2': {} + '@eslint/js@9.39.4': {} '@eslint/object-schema@2.1.7': {} @@ -10571,11 +10437,11 @@ snapshots: '@shikijs/types': 3.20.0 '@shikijs/vscode-textmate': 10.0.2 - '@google/genai@1.46.0': + '@google/genai@1.50.1': dependencies: google-auth-library: 10.6.2 p-retry: 4.6.2 - protobufjs: 7.5.4 + protobufjs: 7.5.5 ws: 8.20.0 transitivePeerDependencies: - bufferutil @@ -10584,11 +10450,23 @@ snapshots: '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.4.3 + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} @@ -11300,147 +11178,230 @@ snapshots: optionalDependencies: rollup: 4.57.1 + '@rollup/pluginutils@5.3.0(rollup@4.60.2)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.60.2 + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true '@rollup/rollup-android-arm-eabi@4.57.1': optional: true + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + '@rollup/rollup-android-arm64@4.53.3': optional: true '@rollup/rollup-android-arm64@4.57.1': optional: true + '@rollup/rollup-android-arm64@4.60.2': + optional: true + '@rollup/rollup-darwin-arm64@4.53.3': optional: true '@rollup/rollup-darwin-arm64@4.57.1': optional: true + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + '@rollup/rollup-darwin-x64@4.53.3': optional: true '@rollup/rollup-darwin-x64@4.57.1': optional: true + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + '@rollup/rollup-freebsd-arm64@4.53.3': optional: true '@rollup/rollup-freebsd-arm64@4.57.1': optional: true + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + '@rollup/rollup-freebsd-x64@4.53.3': optional: true '@rollup/rollup-freebsd-x64@4.57.1': optional: true + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.57.1': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.53.3': optional: true '@rollup/rollup-linux-arm-musleabihf@4.57.1': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.53.3': optional: true '@rollup/rollup-linux-arm64-gnu@4.57.1': optional: true + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + '@rollup/rollup-linux-arm64-musl@4.53.3': optional: true '@rollup/rollup-linux-arm64-musl@4.57.1': optional: true + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + '@rollup/rollup-linux-loong64-gnu@4.53.3': optional: true '@rollup/rollup-linux-loong64-gnu@4.57.1': optional: true + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + '@rollup/rollup-linux-loong64-musl@4.57.1': optional: true + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + '@rollup/rollup-linux-ppc64-gnu@4.53.3': optional: true '@rollup/rollup-linux-ppc64-gnu@4.57.1': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + '@rollup/rollup-linux-ppc64-musl@4.57.1': optional: true + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.53.3': optional: true '@rollup/rollup-linux-riscv64-gnu@4.57.1': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + '@rollup/rollup-linux-riscv64-musl@4.53.3': optional: true '@rollup/rollup-linux-riscv64-musl@4.57.1': optional: true + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.53.3': optional: true '@rollup/rollup-linux-s390x-gnu@4.57.1': optional: true + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + '@rollup/rollup-linux-x64-gnu@4.53.3': optional: true '@rollup/rollup-linux-x64-gnu@4.57.1': optional: true + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + '@rollup/rollup-linux-x64-musl@4.53.3': optional: true '@rollup/rollup-linux-x64-musl@4.57.1': optional: true + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + '@rollup/rollup-openbsd-x64@4.57.1': optional: true + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + '@rollup/rollup-openharmony-arm64@4.53.3': optional: true '@rollup/rollup-openharmony-arm64@4.57.1': optional: true + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.53.3': optional: true '@rollup/rollup-win32-arm64-msvc@4.57.1': optional: true + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.53.3': optional: true '@rollup/rollup-win32-ia32-msvc@4.57.1': optional: true + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + '@rollup/rollup-win32-x64-gnu@4.53.3': optional: true '@rollup/rollup-win32-x64-gnu@4.57.1': optional: true + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + '@rushstack/node-core-library@5.7.0(@types/node@24.10.3)': dependencies: ajv: 8.13.0 @@ -11817,7 +11778,7 @@ snapshots: '@tanstack/devtools-event-bus@0.4.1': dependencies: - ws: 8.18.3 + ws: 8.20.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -12552,13 +12513,13 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/solid-router-ssr-query@1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(eslint@9.39.2(jiti@2.6.1))(solid-js@1.9.10)(typescript@5.9.3)': + '@tanstack/solid-router-ssr-query@1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(eslint@9.39.4(jiti@2.6.1))(solid-js@1.9.10)(typescript@5.9.3)': dependencies: '@tanstack/query-core': 5.90.12 '@tanstack/router-ssr-query-core': 1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4) '@tanstack/solid-query': 5.90.15(solid-js@1.9.10) '@tanstack/solid-router': 1.141.1(solid-js@1.9.10) - eslint-plugin-solid: 0.14.5(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-solid: 0.14.5(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) solid-js: 1.9.10 transitivePeerDependencies: - '@tanstack/router-core' @@ -13101,10 +13062,10 @@ snapshots: '@tanstack/virtual-file-routes@1.154.7': {} - '@tanstack/vite-config@0.4.1(@types/node@24.10.3)(rollup@4.57.1)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@tanstack/vite-config@0.4.1(@types/node@24.10.3)(rollup@4.60.2)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - rollup-plugin-preserve-directives: 0.4.0(rollup@4.57.1) - vite-plugin-dts: 4.2.3(@types/node@24.10.3)(rollup@4.57.1)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + rollup-plugin-preserve-directives: 0.4.0(rollup@4.60.2) + vite-plugin-dts: 4.2.3(@types/node@24.10.3)(rollup@4.60.2)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-externalize-deps: 0.10.0(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: @@ -13248,6 +13209,7 @@ snapshots: '@types/node@25.5.0': dependencies: undici-types: 7.18.2 + optional: true '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: @@ -13357,13 +13319,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.4(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13540,7 +13502,7 @@ snapshots: vite: 7.2.7(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) vue: 3.5.25(typescript@5.9.3) - '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.14 @@ -13553,11 +13515,11 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.1.0 - vitest: 4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.14(vitest@4.1.5)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.14 @@ -13570,36 +13532,10 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.1.0 - vitest: 4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.1.5(@types/node@25.5.0)(@vitest/coverage-v8@4.0.14)(happy-dom@20.0.11)(jsdom@27.3.0(postcss@8.5.10))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.14(vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.14 - ast-v8-to-istanbul: 0.3.12 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magicast: 0.5.2 - obug: 2.1.1 - std-env: 3.10.0 - tinyrainbow: 3.1.0 - vitest: 4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@4.0.14': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 - chai: 6.2.2 - tinyrainbow: 3.1.0 - '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 @@ -13618,21 +13554,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.14(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 4.0.14 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - - '@vitest/mocker@4.0.14(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/expect@4.1.5': dependencies: - '@vitest/spy': 4.0.14 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 '@vitest/mocker@4.0.15(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -13650,6 +13579,14 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + '@vitest/mocker@4.1.5(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + '@vitest/pretty-format@4.0.14': dependencies: tinyrainbow: 3.1.0 @@ -13662,10 +13599,9 @@ snapshots: dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.14': + '@vitest/pretty-format@4.1.5': dependencies: - '@vitest/utils': 4.0.14 - pathe: 2.0.3 + tinyrainbow: 3.1.0 '@vitest/runner@4.0.15': dependencies: @@ -13677,10 +13613,9 @@ snapshots: '@vitest/utils': 4.0.18 pathe: 2.0.3 - '@vitest/snapshot@4.0.14': + '@vitest/runner@4.1.5': dependencies: - '@vitest/pretty-format': 4.0.14 - magic-string: 0.30.21 + '@vitest/utils': 4.1.5 pathe: 2.0.3 '@vitest/snapshot@4.0.15': @@ -13695,12 +13630,19 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.14': {} + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 '@vitest/spy@4.0.15': {} '@vitest/spy@4.0.18': {} + '@vitest/spy@4.1.5': {} + '@vitest/utils@4.0.14': dependencies: '@vitest/pretty-format': 4.0.14 @@ -13716,6 +13658,12 @@ snapshots: '@vitest/pretty-format': 4.0.18 tinyrainbow: 3.0.3 + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@volar/language-core@2.4.15': dependencies: '@volar/source-map': 2.4.15 @@ -13885,7 +13833,7 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -14004,14 +13952,14 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.22(postcss@8.5.6): + autoprefixer@10.4.22(postcss@8.5.10): dependencies: browserslist: 4.28.1 caniuse-lite: 1.0.30001760 fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.6 + postcss: 8.5.10 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -14130,6 +14078,11 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -14398,6 +14351,8 @@ snapshots: cookie-es@2.0.0: {} + cookie-es@2.0.1: {} + cookie-signature@1.2.2: {} cookie@0.6.0: {} @@ -14449,10 +14404,10 @@ snapshots: css-what@6.2.2: {} - cssstyle@5.3.4(postcss@8.5.6): + cssstyle@5.3.4(postcss@8.5.10): dependencies: '@asamuzakjp/css-color': 4.1.0 - '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) + '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.10) css-tree: 3.1.0 transitivePeerDependencies: - postcss @@ -14690,6 +14645,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.1.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -14766,35 +14723,6 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.2 - '@esbuild/android-arm': 0.27.2 - '@esbuild/android-arm64': 0.27.2 - '@esbuild/android-x64': 0.27.2 - '@esbuild/darwin-arm64': 0.27.2 - '@esbuild/darwin-x64': 0.27.2 - '@esbuild/freebsd-arm64': 0.27.2 - '@esbuild/freebsd-x64': 0.27.2 - '@esbuild/linux-arm': 0.27.2 - '@esbuild/linux-arm64': 0.27.2 - '@esbuild/linux-ia32': 0.27.2 - '@esbuild/linux-loong64': 0.27.2 - '@esbuild/linux-mips64el': 0.27.2 - '@esbuild/linux-ppc64': 0.27.2 - '@esbuild/linux-riscv64': 0.27.2 - '@esbuild/linux-s390x': 0.27.2 - '@esbuild/linux-x64': 0.27.2 - '@esbuild/netbsd-arm64': 0.27.2 - '@esbuild/netbsd-x64': 0.27.2 - '@esbuild/openbsd-arm64': 0.27.2 - '@esbuild/openbsd-x64': 0.27.2 - '@esbuild/openharmony-arm64': 0.27.2 - '@esbuild/sunos-x64': 0.27.2 - '@esbuild/win32-arm64': 0.27.2 - '@esbuild/win32-ia32': 0.27.2 - '@esbuild/win32-x64': 0.27.2 - esbuild@0.27.3: optionalDependencies: '@esbuild/aix-ppc64': 0.27.3 @@ -14885,10 +14813,10 @@ snapshots: transitivePeerDependencies: - typescript - eslint-plugin-solid@0.14.5(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-plugin-solid@0.14.5(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) estraverse: 5.3.0 is-html: 2.0.0 kebab-case: 1.0.2 @@ -14954,21 +14882,21 @@ snapshots: transitivePeerDependencies: - supports-color - eslint@9.39.2(jiti@2.6.1): + eslint@9.39.4(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.2 + '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.14.0 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -15130,6 +15058,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -15434,7 +15366,7 @@ snapshots: h3@2.0.0-beta.5(crossws@0.4.4(srvx@0.11.2)): dependencies: - cookie-es: 2.0.0 + cookie-es: 2.0.1 fetchdts: 0.1.7 rou3: 0.7.12 srvx: 0.8.16 @@ -15959,11 +15891,11 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@27.3.0(postcss@8.5.6): + jsdom@27.3.0(postcss@8.5.10): dependencies: '@acemir/cssom': 0.9.29 '@asamuzakjp/dom-selector': 6.7.6 - cssstyle: 5.3.4(postcss@8.5.6) + cssstyle: 5.3.4(postcss@8.5.10) data-urls: 6.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 @@ -15979,7 +15911,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.18.3 + ws: 8.20.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -16685,7 +16617,7 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 minimatch@5.1.6: dependencies: @@ -16744,7 +16676,7 @@ snapshots: nf3@0.3.10: {} - nitro@3.0.1-alpha.2(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.4)(rolldown@1.0.0-beta.53)(rollup@4.57.1)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): + nitro@3.0.1-alpha.2(chokidar@5.0.0)(ioredis@5.9.2)(lru-cache@11.2.4)(rolldown@1.0.0-beta.53)(rollup@4.60.2)(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: consola: 3.4.2 crossws: 0.4.4(srvx@0.10.1) @@ -16762,7 +16694,7 @@ snapshots: unstorage: 2.0.0-alpha.5(chokidar@5.0.0)(db0@0.3.4)(ioredis@5.9.2)(lru-cache@11.2.4)(ofetch@2.0.0-alpha.3) optionalDependencies: rolldown: 1.0.0-beta.53 - rollup: 4.57.1 + rollup: 4.60.2 vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@azure/app-configuration' @@ -17374,6 +17306,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.4: {} + pify@4.0.1: {} pirates@4.0.7: {} @@ -17400,17 +17334,23 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(yaml@2.8.2): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.6.1 - postcss: 8.5.6 + postcss: 8.5.10 tsx: 4.21.0 yaml: 2.8.2 postcss-value-parser@4.2.0: {} + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -17458,7 +17398,7 @@ snapshots: proto-list@1.2.4: {} - protobufjs@7.5.4: + protobufjs@7.5.5: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 @@ -17470,7 +17410,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.5.0 + '@types/node': 24.10.3 long: 5.3.2 proxy-addr@2.0.7: @@ -17740,11 +17680,11 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53 - rollup-plugin-preserve-directives@0.4.0(rollup@4.57.1): + rollup-plugin-preserve-directives@0.4.0(rollup@4.60.2): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.2) magic-string: 0.30.21 - rollup: 4.57.1 + rollup: 4.60.2 rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-beta.53)(rollup@4.57.1): dependencies: @@ -17815,6 +17755,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + rou3@0.7.12: {} router@2.2.0: @@ -18139,6 +18110,8 @@ snapshots: std-env@3.10.0: {} + std-env@4.1.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -18234,11 +18207,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.10)(typescript@5.9.3): + svelte-check@4.3.4(picomatch@4.0.4)(svelte@5.45.10)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 - fdir: 6.5.0(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 svelte: 5.45.10 @@ -18339,11 +18312,18 @@ snapshots: tinyexec@1.0.2: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyrainbow@3.0.3: {} tinyrainbow@3.1.0: {} @@ -18434,16 +18414,16 @@ snapshots: tslib@2.8.1: {} - tsup-preset-solid@2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + tsup-preset-solid@2.2.0(esbuild@0.27.3)(solid-js@1.9.10)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: esbuild-plugin-solid: 0.5.0(esbuild@0.27.3)(solid-js@1.9.10) - tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) transitivePeerDependencies: - esbuild - solid-js - supports-color - tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@25.5.0))(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) cac: 6.7.14 @@ -18454,7 +18434,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(yaml@2.8.2) resolve-from: 5.0.0 rollup: 4.57.1 source-map: 0.7.6 @@ -18464,7 +18444,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: '@microsoft/api-extractor': 7.47.7(@types/node@25.5.0) - postcss: 8.5.6 + postcss: 8.5.10 typescript: 5.9.3 transitivePeerDependencies: - jiti @@ -18474,7 +18454,7 @@ snapshots: tsx@4.21.0: dependencies: - esbuild: 0.27.2 + esbuild: 0.27.3 get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 @@ -18569,7 +18549,8 @@ snapshots: undici-types@7.16.0: {} - undici-types@7.18.2: {} + undici-types@7.18.2: + optional: true undici@7.16.0: {} @@ -18906,10 +18887,10 @@ snapshots: - xml2js - yaml - vite-plugin-dts@4.2.3(@types/node@24.10.3)(rollup@4.57.1)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-dts@4.2.3(@types/node@24.10.3)(rollup@4.60.2)(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@microsoft/api-extractor': 7.47.7(@types/node@24.10.3) - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.2) '@volar/typescript': 2.4.27 '@vue/language-core': 2.1.6(typescript@5.9.3) compare-versions: 6.1.1 @@ -19088,85 +19069,7 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - vitest@4.0.14(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.14 - '@vitest/runner': 4.0.14 - '@vitest/snapshot': 4.0.14 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.1.0 - vite: 7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.10.3 - happy-dom: 20.0.11 - jsdom: 27.3.0(postcss@8.5.6) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - vitest@4.0.14(@types/node@25.5.0)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.14 - '@vitest/runner': 4.0.14 - '@vitest/snapshot': 4.0.14 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.1.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.5.0 - happy-dom: 20.0.11 - jsdom: 27.3.0(postcss@8.5.6) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.15(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.15 '@vitest/mocker': 4.0.15(vite@7.2.7(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -19191,7 +19094,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.3 happy-dom: 20.0.11 - jsdom: 27.3.0(postcss@8.5.6) + jsdom: 27.3.0(postcss@8.5.10) transitivePeerDependencies: - jiti - less @@ -19205,7 +19108,7 @@ snapshots: - tsx - yaml - vitest@4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.18(@types/node@24.10.3)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.10))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@24.10.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -19230,7 +19133,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.3 happy-dom: 20.0.11 - jsdom: 27.3.0(postcss@8.5.6) + jsdom: 27.3.0(postcss@8.5.10) transitivePeerDependencies: - jiti - less @@ -19244,6 +19147,36 @@ snapshots: - tsx - yaml + vitest@4.1.5(@types/node@25.5.0)(@vitest/coverage-v8@4.0.14)(happy-dom@20.0.11)(jsdom@27.3.0(postcss@8.5.10))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.0 + '@vitest/coverage-v8': 4.0.14(vitest@4.1.5) + happy-dom: 20.0.11 + jsdom: 27.3.0(postcss@8.5.10) + transitivePeerDependencies: + - msw + vscode-uri@3.1.0: {} vue-component-type-helpers@2.2.12: {} From 646efc17e01e57286179f97b397dc459121e604b Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Mon, 27 Apr 2026 15:34:37 +0300 Subject: [PATCH 16/16] consolidated all session updates under updateSession method in the realtime client, added more config --- .../ai-gemini/src/realtime/adapter.ts | 2 ++ .../ai-gemini/src/realtime/client.ts | 18 ++++++++++-- .../typescript/ai-react/src/realtime-types.ts | 10 ++----- .../ai-react/src/use-realtime-chat.ts | 29 ++++--------------- 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/packages/typescript/ai-gemini/src/realtime/adapter.ts b/packages/typescript/ai-gemini/src/realtime/adapter.ts index f83dade52..5572e3e34 100644 --- a/packages/typescript/ai-gemini/src/realtime/adapter.ts +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -27,7 +27,9 @@ import type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types' * const client = new RealtimeClient({ * getToken: () => fetch('/api/realtime-token').then(r => r.json()), * adapter: geminiRealtime(), + * onGoAway: () => client.updateSession({ ... }) // Resume session with new config (available only for Gemini Live adapter) * }) + * * ``` */ export function geminiRealtime( diff --git a/packages/typescript/ai-gemini/src/realtime/client.ts b/packages/typescript/ai-gemini/src/realtime/client.ts index 7d4a73b11..be8653a5a 100644 --- a/packages/typescript/ai-gemini/src/realtime/client.ts +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -1,6 +1,6 @@ import { convertSchemaToJsonSchema } from "@tanstack/ai"; import { ActivityHandling, EndSensitivity, Modality, StartSensitivity, TurnCoverage } from "@google/genai"; -import type { ContextWindowCompressionConfig, FunctionDeclaration, FunctionResponse, LiveClientMessage, LiveServerGoAway, LiveServerMessage, LiveServerSessionResumptionUpdate, LiveServerToolCall, UsageMetadata } from "@google/genai"; +import type { ContextWindowCompressionConfig, FunctionDeclaration, FunctionResponse, LiveClientMessage, LiveServerGoAway, LiveServerMessage, LiveServerSessionResumptionUpdate, LiveServerToolCall, ThinkingConfig, UsageMetadata } from "@google/genai"; import type { AnyClientTool, RealtimeSessionConfig, RealtimeToken } from "@tanstack/ai"; import type { GeminiRealtimeModel, GeminiRealtimeVoice } from "./types"; @@ -148,11 +148,13 @@ export class GeminiLiveClient { private temperature = 1.0; private inputAudioTranscription = false; private outputAudioTranscription = false; - private contextWindowCompression: ContextWindowCompressionConfig | undefined = undefined; + private contextWindowCompression: ContextWindowCompressionConfig | undefined; private proactiveAudio = false; private enableAffectiveDialog = false; + private thinkingConfig: ThinkingConfig | undefined + private speechLanguageCode: string | undefined - private maxOutputTokens: number | undefined = undefined; + private maxOutputTokens: number | undefined; private functions: Array = []; private functionsMap = new Map(); @@ -263,6 +265,7 @@ export class GeminiLiveClient { responseModalities: this.responseModalities, temperature: this.temperature, speechConfig: { + languageCode: this.speechLanguageCode, voiceConfig: { prebuiltVoiceConfig: { voiceName: this.voiceName @@ -271,6 +274,7 @@ export class GeminiLiveClient { }, enableAffectiveDialog: this.enableAffectiveDialog, maxOutputTokens: this.maxOutputTokens, + thinkingConfig: this.thinkingConfig }, sessionResumption: { transparent: true, @@ -381,6 +385,14 @@ export class GeminiLiveClient { this.contextWindowCompression = config.modelOptions.contextWindowCompression } + if (config.modelOptions?.thinkingConfig) { + this.thinkingConfig = config.modelOptions.thinkingConfig + } + + if (config.modelOptions?.languageCode) { + this.speechLanguageCode = config.modelOptions.languageCode + } + const includeTranscription = config.outputModalities?.includes("text") || false this.inputAudioTranscription = includeTranscription this.outputAudioTranscription = includeTranscription diff --git a/packages/typescript/ai-react/src/realtime-types.ts b/packages/typescript/ai-react/src/realtime-types.ts index 74ac1c10f..e58150237 100644 --- a/packages/typescript/ai-react/src/realtime-types.ts +++ b/packages/typescript/ai-react/src/realtime-types.ts @@ -2,6 +2,7 @@ import type { AnyClientTool, RealtimeMessage, RealtimeMode, + RealtimeSessionConfig, RealtimeStatus, RealtimeToken, UsageInfo, @@ -140,12 +141,5 @@ export interface UseRealtimeChatReturn { getOutputTimeDomainData: () => Uint8Array // Session control - /** Current VAD mode */ - vadMode: 'server' | 'semantic' | 'manual' - /** Change VAD mode at runtime */ - setVADMode: (mode: 'server' | 'semantic' | 'manual') => void - /** Current max output tokens */ - maxOutputTokens: number | 'inf' - /** Change max output tokens at runtime */ - setMaxOutputTokens: (maxOutputTokens: number | 'inf') => void + updateSession: (config: RealtimeSessionConfig) => void } diff --git a/packages/typescript/ai-react/src/use-realtime-chat.ts b/packages/typescript/ai-react/src/use-realtime-chat.ts index 107e2f638..6f2dabf89 100644 --- a/packages/typescript/ai-react/src/use-realtime-chat.ts +++ b/packages/typescript/ai-react/src/use-realtime-chat.ts @@ -3,6 +3,7 @@ import { RealtimeClient } from '@tanstack/ai-client' import type { RealtimeMessage, RealtimeMode, + RealtimeSessionConfig, RealtimeStatus, } from '@tanstack/ai' import type { @@ -70,10 +71,6 @@ export function useRealtimeChat( const [error, setError] = useState(null) const [inputLevel, setInputLevel] = useState(0) const [outputLevel, setOutputLevel] = useState(0) - const [vadMode, setVADModeState] = useState<'server' | 'semantic' | 'manual'>( - options.vadMode ?? 'server', - ) - const [maxOutputTokens, setMaxOutputTokensState] = useState(options.maxOutputTokens ?? 'inf') // Refs const clientRef = useRef(null) @@ -236,21 +233,10 @@ export function useRealtimeChat( ) }, []) - // VAD mode control - const setVADMode = useCallback( - (newMode: 'server' | 'semantic' | 'manual') => { - setVADModeState(newMode) - clientRef.current?.updateSession({ vadMode: newMode }) - }, - [], - ) - - const setMaxOutputTokens = useCallback( - (newMaxOutputTokens: number | 'inf') => { - setMaxOutputTokensState(newMaxOutputTokens) - clientRef.current?.updateSession({ maxOutputTokens: newMaxOutputTokens }) - }, - [], + const updateSession = useCallback( + (config: RealtimeSessionConfig) => { + clientRef.current?.updateSession(config) + }, [] ) return { @@ -286,9 +272,6 @@ export function useRealtimeChat( getOutputTimeDomainData, // Session control - maxOutputTokens, - setMaxOutputTokens, - vadMode, - setVADMode, + updateSession, } }