{"version":3,"sources":["../../../src/inference/interruption/interruption_session_create.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\n// Tests for the adaptive-interruption threshold negotiation contract. Ported from the Python\n// `test_interruption_session_create.py` suite.\n//\n// The feature is server-driven: the SDK only sends `threshold` in `session.create` when the user\n// explicitly overrode it, and otherwise omits the field so the server applies its fetched default.\n// These tests lock that serialization contract plus the parsing of the server's `default_threshold`\n// off `session.created` and the observability-only effective-threshold resolution.\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { initializeLogger } from '../../log.js';\nimport { MockWebSocket } from './_mock_ws.js';\nimport { AdaptiveInterruptionDetector } from './interruption_detector.js';\nimport { InterruptionStreamBase } from './interruption_stream.js';\nimport { resolveEffectiveThreshold, wsMessageSchema } from './ws_transport.js';\n\n// ---------------------------------------------------------------------------\n// Mock `ws` so the WebSocket transport can be driven deterministically.\n// ---------------------------------------------------------------------------\n\nvi.mock('ws', async () => {\n  const { MockWebSocket } = await import('./_mock_ws.js');\n  return { default: MockWebSocket, WebSocket: MockWebSocket };\n});\n\ntype MockSocket = MockWebSocket;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ninitializeLogger({ pretty: false, level: 'silent' });\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nasync function waitFor(predicate: () => boolean, timeoutMs = 2000): Promise<void> {\n  const start = performance.now();\n  while (!predicate()) {\n    if (performance.now() - start > timeoutMs) {\n      throw new Error('condition not met within timeout');\n    }\n    await sleep(5);\n  }\n}\n\nfunction createDetector(opts: { threshold?: number } = {}): AdaptiveInterruptionDetector {\n  return new AdaptiveInterruptionDetector({\n    baseUrl: 'http://localhost:9999',\n    apiKey: 'test-key',\n    apiSecret: 'test-secret',\n    ...opts,\n  });\n}\n\n/** Run the real connect path and return the parsed session.create payload the transport sent. */\nasync function captureSessionCreate(\n  detector: AdaptiveInterruptionDetector,\n): Promise<{ settings: Record<string, unknown> }> {\n  const stream = new InterruptionStreamBase(detector, {});\n  try {\n    await waitFor(() => MockWebSocket.instances.length > 0);\n    const ws = MockWebSocket.instances[MockWebSocket.instances.length - 1] as MockSocket;\n    ws.simulateOpen();\n    await waitFor(() => ws.sent.length > 0);\n    return JSON.parse(String(ws.sent[0]));\n  } finally {\n    await stream.close();\n  }\n}\n\nbeforeEach(() => {\n  MockWebSocket.instances.length = 0;\n});\n\n// ---------------------------------------------------------------------------\n// session.create threshold serialization contract\n// ---------------------------------------------------------------------------\n\ndescribe('session.create threshold', () => {\n  it('omits threshold when not given', async () => {\n    const payload = await captureSessionCreate(createDetector());\n    expect('threshold' in payload.settings).toBe(false);\n  });\n\n  it('includes threshold when overridden', async () => {\n    const payload = await captureSessionCreate(createDetector({ threshold: 0.7 }));\n    expect(payload.settings.threshold).toBe(0.7);\n  });\n});\n\n// ---------------------------------------------------------------------------\n// session.created default_threshold parsing\n// ---------------------------------------------------------------------------\n\ndescribe('session.created default_threshold parsing', () => {\n  it('parses default_threshold', () => {\n    const msg = wsMessageSchema.parse({ type: 'session.created', default_threshold: 0.42 });\n    expect(msg).toMatchObject({ type: 'session.created', default_threshold: 0.42 });\n  });\n\n  it('treats default_threshold as optional', () => {\n    const msg = wsMessageSchema.parse({ type: 'session.created' }) as {\n      default_threshold?: number | null;\n    };\n    expect(msg.default_threshold ?? null).toBeNull();\n  });\n});\n\n// ---------------------------------------------------------------------------\n// effective-threshold resolution (observability only)\n// ---------------------------------------------------------------------------\n\ndescribe('resolveEffectiveThreshold', () => {\n  it('prefers the user override', () => {\n    expect(resolveEffectiveThreshold(0.7, 0.3)).toBe(0.7);\n  });\n\n  it('falls back to the server default', () => {\n    expect(resolveEffectiveThreshold(undefined, 0.3)).toBe(0.3);\n  });\n\n  it('returns null when neither the user nor the server provides a value', () => {\n    expect(resolveEffectiveThreshold(undefined, null)).toBeNull();\n  });\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAWA,oBAAqD;AACrD,iBAAiC;AACjC,qBAA8B;AAC9B,mCAA6C;AAC7C,iCAAuC;AACvC,0BAA2D;AAM3D,iBAAG,KAAK,MAAM,YAAY;AACxB,QAAM,EAAE,eAAAA,eAAc,IAAI,MAAM,OAAO,eAAe;AACtD,SAAO,EAAE,SAASA,gBAAe,WAAWA,eAAc;AAC5D,CAAC;AAAA,IAQD,6BAAiB,EAAE,QAAQ,OAAO,OAAO,SAAS,CAAC;AAEnD,MAAM,QAAQ,CAAC,OAAe,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAEpF,eAAe,QAAQ,WAA0B,YAAY,KAAqB;AAChF,QAAM,QAAQ,YAAY,IAAI;AAC9B,SAAO,CAAC,UAAU,GAAG;AACnB,QAAI,YAAY,IAAI,IAAI,QAAQ,WAAW;AACzC,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AACA,UAAM,MAAM,CAAC;AAAA,EACf;AACF;AAEA,SAAS,eAAe,OAA+B,CAAC,GAAiC;AACvF,SAAO,IAAI,0DAA6B;AAAA,IACtC,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,GAAG;AAAA,EACL,CAAC;AACH;AAGA,eAAe,qBACb,UACgD;AAChD,QAAM,SAAS,IAAI,kDAAuB,UAAU,CAAC,CAAC;AACtD,MAAI;AACF,UAAM,QAAQ,MAAM,6BAAc,UAAU,SAAS,CAAC;AACtD,UAAM,KAAK,6BAAc,UAAU,6BAAc,UAAU,SAAS,CAAC;AACrE,OAAG,aAAa;AAChB,UAAM,QAAQ,MAAM,GAAG,KAAK,SAAS,CAAC;AACtC,WAAO,KAAK,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC;AAAA,EACtC,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;AAAA,IAEA,0BAAW,MAAM;AACf,+BAAc,UAAU,SAAS;AACnC,CAAC;AAAA,IAMD,wBAAS,4BAA4B,MAAM;AACzC,wBAAG,kCAAkC,YAAY;AAC/C,UAAM,UAAU,MAAM,qBAAqB,eAAe,CAAC;AAC3D,8BAAO,eAAe,QAAQ,QAAQ,EAAE,KAAK,KAAK;AAAA,EACpD,CAAC;AAED,wBAAG,sCAAsC,YAAY;AACnD,UAAM,UAAU,MAAM,qBAAqB,eAAe,EAAE,WAAW,IAAI,CAAC,CAAC;AAC7E,8BAAO,QAAQ,SAAS,SAAS,EAAE,KAAK,GAAG;AAAA,EAC7C,CAAC;AACH,CAAC;AAAA,IAMD,wBAAS,6CAA6C,MAAM;AAC1D,wBAAG,4BAA4B,MAAM;AACnC,UAAM,MAAM,oCAAgB,MAAM,EAAE,MAAM,mBAAmB,mBAAmB,KAAK,CAAC;AACtF,8BAAO,GAAG,EAAE,cAAc,EAAE,MAAM,mBAAmB,mBAAmB,KAAK,CAAC;AAAA,EAChF,CAAC;AAED,wBAAG,wCAAwC,MAAM;AAC/C,UAAM,MAAM,oCAAgB,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG7D,8BAAO,IAAI,qBAAqB,IAAI,EAAE,SAAS;AAAA,EACjD,CAAC;AACH,CAAC;AAAA,IAMD,wBAAS,6BAA6B,MAAM;AAC1C,wBAAG,6BAA6B,MAAM;AACpC,kCAAO,+CAA0B,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG;AAAA,EACtD,CAAC;AAED,wBAAG,oCAAoC,MAAM;AAC3C,kCAAO,+CAA0B,QAAW,GAAG,CAAC,EAAE,KAAK,GAAG;AAAA,EAC5D,CAAC;AAED,wBAAG,sEAAsE,MAAM;AAC7E,kCAAO,+CAA0B,QAAW,IAAI,CAAC,EAAE,SAAS;AAAA,EAC9D,CAAC;AACH,CAAC;","names":["MockWebSocket"]}