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/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 848c702ca..41a17798c 100644 --- a/examples/ts-react-chat/src/lib/use-realtime.ts +++ b/examples/ts-react-chat/src/lib/use-realtime.ts @@ -1,17 +1,20 @@ -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 { 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' +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 }) @@ -24,6 +27,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) { @@ -46,6 +55,7 @@ export function useRealtime({ temperature, maxOutputTokens, semanticEagerness, + voice }: { provider: Provider agentId: string @@ -53,9 +63,14 @@ export function useRealtime({ temperature?: number maxOutputTokens?: number | 'inf' semanticEagerness?: 'low' | 'medium' | 'high' + voice?: OpenAIRealtimeVoice | GeminiRealtimeVoice }) { const adapter = - provider === 'openai' ? openaiRealtime() : elevenlabsRealtime() + provider === 'openai' + ? openaiRealtime() + : provider === 'gemini' + ? geminiRealtime() + : elevenlabsRealtime() return useRealtimeChat({ getToken: () => @@ -78,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/examples/ts-react-chat/src/routes/realtime.tsx b/examples/ts-react-chat/src/routes/realtime.tsx index 3225249e2..1d2e4a527 100644 --- a/examples/ts-react-chat/src/routes/realtime.tsx +++ b/examples/ts-react-chat/src/routes/realtime.tsx @@ -13,10 +13,11 @@ 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: 'gemini', label: 'Google Gemini' }, { value: 'openai', label: 'OpenAI Realtime' }, { value: 'elevenlabs', label: 'ElevenLabs' }, ] @@ -43,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') @@ -275,7 +276,7 @@ function RealtimePage() { {/* Tools indicator */} - {provider === 'openai' && ( + {(provider === 'openai' || provider === 'gemini') && (
diff --git a/packages/typescript/ai-client/src/realtime-client.ts b/packages/typescript/ai-client/src/realtime-client.ts index 2683294c0..7a0b9d86f 100644 --- a/packages/typescript/ai-client/src/realtime-client.ts +++ b/packages/typescript/ai-client/src/realtime-client.ts @@ -100,6 +100,7 @@ export class RealtimeClient { this.clientTools.size > 0 ? Array.from(this.clientTools.values()) : undefined + this.connection = await this.options.adapter.connect( this.token, toolsList, @@ -352,6 +353,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) { @@ -472,6 +474,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 { @@ -500,12 +514,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 bffd6df34..c81f662de 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' // ============================================================================ @@ -25,7 +26,7 @@ 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: ( @@ -64,6 +65,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 @@ -148,6 +151,11 @@ export interface RealtimeClientOptions { */ semanticEagerness?: 'low' | 'medium' | 'high' + /** + * Provider-specific options + */ + modelOptions?: Record + // Callbacks onStatusChange?: (status: RealtimeStatus) => void onModeChange?: (mode: RealtimeMode) => void @@ -156,6 +164,8 @@ export interface RealtimeClientOptions { onConnect?: () => void onDisconnect?: () => void onInterrupted?: () => void + onUsage?: (usage: UsageInfo) => void + onGoAway?: (timeLeft?: string) => void } // ============================================================================ diff --git a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts index 33bc5344e..157a4623a 100644 --- a/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts +++ b/packages/typescript/ai-elevenlabs/src/realtime/adapter.ts @@ -1,9 +1,8 @@ import { Conversation } from '@11labs/client' +import { createRealtimeEventEmitter } from '@tanstack/ai' import type { AnyClientTool, AudioVisualization, - RealtimeEvent, - RealtimeEventHandler, RealtimeMessage, RealtimeMode, RealtimeSessionConfig, @@ -42,7 +41,7 @@ export function elevenlabsRealtime( token: RealtimeToken, clientToolDefs?: ReadonlyArray, ): Promise { - return createElevenLabsConnection(token, options, clientToolDefs) + return createElevenLabsConnection(token, clientToolDefs) }, } } @@ -52,10 +51,9 @@ export function elevenlabsRealtime( */ async function createElevenLabsConnection( token: RealtimeToken, - _options: ElevenLabsRealtimeOptions, clientToolDefs?: ReadonlyArray, ): Promise { - const eventHandlers = new Map>>() + const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() let conversation: Awaited< ReturnType > | null = null @@ -65,19 +63,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 +208,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-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/package.json b/packages/typescript/ai-gemini/package.json index 11656a9c1..a8a4a1953 100644 --- a/packages/typescript/ai-gemini/package.json +++ b/packages/typescript/ai-gemini/package.json @@ -40,14 +40,16 @@ "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" + "vite": "^7.3.1" } } diff --git a/packages/typescript/ai-gemini/src/index.ts b/packages/typescript/ai-gemini/src/index.ts index 05c46547a..92f9a0975 100644 --- a/packages/typescript/ai-gemini/src/index.ts +++ b/packages/typescript/ai-gemini/src/index.ts @@ -82,3 +82,16 @@ export type { GeminiDocumentMetadata, GeminiMessageMetadataByModality, } from './message-types' + +// ============================================================================ +// Realtime (Voice) Adapters +// ============================================================================ + +export { geminiRealtime, geminiRealtimeToken } from './realtime/index' + +export type { + GeminiRealtimeModel, + GeminiRealtimeOptions, + GeminiRealtimeTokenOptions, + GeminiRealtimeVoice, +} 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 new file mode 100644 index 000000000..ebe67b4e3 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/adapter.ts @@ -0,0 +1,282 @@ +import { + createRealtimeEventEmitter, +} from "@tanstack/ai" +import { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils' +import { GeminiLiveClient } from './client' +import type { LiveResponse } from './client' +import type { + AudioVisualization, + RealtimeMessage, + RealtimeMode, + RealtimeToken, +} from '@tanstack/ai' +import type { AnyClientTool, RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' +import type { GeminiRealtimeModel, 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, options.model, clientTools) + }, + } +} + +/** + * Creates a WebSocket connection to Gemini's realtime API + */ +async function createWebSocketConnection( + token: RealtimeToken, + model: GeminiRealtimeModel = 'gemini-3.1-flash-live-preview', + tools?: ReadonlyArray, +): Promise { + + const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter() + + // Current state + let currentMode: RealtimeMode = 'idle' + let currentMessageId: string | null = null + let messageIdCounter = 0 + + function generateMessageId(): string { + return `gemini-msg-${Date.now()}-${++messageIdCounter}` + } + + const client = new GeminiLiveClient(token.token, model, tools) + + let message: RealtimeMessage = { + id: '', + role: 'assistant', + timestamp: 0, + 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': + 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' }) + } + 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' }) + } + 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.functionCalls || []) { + if (tool.id && tool.name) { + emit('tool_call', { + toolCallId: tool.id, + input: tool.args, + toolName: tool.name + }) + } + } + break; + case 'turn_complete': + currentMessageId = generateMessageId() + message.id = currentMessageId + message.timestamp = Date.now() + + emit('message_complete', { message }) + message = { + id: '', + role: 'assistant', + timestamp: 0, + parts: [] + } + 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; + } + } + + await client.connect() + + const audioStreamer = new AudioStreamer(client) + const audioPlayer = new AudioPlayer() + await audioPlayer.init() + await audioStreamer.start() + + const connection: RealtimeConnection = { + async disconnect() { + audioStreamer.stop() + audioPlayer.destroy() + client.disconnect() + currentMode = 'idle' + emit('status_change', { status: 'idle' }) + }, + + async startAudioCapture() { + // Audio capture is established during connection setup + audioStreamer.startAudioCapture() + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + }, + + stopAudioCapture() { + audioStreamer.stopAudioCapture() + currentMode = 'idle' + emit('mode_change', { mode: 'idle' }) + }, + + sendText(text: string) { + client.sendTextMessage(text) + currentMode = 'thinking' + emit('mode_change', { mode: 'thinking' }) + }, + + sendImage(imageData: string, mimeType: string) { + client.sendImageMessage(imageData, mimeType) + currentMode = 'thinking' + emit('mode_change', { mode: 'thinking' }) + }, + + sendToolResult(callId: string, result: string) { + client.sendToolResponse([{ + id: callId, + response: { + result + } + }]) + }, + + updateSession(config) { + client.updateSession(config) + emit('status_change', { status: 'reconnecting' }) + }, + + updateToken(token) { + client.updateToken(token) + emit('status_change', { status: 'reconnecting' }) + }, + + interrupt() { + audioPlayer.interrupt() + currentMode = 'listening' + emit('mode_change', { mode: 'listening' }) + emit('interrupted', { messageId: currentMessageId ?? undefined }) + }, + on: realtimeEventEmitterOn, + getAudioVisualization(): AudioVisualization { + return { + get inputLevel() { + return audioStreamer.inputLevel + }, + + get outputLevel() { + return audioPlayer.outputLevel + }, + + getInputFrequencyData() { + return audioStreamer.inputFrequencyData + }, + + getOutputFrequencyData() { + return audioPlayer.outputFrequencyData + }, + + getInputTimeDomainData() { + return audioStreamer.inputTimeDomainData + }, + + getOutputTimeDomainData() { + return audioPlayer.outputTimeDomainData + }, + + get inputSampleRate() { + return audioStreamer.inputSampleRate + }, + + get outputSampleRate() { + return audioPlayer.outputSampleRate + }, + } + }, + } + + return connection; +} 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..554231022 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/client.ts @@ -0,0 +1,475 @@ +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 { 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: 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 + +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) { + console.log("🎯 πŸ› οΈ TOOL CALL response", data.toolCall); + responses.push({ type: "tool_call", data: data.toolCall, 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; + + 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 = () => { } + public onClose: () => void = () => { } + public onError: (error: Error) => 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) + }) + } + } + + get isConnected() { + return this.connected + } + + get isSetupCompelete() { + return this.setupComplete + } + + /** + * 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() + reject(new Error("Closed before connecting")) + } + + this.webSocket.onerror = (event) => { + console.error('realtime websocket error:', event) + this.connected = false + this.setupComplete = false; + const error = new Error('Connection error') + this.onError(error) + reject(error) + } + + 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 + */ + getFunctionDefinitions(): Array { + return this.functions.map(f => ({ + name: f.name, + description: f.description, + parametersJsonSchema: convertSchemaToJsonSchema(f.inputSchema), + outputJsonSchema: convertSchemaToJsonSchema(f.outputSchema), + })) + } + + 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 + }, + 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("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) { + 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.tools) { + 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 + } + + if (config.temperature) { + this.temperature = config.temperature + } + + if (config.voice) { + this.voiceName = config.voice as GeminiRealtimeVoice + } + + if (config.modelOptions?.googleGrounding) { + this.googleGrounding = config.modelOptions.googleGrounding + } + + if (config.modelOptions?.proactivity) { + this.proactiveAudio = config.modelOptions.proactivity + } + + if (config.modelOptions?.enableAffectiveDialog) { + this.enableAffectiveDialog = config.modelOptions.enableAffectiveDialog + } + + if (config.modelOptions?.contextWindowCompression) { + this.contextWindowCompression = config.modelOptions.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 + } + } + this.sendMessage(message) + } +} \ No newline at end of file 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..1291c083c --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/index.ts @@ -0,0 +1,13 @@ +// Token adapter for server-side use +export { geminiRealtimeToken } from './token' + +// Client adapter for browser use +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 new file mode 100644 index 000000000..fa39698b6 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/token.ts @@ -0,0 +1,69 @@ +import { GoogleGenAI } from '@google/genai' +import { getGeminiApiKeyFromEnv } from '../utils' +import type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai' +import type { 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({ + * // Optional: constraint model config by token + * liveConnectConstraints: { + * 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 token = await client.authTokens.create({ + config: { + uses: 1, // The default + expireTime: new Date(expireTime).toISOString(), + liveConnectConstraints: options.liveConnectConstraints, + httpOptions: { + apiVersion: 'v1alpha', + }, + }, + }) + + if (!token.name) { + throw new Error('Gemini realtime token creation failed') + } + + return { + provider: 'gemini', + token: token.name, + expiresAt: expireTime, + config: { + model: options.liveConnectConstraints?.model, + }, + } + }, + } +} 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..7ed7c2138 --- /dev/null +++ b/packages/typescript/ai-gemini/src/realtime/types.ts @@ -0,0 +1,79 @@ +import type { ContextWindowCompressionConfig, LiveConnectConstraints, ProactivityConfig, ThinkingConfig } from "@google/genai"; + +/** + * 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-3.1-flash-live-preview' + | 'gemini-2.5-flash-native-audio-preview-12-2025' + +/** + * Options for the Gemini realtime client adapter + */ +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') */ + expiresAt?: 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 + proactivity?: ProactivityConfig + enableAffectiveDialog?: boolean, + thinkingConfig?: ThinkingConfig +} \ 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..a2824e49a --- /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 only if after setup complete + if (this.client?.isSetupCompelete) { + 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 35187a5d2..7924f1e18 100644 --- a/packages/typescript/ai-openai/src/realtime/adapter.ts +++ b/packages/typescript/ai-openai/src/realtime/adapter.ts @@ -1,13 +1,12 @@ +import { createRealtimeEventEmitter } from '@tanstack/ai' import type { AnyClientTool, AudioVisualization, - RealtimeEvent, - RealtimeEventHandler, RealtimeMessage, RealtimeMode, RealtimeSessionConfig, RealtimeStatus, - RealtimeToken, + RealtimeToken } from '@tanstack/ai' import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client' import type { OpenAIRealtimeOptions } from './types' @@ -60,7 +59,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() @@ -88,19 +87,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') @@ -489,9 +475,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', @@ -587,19 +573,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-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 } /** 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 daaf6f57c..0cffbdc89 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 */ @@ -22,6 +24,7 @@ export interface RealtimeToolConfig { name: string description: string inputSchema?: Record + outputSchema?: Record } /** @@ -49,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 } /** @@ -244,6 +247,8 @@ export type RealtimeEvent = | 'message_complete' | 'interrupted' | 'error' + | 'go_away' // Event that signals that the current connection will soon be terminated + | 'usage' /** * Event payloads for realtime events @@ -261,6 +266,8 @@ export interface RealtimeEventPayloads { message_complete: { message: RealtimeMessage } interrupted: { messageId?: string } error: { error: Error } + go_away: { timeLeft?: string } + usage: UsageInfo } /** 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: