{"version":3,"sources":["../../src/realtime/realtime_api.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Behavior, FunctionResponseScheduling } from '@google/genai';\nimport { llm } from '@livekit/agents';\nimport { describe, expect, it, vi } from 'vitest';\nimport { RealtimeSession } from './realtime_api.js';\n\ntype ToolCallStatus = {\n  name: string;\n  status: 'pending' | 'continuing' | 'completed' | 'cancelled';\n  willContinueSent: boolean;\n  createdAt: number;\n};\n\ntype RealtimeSessionInternals = {\n  options: {\n    toolBehavior?: Behavior;\n    toolResponseScheduling?: FunctionResponseScheduling;\n    vertexai?: boolean;\n  };\n  currentGeneration?: {\n    functionChannel: {\n      closed: boolean;\n      write: ReturnType<typeof vi.fn>;\n    };\n  };\n  pendingToolCallIds: Set<string>;\n  toolCallStatuses: Map<string, ToolCallStatus>;\n  toolResponseCallIds: WeakMap<Record<string, unknown>, string>;\n  sendClientEvent: ReturnType<typeof vi.fn>;\n  markCurrentGenerationDone: ReturnType<typeof vi.fn>;\n  getToolResultsForRealtime(\n    ctx: llm.ChatContext,\n    vertexai: boolean,\n  ): { functionResponses: Array<Record<string, unknown>> } | undefined;\n  handleToolCall(toolCall: {\n    functionCalls?: Array<{\n      id?: string;\n      name?: string;\n      args?: Record<string, unknown>;\n    }>;\n  }): void;\n  clearPendingToolCallIdsForResponses(functionResponses: Array<Record<string, unknown>>): void;\n};\n\nconst schedulingModes = [\n  FunctionResponseScheduling.SILENT,\n  FunctionResponseScheduling.WHEN_IDLE,\n  FunctionResponseScheduling.INTERRUPT,\n];\n\nfunction createSessionForTest(\n  toolResponseScheduling: FunctionResponseScheduling,\n): RealtimeSessionInternals {\n  const session = Object.create(RealtimeSession.prototype) as RealtimeSessionInternals;\n  session.options = {\n    toolBehavior: Behavior.NON_BLOCKING,\n    toolResponseScheduling,\n    vertexai: false,\n  };\n  session.pendingToolCallIds = new Set();\n  session.toolCallStatuses = new Map();\n  session.toolResponseCallIds = new WeakMap();\n  session.sendClientEvent = vi.fn();\n  session.markCurrentGenerationDone = vi.fn();\n  session.currentGeneration = {\n    functionChannel: {\n      closed: false,\n      write: vi.fn(),\n    },\n  };\n  return session;\n}\n\ndescribe('Google Realtime non-blocking tool scheduling', () => {\n  it.each(schedulingModes)(\n    'sends %s on the immediate willContinue response',\n    (toolResponseScheduling) => {\n      const session = createSessionForTest(toolResponseScheduling);\n\n      session.handleToolCall({\n        functionCalls: [\n          {\n            id: 'call_123',\n            name: 'getWeather',\n            args: { location: 'Seattle' },\n          },\n        ],\n      });\n\n      expect(session.sendClientEvent).toHaveBeenCalledWith({\n        type: 'tool_response',\n        value: {\n          functionResponses: [\n            {\n              id: 'call_123',\n              name: 'getWeather',\n              response: {},\n              scheduling: toolResponseScheduling,\n              willContinue: true,\n            },\n          ],\n        },\n      });\n      expect(session.toolCallStatuses.get('call_123')).toMatchObject({\n        name: 'getWeather',\n        status: 'continuing',\n        willContinueSent: true,\n      });\n      expect(session.pendingToolCallIds.has('call_123')).toBe(true);\n    },\n  );\n\n  it.each(schedulingModes)(\n    'sends %s on the final non-blocking tool response',\n    (toolResponseScheduling) => {\n      const session = createSessionForTest(toolResponseScheduling);\n      session.toolCallStatuses.set('call_123', {\n        name: 'getWeather',\n        status: 'continuing',\n        willContinueSent: true,\n        createdAt: Date.now(),\n      });\n\n      const ctx = llm.ChatContext.empty();\n      ctx.insert(\n        llm.FunctionCallOutput.create({\n          callId: 'call_123',\n          name: 'getWeather',\n          output: 'The weather in Seattle is sunny today.',\n          isError: false,\n        }),\n      );\n\n      const result = session.getToolResultsForRealtime(ctx, false);\n\n      expect(result?.functionResponses).toEqual([\n        {\n          id: 'call_123',\n          name: 'getWeather',\n          response: { output: 'The weather in Seattle is sunny today.' },\n          scheduling: toolResponseScheduling,\n          willContinue: false,\n        },\n      ]);\n      expect(session.toolCallStatuses.get('call_123')).toMatchObject({\n        status: 'completed',\n        willContinueSent: true,\n      });\n    },\n  );\n\n  it('clears pending tool calls for VertexAI responses without ids', () => {\n    const session = createSessionForTest(FunctionResponseScheduling.WHEN_IDLE);\n    session.pendingToolCallIds.add('call_123');\n\n    const ctx = llm.ChatContext.empty();\n    ctx.insert(\n      llm.FunctionCallOutput.create({\n        callId: 'call_123',\n        name: 'getWeather',\n        output: 'The weather in Seattle is sunny today.',\n        isError: false,\n      }),\n    );\n\n    const result = session.getToolResultsForRealtime(ctx, true);\n\n    expect(result?.functionResponses).toEqual([\n      {\n        name: 'getWeather',\n        response: { output: 'The weather in Seattle is sunny today.' },\n        scheduling: FunctionResponseScheduling.WHEN_IDLE,\n      },\n    ]);\n\n    session.clearPendingToolCallIdsForResponses(result?.functionResponses ?? []);\n\n    expect(session.pendingToolCallIds.has('call_123')).toBe(false);\n  });\n});\n"],"mappings":"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","names":[]}