{"version":3,"sources":["../../src/voice/agent_session_endpointing.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Tests for detector-aware endpointing defaults on `AgentSession`.\n *\n * Port of the seven test additions on `tests/test_agent_session.py` from\n * livekit/agents PR #4722 (commits `c72938` and `2328f3`):\n *\n * - `test_streaming_detector_uses_streaming_endpointing_defaults`\n * - `test_non_streaming_detector_uses_legacy_endpointing_defaults`\n * - `test_explicit_endpointing_overrides_streaming_default_per_key`\n * - `test_user_streaming_detector_uses_streaming_defaults`\n * - `test_deprecated_turn_detection_vad_uses_legacy_defaults`\n * - `test_agent_turn_detection_override_resolves_endpointing_per_activity`\n * - `test_runtime_endpointing_opts_survive_handoff`\n *\n * Python uses seconds; JS uses milliseconds (0.3s→300, 2.5s→2500, 0.5s→500, 3.0s→3000).\n */\nimport { describe, expect, it } from 'vitest';\nimport { TurnDetector } from '../inference/eot/detector.js';\nimport type { VADStream } from '../vad.js';\nimport { VAD as BaseVAD, VADEventType } from '../vad.js';\nimport { Agent } from './agent.js';\nimport { AgentActivity } from './agent_activity.js';\nimport { AgentSession } from './agent_session.js';\n\nclass FakeVAD extends BaseVAD {\n  label = 'FakeVAD';\n  constructor() {\n    super({ updateInterval: 32 });\n  }\n  stream(): VADStream {\n    throw new Error('not used in this test');\n  }\n}\n\ndescribe('AgentSession endpointing defaults', () => {\n  it('default session (streaming detector) uses the tighter streaming defaults', async () => {\n    const session = new AgentSession();\n    try {\n      expect(session.sessionOptions.turnHandling.endpointing.minDelay).toBe(300);\n      expect(session.sessionOptions.turnHandling.endpointing.maxDelay).toBe(2500);\n      expect(session.sessionOptions.turnHandling.endpointingOverrides).toEqual({});\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n\n  it('a non-streaming mode keeps the legacy defaults', async () => {\n    const session = new AgentSession({ turnHandling: { turnDetection: 'vad' } });\n    try {\n      expect(session.sessionOptions.turnHandling.endpointing.minDelay).toBe(500);\n      expect(session.sessionOptions.turnHandling.endpointing.maxDelay).toBe(3000);\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n\n  it('an explicit delay is honored; the unset one still gets the streaming default', async () => {\n    const session = new AgentSession({ turnHandling: { endpointing: { minDelay: 400 } } });\n    try {\n      expect(session.sessionOptions.turnHandling.endpointing.minDelay).toBe(400);\n      expect(session.sessionOptions.turnHandling.endpointing.maxDelay).toBe(2500);\n      expect(session.sessionOptions.turnHandling.endpointingOverrides).toEqual({ minDelay: 400 });\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n\n  it('a user-constructed streaming detector also triggers the streaming defaults', async () => {\n    const session = new AgentSession({\n      turnHandling: { turnDetection: new TurnDetector({ version: 'v1-mini' }) },\n    });\n    try {\n      expect(session.sessionOptions.turnHandling.endpointing.minDelay).toBe(300);\n      expect(session.sessionOptions.turnHandling.endpointing.maxDelay).toBe(2500);\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n\n  it('deprecated top-level turnDetection + no delays → legacy defaults', async () => {\n    const session = new AgentSession({ turnDetection: 'vad' });\n    try {\n      expect(session.sessionOptions.turnHandling.endpointing.minDelay).toBe(500);\n      expect(session.sessionOptions.turnHandling.endpointing.maxDelay).toBe(3000);\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n\n  it('endpointingOpts uses the activity-resolved detector, not just the session', async () => {\n    // session default → streaming detector; provide a VAD so the detector validates\n    const session = new AgentSession({ vad: new FakeVAD() });\n    try {\n      const streamingActivity = new AgentActivity(new Agent({ instructions: 'test' }), session);\n      expect(streamingActivity.endpointingOpts.minDelay).toBe(300);\n      expect(streamingActivity.endpointingOpts.maxDelay).toBe(2500);\n\n      // an agent overriding to VAD falls back to legacy defaults for this activity\n      const vadActivity = new AgentActivity(\n        new Agent({ instructions: 'test', turnDetection: 'vad' }),\n        session,\n      );\n      expect(vadActivity.endpointingOpts.minDelay).toBe(500);\n      expect(vadActivity.endpointingOpts.maxDelay).toBe(3000);\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n\n  it.each([\n    { sessionMinDelay: 200, agentMinDelay: 1000, replyHeld: true },\n    { sessionMinDelay: 1000, agentMinDelay: 200, replyHeld: false },\n  ])(\n    'reply hold-off uses agent minDelay: $agentMinDelay',\n    async ({ sessionMinDelay, agentMinDelay, replyHeld }) => {\n      const session = new AgentSession({\n        vad: new FakeVAD(),\n        turnHandling: {\n          turnDetection: 'vad',\n          endpointing: { minDelay: sessionMinDelay },\n        },\n      });\n      try {\n        const activity = new AgentActivity(\n          new Agent({\n            instructions: 'test',\n            turnHandling: { endpointing: { minDelay: agentMinDelay } },\n          }),\n          session,\n        );\n        const activityInternals = activity as unknown as {\n          userSilenceEvent: { isSet: boolean };\n        };\n        expect(activity.endpointingOpts.minDelay).toBe(agentMinDelay);\n\n        activity.onVADInferenceDone({\n          type: VADEventType.INFERENCE_DONE,\n          samplesIndex: 0,\n          timestamp: 0,\n          speechDuration: 0,\n          silenceDuration: 250,\n          frames: [],\n          probability: 0,\n          inferenceDuration: 0,\n          speaking: true,\n          rawAccumulatedSilence: 250,\n          rawAccumulatedSpeech: 0,\n        });\n\n        expect(activityInternals.userSilenceEvent.isSet).toBe(!replyHeld);\n      } finally {\n        await session.close().catch(() => {});\n      }\n    },\n  );\n\n  it('runtime updateOptions changes survive a handoff via overrides', async () => {\n    const session = new AgentSession({ vad: new FakeVAD() });\n    try {\n      session.updateOptions({\n        turnHandling: { endpointing: { mode: 'dynamic', alpha: 0.5, minDelay: 400 } },\n      });\n\n      // a fresh activity (as built on agent handoff) re-resolves from overrides\n      const activity = new AgentActivity(new Agent({ instructions: 'test' }), session);\n      expect(activity.endpointingOpts.mode).toBe('dynamic');\n      expect(activity.endpointingOpts.alpha).toBe(0.5);\n      expect(activity.endpointingOpts.minDelay).toBe(400);\n      // untouched key still gets the streaming default\n      expect(activity.endpointingOpts.maxDelay).toBe(2500);\n    } finally {\n      await session.close().catch(() => {});\n    }\n  });\n});\n"],"mappings":"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","names":["BaseVAD"]}