{"version":3,"sources":["../../../src/voice/testing/run_result.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { ReadableStream } from 'node:stream/web';\nimport { describe, expect, it } from 'vitest';\nimport { z } from 'zod';\nimport { type ChatContext, ChatMessage, FunctionCall } from '../../llm/chat_context.js';\nimport { type ChatChunk, LLM, LLMStream } from '../../llm/llm.js';\nimport {\n  type ToolChoice,\n  ToolContext,\n  type ToolContextLike,\n  tool,\n} from '../../llm/tool_context.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../../types.js';\nimport { Agent } from '../agent.js';\nimport { performToolExecutions } from '../generation.js';\nimport { SpeechHandle } from '../speech_handle.js';\nimport { RunResult, activeMockTools, withMockTools } from './run_result.js';\n\nclass AgentA extends Agent {\n  constructor() {\n    super({ instructions: 'a' });\n  }\n}\n\nclass AgentB extends Agent {\n  constructor() {\n    super({ instructions: 'b' });\n  }\n}\n\nclass CapturingLLM extends LLM {\n  toolChoice?: ToolChoice;\n\n  constructor(private readonly toolCall: FunctionCall) {\n    super();\n  }\n\n  label(): string {\n    return 'capturing';\n  }\n\n  chat({\n    chatCtx,\n    toolCtx,\n    connOptions = DEFAULT_API_CONNECT_OPTIONS,\n    toolChoice,\n  }: Parameters<LLM['chat']>[0]): LLMStream {\n    this.toolChoice = toolChoice;\n    return new CapturingStream(this, {\n      chatCtx,\n      toolCtx,\n      connOptions,\n      toolCall: this.toolCall,\n    });\n  }\n}\n\nclass CapturingStream extends LLMStream {\n  constructor(\n    llm: LLM,\n    {\n      chatCtx,\n      toolCtx,\n      connOptions,\n      toolCall,\n    }: {\n      chatCtx: ChatContext;\n      toolCtx?: ToolContextLike;\n      connOptions: APIConnectOptions;\n      toolCall: FunctionCall;\n    },\n  ) {\n    super(llm, { chatCtx, toolCtx, connOptions });\n    this.toolCall = toolCall;\n  }\n\n  private readonly toolCall: FunctionCall;\n\n  protected async run(): Promise<void> {\n    const chunk: ChatChunk = {\n      id: 'test',\n      delta: {\n        role: 'assistant',\n        toolCalls: [this.toolCall],\n      },\n    };\n    this.queue.put(chunk);\n  }\n}\n\ndescribe('withMockTools', () => {\n  it('sets the mock registry for the given agent inside the block', () => {\n    const mock = () => 'mocked';\n\n    {\n      using _mock = withMockTools(AgentA, { tool1: mock });\n      expect(activeMockTools).toBeDefined();\n      expect(activeMockTools?.get(AgentA)?.tool1).toBe(mock);\n    }\n\n    expect(activeMockTools).toBeUndefined();\n  });\n\n  it('merges mocks across nested blocks and isolates per agent', () => {\n    const mockA = () => 'a';\n    const mockB = () => 'b';\n\n    {\n      using _mockA = withMockTools(AgentA, { toolA: mockA });\n      {\n        using _mockB = withMockTools(AgentB, { toolB: mockB });\n        expect(activeMockTools?.get(AgentA)?.toolA).toBe(mockA);\n        expect(activeMockTools?.get(AgentB)?.toolB).toBe(mockB);\n      }\n\n      expect(activeMockTools?.get(AgentA)?.toolA).toBe(mockA);\n      expect(activeMockTools?.get(AgentB)).toBeUndefined();\n    }\n  });\n\n  it('inner block for same agent overrides outer mocks', () => {\n    const outer = () => 'outer';\n    const inner = () => 'inner';\n\n    {\n      using _outer = withMockTools(AgentA, { tool1: outer });\n      {\n        using _inner = withMockTools(AgentA, { tool1: inner });\n        expect(activeMockTools?.get(AgentA)?.tool1).toBe(inner);\n      }\n      expect(activeMockTools?.get(AgentA)?.tool1).toBe(outer);\n    }\n  });\n\n  it('exposes the mock for invocation within the block', async () => {\n    using _mock = withMockTools(AgentA, { tool1: async () => 42 });\n    const mock = activeMockTools?.get(AgentA)?.tool1;\n    expect(await mock?.()).toBe(42);\n  });\n\n  it('routes performToolExecutions to the mock when set, original otherwise', async () => {\n    let realCalled = false;\n    const realTool = tool({\n      name: 'greet',\n      description: 'real',\n      parameters: z.object({ name: z.string() }),\n      execute: async ({ name }) => {\n        realCalled = true;\n        return `real:${name}`;\n      },\n    });\n\n    const toolCtx = new ToolContext([realTool]);\n    const speechHandle = SpeechHandle.create({ allowInterruptions: false });\n    const agent = new AgentA();\n\n    // Minimal AgentSession stub: performToolExecutions only reads session.currentAgent.\n    const session = { currentAgent: agent } as never;\n\n    const makeStream = (call: FunctionCall) =>\n      new ReadableStream<FunctionCall>({\n        start(controller) {\n          controller.enqueue(call);\n          controller.close();\n        },\n      });\n\n    // 1) With a mock registered: the mock runs, the real tool does not.\n    {\n      using _mock = withMockTools(AgentA, { greet: () => 'mocked' });\n      const controller = new AbortController();\n      const call = FunctionCall.create({\n        callId: 'call_1',\n        name: 'greet',\n        args: JSON.stringify({ name: 'world' }),\n      });\n      const [task, output] = performToolExecutions({\n        session,\n        speechHandle,\n        toolCtx,\n        toolCallStream: makeStream(call),\n        controller,\n      });\n      await task.result;\n      expect(realCalled).toBe(false);\n      expect(output.output[0]?.rawOutput).toBe('mocked');\n    }\n\n    // 2) Without a mock: the real tool runs.\n    const controller = new AbortController();\n    const call = FunctionCall.create({\n      callId: 'call_2',\n      name: 'greet',\n      args: JSON.stringify({ name: 'world' }),\n    });\n    const [task, output] = performToolExecutions({\n      session,\n      speechHandle,\n      toolCtx,\n      toolCallStream: makeStream(call),\n      controller,\n    });\n    await task.result;\n    expect(realCalled).toBe(true);\n    expect(output.output[0]?.rawOutput).toBe('real:world');\n  });\n\n  it('propagates thrown errors from mocks as tool errors', async () => {\n    const realTool = tool({\n      name: 'failing',\n      description: 'real',\n      parameters: z.object({}),\n      execute: async () => 'ok',\n    });\n    const toolCtx = new ToolContext([realTool]);\n    const speechHandle = SpeechHandle.create({ allowInterruptions: false });\n    const session = { currentAgent: new AgentA() } as never;\n\n    using _mock = withMockTools(AgentA, {\n      failing: () => {\n        throw new Error('test failure');\n      },\n    });\n    const controller = new AbortController();\n    const call = FunctionCall.create({\n      callId: 'call_err',\n      name: 'failing',\n      args: '{}',\n    });\n    const stream = new ReadableStream<FunctionCall>({\n      start(c) {\n        c.enqueue(call);\n        c.close();\n      },\n    });\n    const [task, output] = performToolExecutions({\n      session,\n      speechHandle,\n      toolCtx,\n      toolCallStream: stream,\n      controller,\n    });\n    await task.result;\n    expect(output.output[0]?.rawException?.message).toBe('test failure');\n    expect(output.output[0]?.toolCallOutput?.isError).toBe(true);\n  });\n});\n\ndescribe('RunResult speech handle error propagation', () => {\n  it('rejects when the last SpeechHandle completed with an error', async () => {\n    const run = new RunResult();\n    const handle = SpeechHandle.create();\n    run._watchHandle(handle);\n\n    const error = new Error('update_chat_ctx failed unexpectedly');\n    handle._markDone(error);\n    run._markDoneIfNeeded(handle);\n\n    await expect(run.wait()).rejects.toThrow('update_chat_ctx failed unexpectedly');\n  });\n\n  it('resolves when the last SpeechHandle completed without an error', async () => {\n    const run = new RunResult();\n    const handle = SpeechHandle.create();\n    run._watchHandle(handle);\n\n    handle._markDone();\n    run._markDoneIfNeeded(handle);\n\n    await expect(run.wait()).resolves.toBe(run);\n  });\n});\n\ndescribe('MessageAssert judge', () => {\n  it('uses required tool choice', async () => {\n    const llmInstance = new CapturingLLM(\n      FunctionCall.create({\n        callId: 'call_1',\n        name: 'check_intent',\n        args: JSON.stringify({ success: true, reason: 'ok' }),\n      }),\n    );\n    const result = new RunResult();\n    result.events.push({\n      type: 'message',\n      item: ChatMessage.create({ role: 'assistant', content: 'Hello there' }),\n    });\n\n    await result.expect.at(0).isMessage({ role: 'assistant' }).judge(llmInstance, {\n      intent: 'greets the user',\n    });\n\n    expect(llmInstance.toolChoice).toBe('required');\n  });\n});\n"],"mappings":"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","names":["_stack","_","_error","_hasError","call","controller","task","output"]}