@livekit/agents 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exceptions.cjs.map +1 -1
- package/dist/_exceptions.d.ts.map +1 -1
- package/dist/_exceptions.js.map +1 -1
- package/dist/beta/workflows/task_group.cjs +7 -4
- package/dist/beta/workflows/task_group.cjs.map +1 -1
- package/dist/beta/workflows/task_group.d.ts.map +1 -1
- package/dist/beta/workflows/task_group.js +7 -4
- package/dist/beta/workflows/task_group.js.map +1 -1
- package/dist/inference/interruption/http_transport.cjs.map +1 -1
- package/dist/inference/interruption/http_transport.d.cts +3 -1
- package/dist/inference/interruption/http_transport.d.ts +3 -1
- package/dist/inference/interruption/http_transport.d.ts.map +1 -1
- package/dist/inference/interruption/http_transport.js.map +1 -1
- package/dist/inference/interruption/ws_transport.cjs +37 -32
- package/dist/inference/interruption/ws_transport.cjs.map +1 -1
- package/dist/inference/interruption/ws_transport.d.ts.map +1 -1
- package/dist/inference/interruption/ws_transport.js +37 -32
- package/dist/inference/interruption/ws_transport.js.map +1 -1
- package/dist/inference/tts.cjs.map +1 -1
- package/dist/inference/tts.d.cts +42 -4
- package/dist/inference/tts.d.ts +42 -4
- package/dist/inference/tts.d.ts.map +1 -1
- package/dist/inference/tts.js.map +1 -1
- package/dist/inference/tts.test.cjs +72 -0
- package/dist/inference/tts.test.cjs.map +1 -1
- package/dist/inference/tts.test.js +72 -0
- package/dist/inference/tts.test.js.map +1 -1
- package/dist/llm/chat_context.cjs +102 -31
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +102 -31
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +123 -5
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +123 -5
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/fallback_adapter.cjs +2 -0
- package/dist/llm/fallback_adapter.cjs.map +1 -1
- package/dist/llm/fallback_adapter.d.ts.map +1 -1
- package/dist/llm/fallback_adapter.js +2 -0
- package/dist/llm/fallback_adapter.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +2 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/utils.cjs +89 -0
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +8 -0
- package/dist/llm/utils.d.ts +8 -0
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +88 -0
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/utils.test.cjs +90 -0
- package/dist/llm/utils.test.cjs.map +1 -1
- package/dist/llm/utils.test.js +98 -2
- package/dist/llm/utils.test.js.map +1 -1
- package/dist/stt/stt.cjs +8 -0
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.cts +8 -0
- package/dist/stt/stt.d.ts +8 -0
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +8 -0
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/fallback_adapter.cjs +6 -0
- package/dist/tts/fallback_adapter.cjs.map +1 -1
- package/dist/tts/fallback_adapter.d.ts.map +1 -1
- package/dist/tts/fallback_adapter.js +6 -0
- package/dist/tts/fallback_adapter.js.map +1 -1
- package/dist/typed_promise.cjs +48 -0
- package/dist/typed_promise.cjs.map +1 -0
- package/dist/typed_promise.d.cts +24 -0
- package/dist/typed_promise.d.ts +24 -0
- package/dist/typed_promise.d.ts.map +1 -0
- package/dist/typed_promise.js +28 -0
- package/dist/typed_promise.js.map +1 -0
- package/dist/utils.cjs +2 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.js +2 -2
- package/dist/utils.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -2
- package/src/_exceptions.ts +5 -0
- package/src/beta/workflows/task_group.ts +14 -5
- package/src/inference/interruption/http_transport.ts +2 -1
- package/src/inference/interruption/ws_transport.ts +44 -34
- package/src/inference/tts.test.ts +87 -0
- package/src/inference/tts.ts +46 -6
- package/src/llm/chat_context.test.ts +137 -5
- package/src/llm/chat_context.ts +119 -38
- package/src/llm/fallback_adapter.ts +5 -2
- package/src/llm/index.ts +2 -0
- package/src/llm/utils.test.ts +103 -2
- package/src/llm/utils.ts +128 -0
- package/src/stt/stt.ts +9 -1
- package/src/tts/fallback_adapter.ts +9 -2
- package/src/typed_promise.ts +67 -0
- package/src/utils.ts +2 -2
package/dist/_exceptions.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_exceptions.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Raised when accepting a job but not receiving an assignment within the specified timeout.\n * The server may have chosen another worker to handle this job.\n */\nexport class AssignmentTimeoutError extends Error {\n constructor(message = 'Assignment timeout occurred') {\n super(message);\n this.name = 'AssignmentTimeoutError';\n Error.captureStackTrace(this, AssignmentTimeoutError);\n }\n}\n\n/**\n * Interface for API error options\n */\ninterface APIErrorOptions {\n body?: object | null;\n retryable?: boolean;\n}\n\nconst API_ERROR_SYMBOL = Symbol('APIError');\n\n/**\n * Raised when an API request failed.\n * This is used on our TTS/STT/LLM plugins.\n */\nexport class APIError extends Error {\n readonly body: object | null;\n readonly retryable: boolean;\n\n constructor(message: string, { body = null, retryable = true }: APIErrorOptions = {}) {\n super(message);\n this.name = 'APIError';\n\n this.body = body;\n this.retryable = retryable;\n Error.captureStackTrace(this, APIError);\n Object.defineProperty(this, API_ERROR_SYMBOL, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n\n toString(): string {\n return `${this.message} (body=${JSON.stringify(this.body)}, retryable=${this.retryable})`;\n }\n}\n\n/**\n * Interface for API status error options\n */\ninterface APIStatusErrorOptions extends APIErrorOptions {\n statusCode?: number;\n requestId?: string | null;\n}\n\n/**\n * Raised when an API response has a status code of 4xx or 5xx.\n */\nexport class APIStatusError extends APIError {\n readonly statusCode: number;\n readonly requestId: string | null;\n\n constructor({\n message = 'API error.',\n options = {},\n }: {\n message?: string;\n options?: APIStatusErrorOptions;\n }) {\n const statusCode = options.statusCode ?? -1;\n // 4xx errors are not retryable\n const isRetryable = options.retryable ?? !(statusCode >= 400 && statusCode < 500);\n\n super(message, { body: options.body, retryable: isRetryable });\n this.name = 'APIStatusError';\n\n this.statusCode = statusCode;\n this.requestId = options.requestId ?? null;\n Error.captureStackTrace(this, APIStatusError);\n }\n\n toString(): string {\n return (\n `${this.message} ` +\n `(statusCode=${this.statusCode}, ` +\n `requestId=${this.requestId}, ` +\n `body=${JSON.stringify(this.body)}, ` +\n `retryable=${this.retryable})`\n );\n }\n}\n\n/**\n * Raised when an API request failed due to a connection error.\n */\nexport class APIConnectionError extends APIError {\n constructor({\n message = 'Connection error.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n super(message, { body: null, retryable: options.retryable ?? true });\n this.name = 'APIConnectionError';\n Error.captureStackTrace(this, APIConnectionError);\n }\n}\n\n/**\n * Raised when an API request timed out.\n */\nexport class APITimeoutError extends APIConnectionError {\n constructor({\n message = 'Request timed out.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n const retryable = options?.retryable ?? true;\n\n super({ message, options: { retryable } });\n this.name = 'APITimeoutError';\n Error.captureStackTrace(this, APITimeoutError);\n }\n}\n\nexport function isAPIError(error: unknown): error is APIError {\n return error !== null && typeof error === 'object' && API_ERROR_SYMBOL in error;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../src/_exceptions.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n// FIXME(@livekit/agents): Consider migrating error signaling away from Error subclasses\n// toward an AbortSignal-based pattern for cancellation/error propagation. Error subclasses\n// work well for synchronous throw/catch, but AbortSignal integrates better with async\n// streams, fetch, and the broader Web API ecosystem.\n\n/**\n * Raised when accepting a job but not receiving an assignment within the specified timeout.\n * The server may have chosen another worker to handle this job.\n */\nexport class AssignmentTimeoutError extends Error {\n constructor(message = 'Assignment timeout occurred') {\n super(message);\n this.name = 'AssignmentTimeoutError';\n Error.captureStackTrace(this, AssignmentTimeoutError);\n }\n}\n\n/**\n * Interface for API error options\n */\ninterface APIErrorOptions {\n body?: object | null;\n retryable?: boolean;\n}\n\nconst API_ERROR_SYMBOL = Symbol('APIError');\n\n/**\n * Raised when an API request failed.\n * This is used on our TTS/STT/LLM plugins.\n */\nexport class APIError extends Error {\n readonly body: object | null;\n readonly retryable: boolean;\n\n constructor(message: string, { body = null, retryable = true }: APIErrorOptions = {}) {\n super(message);\n this.name = 'APIError';\n\n this.body = body;\n this.retryable = retryable;\n Error.captureStackTrace(this, APIError);\n Object.defineProperty(this, API_ERROR_SYMBOL, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n\n toString(): string {\n return `${this.message} (body=${JSON.stringify(this.body)}, retryable=${this.retryable})`;\n }\n}\n\n/**\n * Interface for API status error options\n */\ninterface APIStatusErrorOptions extends APIErrorOptions {\n statusCode?: number;\n requestId?: string | null;\n}\n\n/**\n * Raised when an API response has a status code of 4xx or 5xx.\n */\nexport class APIStatusError extends APIError {\n readonly statusCode: number;\n readonly requestId: string | null;\n\n constructor({\n message = 'API error.',\n options = {},\n }: {\n message?: string;\n options?: APIStatusErrorOptions;\n }) {\n const statusCode = options.statusCode ?? -1;\n // 4xx errors are not retryable\n const isRetryable = options.retryable ?? !(statusCode >= 400 && statusCode < 500);\n\n super(message, { body: options.body, retryable: isRetryable });\n this.name = 'APIStatusError';\n\n this.statusCode = statusCode;\n this.requestId = options.requestId ?? null;\n Error.captureStackTrace(this, APIStatusError);\n }\n\n toString(): string {\n return (\n `${this.message} ` +\n `(statusCode=${this.statusCode}, ` +\n `requestId=${this.requestId}, ` +\n `body=${JSON.stringify(this.body)}, ` +\n `retryable=${this.retryable})`\n );\n }\n}\n\n/**\n * Raised when an API request failed due to a connection error.\n */\nexport class APIConnectionError extends APIError {\n constructor({\n message = 'Connection error.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n super(message, { body: null, retryable: options.retryable ?? true });\n this.name = 'APIConnectionError';\n Error.captureStackTrace(this, APIConnectionError);\n }\n}\n\n/**\n * Raised when an API request timed out.\n */\nexport class APITimeoutError extends APIConnectionError {\n constructor({\n message = 'Request timed out.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n const retryable = options?.retryable ?? true;\n\n super({ message, options: { retryable } });\n this.name = 'APITimeoutError';\n Error.captureStackTrace(this, APITimeoutError);\n }\n}\n\nexport function isAPIError(error: unknown): error is APIError {\n return error !== null && typeof error === 'object' && API_ERROR_SYMBOL in error;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,MAAM,+BAA+B,MAAM;AAAA,EAChD,YAAY,UAAU,+BAA+B;AACnD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,sBAAsB;AAAA,EACtD;AACF;AAUA,MAAM,mBAAmB,OAAO,UAAU;AAMnC,MAAM,iBAAiB,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,EAAE,OAAO,MAAM,YAAY,KAAK,IAAqB,CAAC,GAAG;AACpF,UAAM,OAAO;AACb,SAAK,OAAO;AAEZ,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,UAAM,kBAAkB,MAAM,QAAQ;AACtC,WAAO,eAAe,MAAM,kBAAkB;AAAA,MAC5C,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEA,WAAmB;AACjB,WAAO,GAAG,KAAK,OAAO,UAAU,KAAK,UAAU,KAAK,IAAI,CAAC,eAAe,KAAK,SAAS;AAAA,EACxF;AACF;AAaO,MAAM,uBAAuB,SAAS;AAAA,EAClC;AAAA,EACA;AAAA,EAET,YAAY;AAAA,IACV,UAAU;AAAA,IACV,UAAU,CAAC;AAAA,EACb,GAGG;AACD,UAAM,aAAa,QAAQ,cAAc;AAEzC,UAAM,cAAc,QAAQ,aAAa,EAAE,cAAc,OAAO,aAAa;AAE7E,UAAM,SAAS,EAAE,MAAM,QAAQ,MAAM,WAAW,YAAY,CAAC;AAC7D,SAAK,OAAO;AAEZ,SAAK,aAAa;AAClB,SAAK,YAAY,QAAQ,aAAa;AACtC,UAAM,kBAAkB,MAAM,cAAc;AAAA,EAC9C;AAAA,EAEA,WAAmB;AACjB,WACE,GAAG,KAAK,OAAO,gBACA,KAAK,UAAU,eACjB,KAAK,SAAS,UACnB,KAAK,UAAU,KAAK,IAAI,CAAC,eACpB,KAAK,SAAS;AAAA,EAE/B;AACF;AAKO,MAAM,2BAA2B,SAAS;AAAA,EAC/C,YAAY;AAAA,IACV,UAAU;AAAA,IACV,UAAU,CAAC;AAAA,EACb,GAGG;AACD,UAAM,SAAS,EAAE,MAAM,MAAM,WAAW,QAAQ,aAAa,KAAK,CAAC;AACnE,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,kBAAkB;AAAA,EAClD;AACF;AAKO,MAAM,wBAAwB,mBAAmB;AAAA,EACtD,YAAY;AAAA,IACV,UAAU;AAAA,IACV,UAAU,CAAC;AAAA,EACb,GAGG;AACD,UAAM,aAAY,mCAAS,cAAa;AAExC,UAAM,EAAE,SAAS,SAAS,EAAE,UAAU,EAAE,CAAC;AACzC,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,eAAe;AAAA,EAC/C;AACF;AAEO,SAAS,WAAW,OAAmC;AAC5D,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,oBAAoB;AAC5E;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_exceptions.d.ts","sourceRoot":"","sources":["../src/_exceptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_exceptions.d.ts","sourceRoot":"","sources":["../src/_exceptions.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,SAAgC;CAKpD;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAID;;;GAGG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,EAAE,IAAW,EAAE,SAAgB,EAAE,GAAE,eAAoB;IAepF,QAAQ,IAAI,MAAM;CAGnB;AAED;;GAEG;AACH,UAAU,qBAAsB,SAAQ,eAAe;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEtB,EACV,OAAsB,EACtB,OAAY,GACb,EAAE;QACD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,qBAAqB,CAAC;KACjC;IAaD,QAAQ,IAAI,MAAM;CASnB;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;gBAClC,EACV,OAA6B,EAC7B,OAAY,GACb,EAAE;QACD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,eAAe,CAAC;KAC3B;CAKF;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,kBAAkB;gBACzC,EACV,OAA8B,EAC9B,OAAY,GACb,EAAE;QACD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,eAAe,CAAC;KAC3B;CAOF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
|
package/dist/_exceptions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_exceptions.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Raised when accepting a job but not receiving an assignment within the specified timeout.\n * The server may have chosen another worker to handle this job.\n */\nexport class AssignmentTimeoutError extends Error {\n constructor(message = 'Assignment timeout occurred') {\n super(message);\n this.name = 'AssignmentTimeoutError';\n Error.captureStackTrace(this, AssignmentTimeoutError);\n }\n}\n\n/**\n * Interface for API error options\n */\ninterface APIErrorOptions {\n body?: object | null;\n retryable?: boolean;\n}\n\nconst API_ERROR_SYMBOL = Symbol('APIError');\n\n/**\n * Raised when an API request failed.\n * This is used on our TTS/STT/LLM plugins.\n */\nexport class APIError extends Error {\n readonly body: object | null;\n readonly retryable: boolean;\n\n constructor(message: string, { body = null, retryable = true }: APIErrorOptions = {}) {\n super(message);\n this.name = 'APIError';\n\n this.body = body;\n this.retryable = retryable;\n Error.captureStackTrace(this, APIError);\n Object.defineProperty(this, API_ERROR_SYMBOL, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n\n toString(): string {\n return `${this.message} (body=${JSON.stringify(this.body)}, retryable=${this.retryable})`;\n }\n}\n\n/**\n * Interface for API status error options\n */\ninterface APIStatusErrorOptions extends APIErrorOptions {\n statusCode?: number;\n requestId?: string | null;\n}\n\n/**\n * Raised when an API response has a status code of 4xx or 5xx.\n */\nexport class APIStatusError extends APIError {\n readonly statusCode: number;\n readonly requestId: string | null;\n\n constructor({\n message = 'API error.',\n options = {},\n }: {\n message?: string;\n options?: APIStatusErrorOptions;\n }) {\n const statusCode = options.statusCode ?? -1;\n // 4xx errors are not retryable\n const isRetryable = options.retryable ?? !(statusCode >= 400 && statusCode < 500);\n\n super(message, { body: options.body, retryable: isRetryable });\n this.name = 'APIStatusError';\n\n this.statusCode = statusCode;\n this.requestId = options.requestId ?? null;\n Error.captureStackTrace(this, APIStatusError);\n }\n\n toString(): string {\n return (\n `${this.message} ` +\n `(statusCode=${this.statusCode}, ` +\n `requestId=${this.requestId}, ` +\n `body=${JSON.stringify(this.body)}, ` +\n `retryable=${this.retryable})`\n );\n }\n}\n\n/**\n * Raised when an API request failed due to a connection error.\n */\nexport class APIConnectionError extends APIError {\n constructor({\n message = 'Connection error.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n super(message, { body: null, retryable: options.retryable ?? true });\n this.name = 'APIConnectionError';\n Error.captureStackTrace(this, APIConnectionError);\n }\n}\n\n/**\n * Raised when an API request timed out.\n */\nexport class APITimeoutError extends APIConnectionError {\n constructor({\n message = 'Request timed out.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n const retryable = options?.retryable ?? true;\n\n super({ message, options: { retryable } });\n this.name = 'APITimeoutError';\n Error.captureStackTrace(this, APITimeoutError);\n }\n}\n\nexport function isAPIError(error: unknown): error is APIError {\n return error !== null && typeof error === 'object' && API_ERROR_SYMBOL in error;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/_exceptions.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n// FIXME(@livekit/agents): Consider migrating error signaling away from Error subclasses\n// toward an AbortSignal-based pattern for cancellation/error propagation. Error subclasses\n// work well for synchronous throw/catch, but AbortSignal integrates better with async\n// streams, fetch, and the broader Web API ecosystem.\n\n/**\n * Raised when accepting a job but not receiving an assignment within the specified timeout.\n * The server may have chosen another worker to handle this job.\n */\nexport class AssignmentTimeoutError extends Error {\n constructor(message = 'Assignment timeout occurred') {\n super(message);\n this.name = 'AssignmentTimeoutError';\n Error.captureStackTrace(this, AssignmentTimeoutError);\n }\n}\n\n/**\n * Interface for API error options\n */\ninterface APIErrorOptions {\n body?: object | null;\n retryable?: boolean;\n}\n\nconst API_ERROR_SYMBOL = Symbol('APIError');\n\n/**\n * Raised when an API request failed.\n * This is used on our TTS/STT/LLM plugins.\n */\nexport class APIError extends Error {\n readonly body: object | null;\n readonly retryable: boolean;\n\n constructor(message: string, { body = null, retryable = true }: APIErrorOptions = {}) {\n super(message);\n this.name = 'APIError';\n\n this.body = body;\n this.retryable = retryable;\n Error.captureStackTrace(this, APIError);\n Object.defineProperty(this, API_ERROR_SYMBOL, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n\n toString(): string {\n return `${this.message} (body=${JSON.stringify(this.body)}, retryable=${this.retryable})`;\n }\n}\n\n/**\n * Interface for API status error options\n */\ninterface APIStatusErrorOptions extends APIErrorOptions {\n statusCode?: number;\n requestId?: string | null;\n}\n\n/**\n * Raised when an API response has a status code of 4xx or 5xx.\n */\nexport class APIStatusError extends APIError {\n readonly statusCode: number;\n readonly requestId: string | null;\n\n constructor({\n message = 'API error.',\n options = {},\n }: {\n message?: string;\n options?: APIStatusErrorOptions;\n }) {\n const statusCode = options.statusCode ?? -1;\n // 4xx errors are not retryable\n const isRetryable = options.retryable ?? !(statusCode >= 400 && statusCode < 500);\n\n super(message, { body: options.body, retryable: isRetryable });\n this.name = 'APIStatusError';\n\n this.statusCode = statusCode;\n this.requestId = options.requestId ?? null;\n Error.captureStackTrace(this, APIStatusError);\n }\n\n toString(): string {\n return (\n `${this.message} ` +\n `(statusCode=${this.statusCode}, ` +\n `requestId=${this.requestId}, ` +\n `body=${JSON.stringify(this.body)}, ` +\n `retryable=${this.retryable})`\n );\n }\n}\n\n/**\n * Raised when an API request failed due to a connection error.\n */\nexport class APIConnectionError extends APIError {\n constructor({\n message = 'Connection error.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n super(message, { body: null, retryable: options.retryable ?? true });\n this.name = 'APIConnectionError';\n Error.captureStackTrace(this, APIConnectionError);\n }\n}\n\n/**\n * Raised when an API request timed out.\n */\nexport class APITimeoutError extends APIConnectionError {\n constructor({\n message = 'Request timed out.',\n options = {},\n }: {\n message?: string;\n options?: APIErrorOptions;\n }) {\n const retryable = options?.retryable ?? true;\n\n super({ message, options: { retryable } });\n this.name = 'APITimeoutError';\n Error.captureStackTrace(this, APITimeoutError);\n }\n}\n\nexport function isAPIError(error: unknown): error is APIError {\n return error !== null && typeof error === 'object' && API_ERROR_SYMBOL in error;\n}\n"],"mappings":"AAYO,MAAM,+BAA+B,MAAM;AAAA,EAChD,YAAY,UAAU,+BAA+B;AACnD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,sBAAsB;AAAA,EACtD;AACF;AAUA,MAAM,mBAAmB,OAAO,UAAU;AAMnC,MAAM,iBAAiB,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,EAAE,OAAO,MAAM,YAAY,KAAK,IAAqB,CAAC,GAAG;AACpF,UAAM,OAAO;AACb,SAAK,OAAO;AAEZ,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,UAAM,kBAAkB,MAAM,QAAQ;AACtC,WAAO,eAAe,MAAM,kBAAkB;AAAA,MAC5C,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEA,WAAmB;AACjB,WAAO,GAAG,KAAK,OAAO,UAAU,KAAK,UAAU,KAAK,IAAI,CAAC,eAAe,KAAK,SAAS;AAAA,EACxF;AACF;AAaO,MAAM,uBAAuB,SAAS;AAAA,EAClC;AAAA,EACA;AAAA,EAET,YAAY;AAAA,IACV,UAAU;AAAA,IACV,UAAU,CAAC;AAAA,EACb,GAGG;AACD,UAAM,aAAa,QAAQ,cAAc;AAEzC,UAAM,cAAc,QAAQ,aAAa,EAAE,cAAc,OAAO,aAAa;AAE7E,UAAM,SAAS,EAAE,MAAM,QAAQ,MAAM,WAAW,YAAY,CAAC;AAC7D,SAAK,OAAO;AAEZ,SAAK,aAAa;AAClB,SAAK,YAAY,QAAQ,aAAa;AACtC,UAAM,kBAAkB,MAAM,cAAc;AAAA,EAC9C;AAAA,EAEA,WAAmB;AACjB,WACE,GAAG,KAAK,OAAO,gBACA,KAAK,UAAU,eACjB,KAAK,SAAS,UACnB,KAAK,UAAU,KAAK,IAAI,CAAC,eACpB,KAAK,SAAS;AAAA,EAE/B;AACF;AAKO,MAAM,2BAA2B,SAAS;AAAA,EAC/C,YAAY;AAAA,IACV,UAAU;AAAA,IACV,UAAU,CAAC;AAAA,EACb,GAGG;AACD,UAAM,SAAS,EAAE,MAAM,MAAM,WAAW,QAAQ,aAAa,KAAK,CAAC;AACnE,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,kBAAkB;AAAA,EAClD;AACF;AAKO,MAAM,wBAAwB,mBAAmB;AAAA,EACtD,YAAY;AAAA,IACV,UAAU;AAAA,IACV,UAAU,CAAC;AAAA,EACb,GAGG;AACD,UAAM,aAAY,mCAAS,cAAa;AAExC,UAAM,EAAE,SAAS,SAAS,EAAE,UAAU,EAAE,CAAC;AACzC,SAAK,OAAO;AACZ,UAAM,kBAAkB,MAAM,eAAe;AAAA,EAC/C;AACF;AAEO,SAAS,WAAW,OAAmC;AAC5D,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,oBAAoB;AAC5E;","names":[]}
|
|
@@ -68,6 +68,9 @@ class TaskGroup extends import_agent.AgentTask {
|
|
|
68
68
|
try {
|
|
69
69
|
this._visitedTasks.add(taskId);
|
|
70
70
|
const res = await this._currentTask.run();
|
|
71
|
+
this._chatCtx.merge(this._currentTask.chatCtx.copy(), {
|
|
72
|
+
excludeInstructions: true
|
|
73
|
+
});
|
|
71
74
|
taskResults[taskId] = res;
|
|
72
75
|
if (this._taskCompletedCallback) {
|
|
73
76
|
await this._taskCompletedCallback({
|
|
@@ -100,10 +103,10 @@ class TaskGroup extends import_agent.AgentTask {
|
|
|
100
103
|
throw new Error("summarizeChatCtx requires a standard LLM on the session");
|
|
101
104
|
}
|
|
102
105
|
const ctxToSummarize = this._chatCtx.copy({
|
|
103
|
-
excludeInstructions:
|
|
104
|
-
|
|
105
|
-
excludeEmptyMessage:
|
|
106
|
-
|
|
106
|
+
excludeInstructions: false,
|
|
107
|
+
excludeFunctionCall: false,
|
|
108
|
+
excludeEmptyMessage: false,
|
|
109
|
+
excludeHandoff: false
|
|
107
110
|
});
|
|
108
111
|
const summarizedChatCtx = await ctxToSummarize._summarize(sessionLlm, {
|
|
109
112
|
keepLastTurns: 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/beta/workflows/task_group.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { z } from 'zod';\nimport type { ChatContext } from '../../llm/chat_context.js';\nimport { LLM, ToolError, ToolFlag, tool } from '../../llm/index.js';\nimport { AgentTask } from '../../voice/agent.js';\n\ninterface FactoryInfo {\n taskFactory: () => AgentTask;\n id: string;\n description: string;\n}\n\nexport interface TaskGroupResult {\n taskResults: Record<string, unknown>;\n}\n\nexport interface TaskCompletedEvent {\n agentTask: AgentTask;\n taskId: string;\n result: unknown;\n}\n\nclass OutOfScopeError extends ToolError {\n readonly targetTaskIds: string[];\n\n constructor(targetTaskIds: string[]) {\n super('out_of_scope');\n this.targetTaskIds = targetTaskIds;\n }\n}\n\nexport interface TaskGroupOptions {\n summarizeChatCtx?: boolean;\n returnExceptions?: boolean;\n chatCtx?: ChatContext;\n onTaskCompleted?: (event: TaskCompletedEvent) => Promise<void>;\n}\n\nexport class TaskGroup extends AgentTask<TaskGroupResult> {\n private _summarizeChatCtx: boolean;\n private _returnExceptions: boolean;\n private _visitedTasks = new Set<string>();\n private _registeredFactories = new Map<string, FactoryInfo>();\n private _taskCompletedCallback?: (event: TaskCompletedEvent) => Promise<void>;\n private _currentTask?: AgentTask;\n\n constructor(options: TaskGroupOptions = {}) {\n const { summarizeChatCtx = true, returnExceptions = false, chatCtx, onTaskCompleted } = options;\n\n super({ instructions: '*empty*', chatCtx });\n\n this._summarizeChatCtx = summarizeChatCtx;\n this._returnExceptions = returnExceptions;\n this._taskCompletedCallback = onTaskCompleted;\n }\n\n add(task: () => AgentTask, { id, description }: { id: string; description: string }): this {\n this._registeredFactories.set(id, { taskFactory: task, id, description });\n return this;\n }\n\n async onEnter(): Promise<void> {\n const taskStack = [...this._registeredFactories.keys()];\n const taskResults: Record<string, unknown> = {};\n\n while (taskStack.length > 0) {\n const taskId = taskStack.shift()!;\n const factoryInfo = this._registeredFactories.get(taskId)!;\n\n this._currentTask = factoryInfo.taskFactory();\n\n const sharedChatCtx = this._chatCtx.copy();\n await this._currentTask.updateChatCtx(sharedChatCtx);\n\n const outOfScopeTool = this.buildOutOfScopeTool(taskId);\n if (outOfScopeTool) {\n await this._currentTask.updateTools({\n ...this._currentTask.toolCtx,\n out_of_scope: outOfScopeTool,\n });\n }\n\n try {\n this._visitedTasks.add(taskId);\n const res = await this._currentTask.run();\n taskResults[taskId] = res;\n\n if (this._taskCompletedCallback) {\n await this._taskCompletedCallback({\n agentTask: this._currentTask,\n taskId,\n result: res,\n });\n }\n } catch (e) {\n if (e instanceof OutOfScopeError) {\n taskStack.unshift(taskId);\n for (let i = e.targetTaskIds.length - 1; i >= 0; i--) {\n taskStack.unshift(e.targetTaskIds[i]!);\n }\n continue;\n }\n\n if (this._returnExceptions) {\n taskResults[taskId] = e;\n continue;\n } else {\n this.complete(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n }\n }\n\n try {\n if (this._summarizeChatCtx) {\n const sessionLlm = this.session.llm;\n if (!(sessionLlm instanceof LLM)) {\n throw new Error('summarizeChatCtx requires a standard LLM on the session');\n }\n\n //
|
|
1
|
+
{"version":3,"sources":["../../../src/beta/workflows/task_group.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { z } from 'zod';\nimport type { ChatContext } from '../../llm/chat_context.js';\nimport { LLM, ToolError, ToolFlag, tool } from '../../llm/index.js';\nimport { AgentTask } from '../../voice/agent.js';\n\ninterface FactoryInfo {\n taskFactory: () => AgentTask;\n id: string;\n description: string;\n}\n\nexport interface TaskGroupResult {\n taskResults: Record<string, unknown>;\n}\n\nexport interface TaskCompletedEvent {\n agentTask: AgentTask;\n taskId: string;\n result: unknown;\n}\n\nclass OutOfScopeError extends ToolError {\n readonly targetTaskIds: string[];\n\n constructor(targetTaskIds: string[]) {\n super('out_of_scope');\n this.targetTaskIds = targetTaskIds;\n }\n}\n\nexport interface TaskGroupOptions {\n summarizeChatCtx?: boolean;\n returnExceptions?: boolean;\n chatCtx?: ChatContext;\n onTaskCompleted?: (event: TaskCompletedEvent) => Promise<void>;\n}\n\nexport class TaskGroup extends AgentTask<TaskGroupResult> {\n private _summarizeChatCtx: boolean;\n private _returnExceptions: boolean;\n private _visitedTasks = new Set<string>();\n private _registeredFactories = new Map<string, FactoryInfo>();\n private _taskCompletedCallback?: (event: TaskCompletedEvent) => Promise<void>;\n private _currentTask?: AgentTask;\n\n constructor(options: TaskGroupOptions = {}) {\n const { summarizeChatCtx = true, returnExceptions = false, chatCtx, onTaskCompleted } = options;\n\n super({ instructions: '*empty*', chatCtx });\n\n this._summarizeChatCtx = summarizeChatCtx;\n this._returnExceptions = returnExceptions;\n this._taskCompletedCallback = onTaskCompleted;\n }\n\n add(task: () => AgentTask, { id, description }: { id: string; description: string }): this {\n this._registeredFactories.set(id, { taskFactory: task, id, description });\n return this;\n }\n\n async onEnter(): Promise<void> {\n const taskStack = [...this._registeredFactories.keys()];\n const taskResults: Record<string, unknown> = {};\n\n while (taskStack.length > 0) {\n const taskId = taskStack.shift()!;\n const factoryInfo = this._registeredFactories.get(taskId)!;\n\n this._currentTask = factoryInfo.taskFactory();\n\n const sharedChatCtx = this._chatCtx.copy();\n await this._currentTask.updateChatCtx(sharedChatCtx);\n\n const outOfScopeTool = this.buildOutOfScopeTool(taskId);\n if (outOfScopeTool) {\n await this._currentTask.updateTools({\n ...this._currentTask.toolCtx,\n out_of_scope: outOfScopeTool,\n });\n }\n\n try {\n this._visitedTasks.add(taskId);\n const res = await this._currentTask.run();\n\n // AgentTask handoff merges omit function calls. Re-merge the completed\n // task context so task-group summarization can incorporate tool results.\n this._chatCtx.merge(this._currentTask.chatCtx.copy(), {\n excludeInstructions: true,\n });\n\n taskResults[taskId] = res;\n\n if (this._taskCompletedCallback) {\n await this._taskCompletedCallback({\n agentTask: this._currentTask,\n taskId,\n result: res,\n });\n }\n } catch (e) {\n if (e instanceof OutOfScopeError) {\n taskStack.unshift(taskId);\n for (let i = e.targetTaskIds.length - 1; i >= 0; i--) {\n taskStack.unshift(e.targetTaskIds[i]!);\n }\n continue;\n }\n\n if (this._returnExceptions) {\n taskResults[taskId] = e;\n continue;\n } else {\n this.complete(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n }\n }\n\n try {\n if (this._summarizeChatCtx) {\n const sessionLlm = this.session.llm;\n if (!(sessionLlm instanceof LLM)) {\n throw new Error('summarizeChatCtx requires a standard LLM on the session');\n }\n\n // Keep the full item stream so summarization can distill tool results\n // into the history summary instead of dropping them up front.\n const ctxToSummarize = this._chatCtx.copy({\n excludeInstructions: false,\n excludeFunctionCall: false,\n excludeEmptyMessage: false,\n excludeHandoff: false,\n });\n\n const summarizedChatCtx = await ctxToSummarize._summarize(sessionLlm, {\n keepLastTurns: 0,\n });\n\n await this.updateChatCtx(summarizedChatCtx);\n }\n } catch (e) {\n this.complete(new Error(`failed to summarize the chat_ctx: ${e}`));\n return;\n }\n\n this.complete({ taskResults });\n }\n\n private buildOutOfScopeTool(activeTaskId: string) {\n if (this._visitedTasks.size === 0) {\n return undefined;\n }\n\n const regressionTaskIds = new Set(this._visitedTasks);\n regressionTaskIds.delete(activeTaskId);\n\n if (regressionTaskIds.size === 0) {\n return undefined;\n }\n\n const taskRepr: Record<string, string> = {};\n for (const [id, info] of this._registeredFactories) {\n if (regressionTaskIds.has(id)) {\n taskRepr[id] = info.description;\n }\n }\n\n const taskIdValues = [...regressionTaskIds] as [string, ...string[]];\n\n const description =\n 'Call to regress to other tasks according to what the user requested to modify, return the corresponding task ids. ' +\n 'For example, if the user wants to change their email and there is a task with id \"email_task\" with a description of \"Collect the user\\'s email\", return the id (\"get_email_task\"). ' +\n 'If the user requests to regress to multiple tasks, such as changing their phone number and email, return both task ids in the order they were requested. ' +\n `The following are the IDs and their corresponding task description. ${JSON.stringify(taskRepr)}`;\n\n const currentTask = this._currentTask;\n const registeredFactories = this._registeredFactories;\n const visitedTasks = this._visitedTasks;\n\n return tool({\n description,\n flags: ToolFlag.IGNORE_ON_ENTER,\n parameters: z.object({\n task_ids: z.array(z.enum(taskIdValues)).describe('The IDs of the tasks requested'),\n }),\n execute: async ({ task_ids }: { task_ids: string[] }) => {\n for (const tid of task_ids) {\n if (!registeredFactories.has(tid) || !visitedTasks.has(tid)) {\n throw new ToolError(`Unable to regress, invalid task id ${tid}`);\n }\n }\n\n if (currentTask && !currentTask.done) {\n currentTask.complete(new OutOfScopeError(task_ids));\n }\n },\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task_group.d.ts","sourceRoot":"","sources":["../../../src/beta/workflows/task_group.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAQjD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAWD,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,qBAAa,SAAU,SAAQ,SAAS,CAAC,eAAe,CAAC;IACvD,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,oBAAoB,CAAkC;IAC9D,OAAO,CAAC,sBAAsB,CAAC,CAA+C;IAC9E,OAAO,CAAC,YAAY,CAAC,CAAY;gBAErB,OAAO,GAAE,gBAAqB;IAU1C,GAAG,CAAC,IAAI,EAAE,MAAM,SAAS,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKpF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"task_group.d.ts","sourceRoot":"","sources":["../../../src/beta/workflows/task_group.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAQjD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAWD,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,qBAAa,SAAU,SAAQ,SAAS,CAAC,eAAe,CAAC;IACvD,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,oBAAoB,CAAkC;IAC9D,OAAO,CAAC,sBAAsB,CAAC,CAA+C;IAC9E,OAAO,CAAC,YAAY,CAAC,CAAY;gBAErB,OAAO,GAAE,gBAAqB;IAU1C,GAAG,CAAC,IAAI,EAAE,MAAM,SAAS,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKpF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyF9B,OAAO,CAAC,mBAAmB;CAkD5B"}
|
|
@@ -45,6 +45,9 @@ class TaskGroup extends AgentTask {
|
|
|
45
45
|
try {
|
|
46
46
|
this._visitedTasks.add(taskId);
|
|
47
47
|
const res = await this._currentTask.run();
|
|
48
|
+
this._chatCtx.merge(this._currentTask.chatCtx.copy(), {
|
|
49
|
+
excludeInstructions: true
|
|
50
|
+
});
|
|
48
51
|
taskResults[taskId] = res;
|
|
49
52
|
if (this._taskCompletedCallback) {
|
|
50
53
|
await this._taskCompletedCallback({
|
|
@@ -77,10 +80,10 @@ class TaskGroup extends AgentTask {
|
|
|
77
80
|
throw new Error("summarizeChatCtx requires a standard LLM on the session");
|
|
78
81
|
}
|
|
79
82
|
const ctxToSummarize = this._chatCtx.copy({
|
|
80
|
-
excludeInstructions:
|
|
81
|
-
|
|
82
|
-
excludeEmptyMessage:
|
|
83
|
-
|
|
83
|
+
excludeInstructions: false,
|
|
84
|
+
excludeFunctionCall: false,
|
|
85
|
+
excludeEmptyMessage: false,
|
|
86
|
+
excludeHandoff: false
|
|
84
87
|
});
|
|
85
88
|
const summarizedChatCtx = await ctxToSummarize._summarize(sessionLlm, {
|
|
86
89
|
keepLastTurns: 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/beta/workflows/task_group.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { z } from 'zod';\nimport type { ChatContext } from '../../llm/chat_context.js';\nimport { LLM, ToolError, ToolFlag, tool } from '../../llm/index.js';\nimport { AgentTask } from '../../voice/agent.js';\n\ninterface FactoryInfo {\n taskFactory: () => AgentTask;\n id: string;\n description: string;\n}\n\nexport interface TaskGroupResult {\n taskResults: Record<string, unknown>;\n}\n\nexport interface TaskCompletedEvent {\n agentTask: AgentTask;\n taskId: string;\n result: unknown;\n}\n\nclass OutOfScopeError extends ToolError {\n readonly targetTaskIds: string[];\n\n constructor(targetTaskIds: string[]) {\n super('out_of_scope');\n this.targetTaskIds = targetTaskIds;\n }\n}\n\nexport interface TaskGroupOptions {\n summarizeChatCtx?: boolean;\n returnExceptions?: boolean;\n chatCtx?: ChatContext;\n onTaskCompleted?: (event: TaskCompletedEvent) => Promise<void>;\n}\n\nexport class TaskGroup extends AgentTask<TaskGroupResult> {\n private _summarizeChatCtx: boolean;\n private _returnExceptions: boolean;\n private _visitedTasks = new Set<string>();\n private _registeredFactories = new Map<string, FactoryInfo>();\n private _taskCompletedCallback?: (event: TaskCompletedEvent) => Promise<void>;\n private _currentTask?: AgentTask;\n\n constructor(options: TaskGroupOptions = {}) {\n const { summarizeChatCtx = true, returnExceptions = false, chatCtx, onTaskCompleted } = options;\n\n super({ instructions: '*empty*', chatCtx });\n\n this._summarizeChatCtx = summarizeChatCtx;\n this._returnExceptions = returnExceptions;\n this._taskCompletedCallback = onTaskCompleted;\n }\n\n add(task: () => AgentTask, { id, description }: { id: string; description: string }): this {\n this._registeredFactories.set(id, { taskFactory: task, id, description });\n return this;\n }\n\n async onEnter(): Promise<void> {\n const taskStack = [...this._registeredFactories.keys()];\n const taskResults: Record<string, unknown> = {};\n\n while (taskStack.length > 0) {\n const taskId = taskStack.shift()!;\n const factoryInfo = this._registeredFactories.get(taskId)!;\n\n this._currentTask = factoryInfo.taskFactory();\n\n const sharedChatCtx = this._chatCtx.copy();\n await this._currentTask.updateChatCtx(sharedChatCtx);\n\n const outOfScopeTool = this.buildOutOfScopeTool(taskId);\n if (outOfScopeTool) {\n await this._currentTask.updateTools({\n ...this._currentTask.toolCtx,\n out_of_scope: outOfScopeTool,\n });\n }\n\n try {\n this._visitedTasks.add(taskId);\n const res = await this._currentTask.run();\n taskResults[taskId] = res;\n\n if (this._taskCompletedCallback) {\n await this._taskCompletedCallback({\n agentTask: this._currentTask,\n taskId,\n result: res,\n });\n }\n } catch (e) {\n if (e instanceof OutOfScopeError) {\n taskStack.unshift(taskId);\n for (let i = e.targetTaskIds.length - 1; i >= 0; i--) {\n taskStack.unshift(e.targetTaskIds[i]!);\n }\n continue;\n }\n\n if (this._returnExceptions) {\n taskResults[taskId] = e;\n continue;\n } else {\n this.complete(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n }\n }\n\n try {\n if (this._summarizeChatCtx) {\n const sessionLlm = this.session.llm;\n if (!(sessionLlm instanceof LLM)) {\n throw new Error('summarizeChatCtx requires a standard LLM on the session');\n }\n\n //
|
|
1
|
+
{"version":3,"sources":["../../../src/beta/workflows/task_group.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { z } from 'zod';\nimport type { ChatContext } from '../../llm/chat_context.js';\nimport { LLM, ToolError, ToolFlag, tool } from '../../llm/index.js';\nimport { AgentTask } from '../../voice/agent.js';\n\ninterface FactoryInfo {\n taskFactory: () => AgentTask;\n id: string;\n description: string;\n}\n\nexport interface TaskGroupResult {\n taskResults: Record<string, unknown>;\n}\n\nexport interface TaskCompletedEvent {\n agentTask: AgentTask;\n taskId: string;\n result: unknown;\n}\n\nclass OutOfScopeError extends ToolError {\n readonly targetTaskIds: string[];\n\n constructor(targetTaskIds: string[]) {\n super('out_of_scope');\n this.targetTaskIds = targetTaskIds;\n }\n}\n\nexport interface TaskGroupOptions {\n summarizeChatCtx?: boolean;\n returnExceptions?: boolean;\n chatCtx?: ChatContext;\n onTaskCompleted?: (event: TaskCompletedEvent) => Promise<void>;\n}\n\nexport class TaskGroup extends AgentTask<TaskGroupResult> {\n private _summarizeChatCtx: boolean;\n private _returnExceptions: boolean;\n private _visitedTasks = new Set<string>();\n private _registeredFactories = new Map<string, FactoryInfo>();\n private _taskCompletedCallback?: (event: TaskCompletedEvent) => Promise<void>;\n private _currentTask?: AgentTask;\n\n constructor(options: TaskGroupOptions = {}) {\n const { summarizeChatCtx = true, returnExceptions = false, chatCtx, onTaskCompleted } = options;\n\n super({ instructions: '*empty*', chatCtx });\n\n this._summarizeChatCtx = summarizeChatCtx;\n this._returnExceptions = returnExceptions;\n this._taskCompletedCallback = onTaskCompleted;\n }\n\n add(task: () => AgentTask, { id, description }: { id: string; description: string }): this {\n this._registeredFactories.set(id, { taskFactory: task, id, description });\n return this;\n }\n\n async onEnter(): Promise<void> {\n const taskStack = [...this._registeredFactories.keys()];\n const taskResults: Record<string, unknown> = {};\n\n while (taskStack.length > 0) {\n const taskId = taskStack.shift()!;\n const factoryInfo = this._registeredFactories.get(taskId)!;\n\n this._currentTask = factoryInfo.taskFactory();\n\n const sharedChatCtx = this._chatCtx.copy();\n await this._currentTask.updateChatCtx(sharedChatCtx);\n\n const outOfScopeTool = this.buildOutOfScopeTool(taskId);\n if (outOfScopeTool) {\n await this._currentTask.updateTools({\n ...this._currentTask.toolCtx,\n out_of_scope: outOfScopeTool,\n });\n }\n\n try {\n this._visitedTasks.add(taskId);\n const res = await this._currentTask.run();\n\n // AgentTask handoff merges omit function calls. Re-merge the completed\n // task context so task-group summarization can incorporate tool results.\n this._chatCtx.merge(this._currentTask.chatCtx.copy(), {\n excludeInstructions: true,\n });\n\n taskResults[taskId] = res;\n\n if (this._taskCompletedCallback) {\n await this._taskCompletedCallback({\n agentTask: this._currentTask,\n taskId,\n result: res,\n });\n }\n } catch (e) {\n if (e instanceof OutOfScopeError) {\n taskStack.unshift(taskId);\n for (let i = e.targetTaskIds.length - 1; i >= 0; i--) {\n taskStack.unshift(e.targetTaskIds[i]!);\n }\n continue;\n }\n\n if (this._returnExceptions) {\n taskResults[taskId] = e;\n continue;\n } else {\n this.complete(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n }\n }\n\n try {\n if (this._summarizeChatCtx) {\n const sessionLlm = this.session.llm;\n if (!(sessionLlm instanceof LLM)) {\n throw new Error('summarizeChatCtx requires a standard LLM on the session');\n }\n\n // Keep the full item stream so summarization can distill tool results\n // into the history summary instead of dropping them up front.\n const ctxToSummarize = this._chatCtx.copy({\n excludeInstructions: false,\n excludeFunctionCall: false,\n excludeEmptyMessage: false,\n excludeHandoff: false,\n });\n\n const summarizedChatCtx = await ctxToSummarize._summarize(sessionLlm, {\n keepLastTurns: 0,\n });\n\n await this.updateChatCtx(summarizedChatCtx);\n }\n } catch (e) {\n this.complete(new Error(`failed to summarize the chat_ctx: ${e}`));\n return;\n }\n\n this.complete({ taskResults });\n }\n\n private buildOutOfScopeTool(activeTaskId: string) {\n if (this._visitedTasks.size === 0) {\n return undefined;\n }\n\n const regressionTaskIds = new Set(this._visitedTasks);\n regressionTaskIds.delete(activeTaskId);\n\n if (regressionTaskIds.size === 0) {\n return undefined;\n }\n\n const taskRepr: Record<string, string> = {};\n for (const [id, info] of this._registeredFactories) {\n if (regressionTaskIds.has(id)) {\n taskRepr[id] = info.description;\n }\n }\n\n const taskIdValues = [...regressionTaskIds] as [string, ...string[]];\n\n const description =\n 'Call to regress to other tasks according to what the user requested to modify, return the corresponding task ids. ' +\n 'For example, if the user wants to change their email and there is a task with id \"email_task\" with a description of \"Collect the user\\'s email\", return the id (\"get_email_task\"). ' +\n 'If the user requests to regress to multiple tasks, such as changing their phone number and email, return both task ids in the order they were requested. ' +\n `The following are the IDs and their corresponding task description. ${JSON.stringify(taskRepr)}`;\n\n const currentTask = this._currentTask;\n const registeredFactories = this._registeredFactories;\n const visitedTasks = this._visitedTasks;\n\n return tool({\n description,\n flags: ToolFlag.IGNORE_ON_ENTER,\n parameters: z.object({\n task_ids: z.array(z.enum(taskIdValues)).describe('The IDs of the tasks requested'),\n }),\n execute: async ({ task_ids }: { task_ids: string[] }) => {\n for (const tid of task_ids) {\n if (!registeredFactories.has(tid) || !visitedTasks.has(tid)) {\n throw new ToolError(`Unable to regress, invalid task id ${tid}`);\n }\n }\n\n if (currentTask && !currentTask.done) {\n currentTask.complete(new OutOfScopeError(task_ids));\n }\n },\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/inference/interruption/http_transport.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { FetchError, ofetch } from 'ofetch';\nimport { TransformStream } from 'stream/web';\nimport { z } from 'zod';\nimport { APIConnectionError, APIError, APIStatusError, isAPIError } from '../../_exceptions.js';\nimport { log } from '../../log.js';\nimport { createAccessToken } from '../utils.js';\nimport { InterruptionCacheEntry } from './interruption_cache_entry.js';\nimport type { OverlappingSpeechEvent } from './types.js';\nimport type { BoundedCache } from './utils.js';\n\nexport interface PostOptions {\n baseUrl: string;\n token: string;\n signal?: AbortSignal;\n timeout?: number;\n maxRetries?: number;\n}\n\nexport interface PredictOptions {\n threshold: number;\n minFrames: number;\n}\n\nexport const predictEndpointResponseSchema = z.object({\n created_at: z.number(),\n is_bargein: z.boolean(),\n probabilities: z.array(z.number()),\n});\n\nexport type PredictEndpointResponse = z.infer<typeof predictEndpointResponseSchema>;\n\nexport interface PredictResponse {\n createdAt: number;\n isBargein: boolean;\n probabilities: number[];\n predictionDurationInS: number;\n}\n\nexport async function predictHTTP(\n data: Int16Array,\n predictOptions: PredictOptions,\n options: PostOptions,\n): Promise<PredictResponse> {\n const createdAt = performance.now();\n const url = new URL(`/bargein`, options.baseUrl);\n url.searchParams.append('threshold', predictOptions.threshold.toString());\n url.searchParams.append('min_frames', predictOptions.minFrames.toFixed());\n url.searchParams.append('created_at', createdAt.toFixed());\n\n try {\n const response = await ofetch(url.toString(), {\n retry: 0,\n headers: {\n 'Content-Type': 'application/octet-stream',\n Authorization: `Bearer ${options.token}`,\n },\n signal: options.signal,\n timeout: options.timeout,\n method: 'POST',\n body: data,\n });\n const { created_at, is_bargein, probabilities } = predictEndpointResponseSchema.parse(response);\n\n return {\n createdAt: created_at,\n isBargein: is_bargein,\n probabilities,\n predictionDurationInS: (performance.now() - createdAt) / 1000,\n };\n } catch (err) {\n if (isAPIError(err)) throw err;\n if (err instanceof FetchError) {\n if (err.statusCode) {\n throw new APIStatusError({\n message: `error during interruption prediction: ${err.message}`,\n options: { statusCode: err.statusCode, body: err.data },\n });\n }\n if (\n err.cause instanceof Error &&\n (err.cause.name === 'TimeoutError' || err.cause.name === 'AbortError')\n ) {\n throw new APIStatusError({\n message: `interruption inference timeout: ${err.message}`,\n options: { statusCode: 408, retryable: false },\n });\n }\n throw new APIConnectionError({\n message: `interruption inference connection error: ${err.message}`,\n });\n }\n throw new APIError(`error during interruption prediction: ${err}`);\n }\n}\n\nexport interface HttpTransportOptions {\n baseUrl: string;\n apiKey: string;\n apiSecret: string;\n threshold: number;\n minFrames: number;\n timeout: number;\n maxRetries?: number;\n}\n\nexport interface HttpTransportState {\n overlapSpeechStarted: boolean;\n overlapSpeechStartedAt: number | undefined;\n cache: BoundedCache<number, InterruptionCacheEntry>;\n}\n\n/**\n * Creates an HTTP transport TransformStream for interruption detection.\n *\n * This transport receives Int16Array audio slices and outputs InterruptionEvents.\n * Each audio slice triggers an HTTP POST request.\n *\n * @param options - Transport options object. This is read on each request, so mutations\n * to threshold/minFrames will be picked up dynamically.\n */\nexport function createHttpTransport(\n options: HttpTransportOptions,\n getState: () => HttpTransportState,\n setState: (partial: Partial<HttpTransportState>) => void,\n updateUserSpeakingSpan?: (entry: InterruptionCacheEntry) => void,\n getAndResetNumRequests?: () => number,\n): TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent> {\n const logger = log();\n\n return new TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent>(\n {\n async transform(chunk, controller) {\n if (!(chunk instanceof Int16Array)) {\n controller.enqueue(chunk);\n return;\n }\n\n const state = getState();\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (overlapSpeechStartedAt === undefined || !state.overlapSpeechStarted) return;\n\n try {\n const resp = await predictHTTP(\n chunk,\n { threshold: options.threshold, minFrames: options.minFrames },\n {\n baseUrl: options.baseUrl,\n timeout: options.timeout,\n maxRetries: options.maxRetries,\n token: await createAccessToken(options.apiKey, options.apiSecret),\n },\n );\n\n const { createdAt, isBargein, probabilities, predictionDurationInS } = resp;\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n probabilities,\n isInterruption: isBargein,\n speechInput: chunk,\n totalDurationInS: (performance.now() - createdAt) / 1000,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n predictionDurationInS,\n },\n );\n\n if (state.overlapSpeechStarted && entry.isInterruption) {\n if (updateUserSpeakingSpan) {\n updateUserSpeakingSpan(entry);\n }\n const event: OverlappingSpeechEvent = {\n type: 'overlapping_speech',\n detectedAt: Date.now(),\n overlapStartedAt: overlapSpeechStartedAt,\n isInterruption: entry.isInterruption,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n detectionDelayInS: entry.detectionDelayInS,\n probability: entry.probability,\n numRequests: getAndResetNumRequests?.() ?? 0,\n };\n logger.debug(\n {\n detectionDelayInS: entry.detectionDelayInS,\n totalDurationInS: entry.totalDurationInS,\n },\n 'interruption detected',\n );\n setState({ overlapSpeechStarted: false });\n controller.enqueue(event);\n }\n } catch (err) {\n controller.error(err);\n }\n },\n },\n { highWaterMark: 2 },\n { highWaterMark: 2 },\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/inference/interruption/http_transport.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Throws } from '@livekit/throws-transformer/throws';\nimport { FetchError, ofetch } from 'ofetch';\nimport { TransformStream } from 'stream/web';\nimport { z } from 'zod';\nimport { APIConnectionError, APIError, APIStatusError, isAPIError } from '../../_exceptions.js';\nimport { log } from '../../log.js';\nimport { createAccessToken } from '../utils.js';\nimport { InterruptionCacheEntry } from './interruption_cache_entry.js';\nimport type { OverlappingSpeechEvent } from './types.js';\nimport type { BoundedCache } from './utils.js';\n\nexport interface PostOptions {\n baseUrl: string;\n token: string;\n signal?: AbortSignal;\n timeout?: number;\n maxRetries?: number;\n}\n\nexport interface PredictOptions {\n threshold: number;\n minFrames: number;\n}\n\nexport const predictEndpointResponseSchema = z.object({\n created_at: z.number(),\n is_bargein: z.boolean(),\n probabilities: z.array(z.number()),\n});\n\nexport type PredictEndpointResponse = z.infer<typeof predictEndpointResponseSchema>;\n\nexport interface PredictResponse {\n createdAt: number;\n isBargein: boolean;\n probabilities: number[];\n predictionDurationInS: number;\n}\n\nexport async function predictHTTP(\n data: Int16Array,\n predictOptions: PredictOptions,\n options: PostOptions,\n): Promise<Throws<PredictResponse, APIConnectionError | APIStatusError | APIError>> {\n const createdAt = performance.now();\n const url = new URL(`/bargein`, options.baseUrl);\n url.searchParams.append('threshold', predictOptions.threshold.toString());\n url.searchParams.append('min_frames', predictOptions.minFrames.toFixed());\n url.searchParams.append('created_at', createdAt.toFixed());\n\n try {\n const response = await ofetch(url.toString(), {\n retry: 0,\n headers: {\n 'Content-Type': 'application/octet-stream',\n Authorization: `Bearer ${options.token}`,\n },\n signal: options.signal,\n timeout: options.timeout,\n method: 'POST',\n body: data,\n });\n const { created_at, is_bargein, probabilities } = predictEndpointResponseSchema.parse(response);\n\n return {\n createdAt: created_at,\n isBargein: is_bargein,\n probabilities,\n predictionDurationInS: (performance.now() - createdAt) / 1000,\n };\n } catch (err) {\n if (isAPIError(err)) throw err;\n if (err instanceof FetchError) {\n if (err.statusCode) {\n throw new APIStatusError({\n message: `error during interruption prediction: ${err.message}`,\n options: { statusCode: err.statusCode, body: err.data },\n });\n }\n if (\n err.cause instanceof Error &&\n (err.cause.name === 'TimeoutError' || err.cause.name === 'AbortError')\n ) {\n throw new APIStatusError({\n message: `interruption inference timeout: ${err.message}`,\n options: { statusCode: 408, retryable: false },\n });\n }\n throw new APIConnectionError({\n message: `interruption inference connection error: ${err.message}`,\n });\n }\n throw new APIError(`error during interruption prediction: ${err}`);\n }\n}\n\nexport interface HttpTransportOptions {\n baseUrl: string;\n apiKey: string;\n apiSecret: string;\n threshold: number;\n minFrames: number;\n timeout: number;\n maxRetries?: number;\n}\n\nexport interface HttpTransportState {\n overlapSpeechStarted: boolean;\n overlapSpeechStartedAt: number | undefined;\n cache: BoundedCache<number, InterruptionCacheEntry>;\n}\n\n/**\n * Creates an HTTP transport TransformStream for interruption detection.\n *\n * This transport receives Int16Array audio slices and outputs InterruptionEvents.\n * Each audio slice triggers an HTTP POST request.\n *\n * @param options - Transport options object. This is read on each request, so mutations\n * to threshold/minFrames will be picked up dynamically.\n */\nexport function createHttpTransport(\n options: HttpTransportOptions,\n getState: () => HttpTransportState,\n setState: (partial: Partial<HttpTransportState>) => void,\n updateUserSpeakingSpan?: (entry: InterruptionCacheEntry) => void,\n getAndResetNumRequests?: () => number,\n): TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent> {\n const logger = log();\n\n return new TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent>(\n {\n async transform(chunk, controller) {\n if (!(chunk instanceof Int16Array)) {\n controller.enqueue(chunk);\n return;\n }\n\n const state = getState();\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (overlapSpeechStartedAt === undefined || !state.overlapSpeechStarted) return;\n\n try {\n const resp = await predictHTTP(\n chunk,\n { threshold: options.threshold, minFrames: options.minFrames },\n {\n baseUrl: options.baseUrl,\n timeout: options.timeout,\n maxRetries: options.maxRetries,\n token: await createAccessToken(options.apiKey, options.apiSecret),\n },\n );\n\n const { createdAt, isBargein, probabilities, predictionDurationInS } = resp;\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n probabilities,\n isInterruption: isBargein,\n speechInput: chunk,\n totalDurationInS: (performance.now() - createdAt) / 1000,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n predictionDurationInS,\n },\n );\n\n if (state.overlapSpeechStarted && entry.isInterruption) {\n if (updateUserSpeakingSpan) {\n updateUserSpeakingSpan(entry);\n }\n const event: OverlappingSpeechEvent = {\n type: 'overlapping_speech',\n detectedAt: Date.now(),\n overlapStartedAt: overlapSpeechStartedAt,\n isInterruption: entry.isInterruption,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n detectionDelayInS: entry.detectionDelayInS,\n probability: entry.probability,\n numRequests: getAndResetNumRequests?.() ?? 0,\n };\n logger.debug(\n {\n detectionDelayInS: entry.detectionDelayInS,\n totalDurationInS: entry.totalDurationInS,\n },\n 'interruption detected',\n );\n setState({ overlapSpeechStarted: false });\n controller.enqueue(event);\n }\n } catch (err) {\n controller.error(err);\n }\n },\n },\n { highWaterMark: 2 },\n { highWaterMark: 2 },\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Throws } from '@livekit/throws-transformer/throws';
|
|
2
3
|
import { TransformStream } from 'stream/web';
|
|
3
4
|
import { z } from 'zod';
|
|
5
|
+
import { APIConnectionError, APIError, APIStatusError } from '../../_exceptions.js';
|
|
4
6
|
import { InterruptionCacheEntry } from './interruption_cache_entry.js';
|
|
5
7
|
import type { OverlappingSpeechEvent } from './types.js';
|
|
6
8
|
import type { BoundedCache } from './utils.js';
|
|
@@ -35,7 +37,7 @@ export interface PredictResponse {
|
|
|
35
37
|
probabilities: number[];
|
|
36
38
|
predictionDurationInS: number;
|
|
37
39
|
}
|
|
38
|
-
export declare function predictHTTP(data: Int16Array, predictOptions: PredictOptions, options: PostOptions): Promise<PredictResponse
|
|
40
|
+
export declare function predictHTTP(data: Int16Array, predictOptions: PredictOptions, options: PostOptions): Promise<Throws<PredictResponse, APIConnectionError | APIStatusError | APIError>>;
|
|
39
41
|
export interface HttpTransportOptions {
|
|
40
42
|
baseUrl: string;
|
|
41
43
|
apiKey: string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Throws } from '@livekit/throws-transformer/throws';
|
|
2
3
|
import { TransformStream } from 'stream/web';
|
|
3
4
|
import { z } from 'zod';
|
|
5
|
+
import { APIConnectionError, APIError, APIStatusError } from '../../_exceptions.js';
|
|
4
6
|
import { InterruptionCacheEntry } from './interruption_cache_entry.js';
|
|
5
7
|
import type { OverlappingSpeechEvent } from './types.js';
|
|
6
8
|
import type { BoundedCache } from './utils.js';
|
|
@@ -35,7 +37,7 @@ export interface PredictResponse {
|
|
|
35
37
|
probabilities: number[];
|
|
36
38
|
predictionDurationInS: number;
|
|
37
39
|
}
|
|
38
|
-
export declare function predictHTTP(data: Int16Array, predictOptions: PredictOptions, options: PostOptions): Promise<PredictResponse
|
|
40
|
+
export declare function predictHTTP(data: Int16Array, predictOptions: PredictOptions, options: PostOptions): Promise<Throws<PredictResponse, APIConnectionError | APIStatusError | APIError>>;
|
|
39
41
|
export interface HttpTransportOptions {
|
|
40
42
|
baseUrl: string;
|
|
41
43
|
apiKey: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http_transport.d.ts","sourceRoot":"","sources":["../../../src/inference/interruption/http_transport.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"http_transport.d.ts","sourceRoot":"","sources":["../../../src/inference/interruption/http_transport.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAEjE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,cAAc,EAAc,MAAM,sBAAsB,CAAC;AAGhG,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,6BAA6B;;;;;;;;;;;;EAIxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,kBAAkB,GAAG,cAAc,GAAG,QAAQ,CAAC,CAAC,CAmDlF;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,kBAAkB,EAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,EACxD,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,EAChE,sBAAsB,CAAC,EAAE,MAAM,MAAM,GACpC,eAAe,CAAC,UAAU,GAAG,sBAAsB,EAAE,sBAAsB,CAAC,CA4E9E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/inference/interruption/http_transport.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { FetchError, ofetch } from 'ofetch';\nimport { TransformStream } from 'stream/web';\nimport { z } from 'zod';\nimport { APIConnectionError, APIError, APIStatusError, isAPIError } from '../../_exceptions.js';\nimport { log } from '../../log.js';\nimport { createAccessToken } from '../utils.js';\nimport { InterruptionCacheEntry } from './interruption_cache_entry.js';\nimport type { OverlappingSpeechEvent } from './types.js';\nimport type { BoundedCache } from './utils.js';\n\nexport interface PostOptions {\n baseUrl: string;\n token: string;\n signal?: AbortSignal;\n timeout?: number;\n maxRetries?: number;\n}\n\nexport interface PredictOptions {\n threshold: number;\n minFrames: number;\n}\n\nexport const predictEndpointResponseSchema = z.object({\n created_at: z.number(),\n is_bargein: z.boolean(),\n probabilities: z.array(z.number()),\n});\n\nexport type PredictEndpointResponse = z.infer<typeof predictEndpointResponseSchema>;\n\nexport interface PredictResponse {\n createdAt: number;\n isBargein: boolean;\n probabilities: number[];\n predictionDurationInS: number;\n}\n\nexport async function predictHTTP(\n data: Int16Array,\n predictOptions: PredictOptions,\n options: PostOptions,\n): Promise<PredictResponse> {\n const createdAt = performance.now();\n const url = new URL(`/bargein`, options.baseUrl);\n url.searchParams.append('threshold', predictOptions.threshold.toString());\n url.searchParams.append('min_frames', predictOptions.minFrames.toFixed());\n url.searchParams.append('created_at', createdAt.toFixed());\n\n try {\n const response = await ofetch(url.toString(), {\n retry: 0,\n headers: {\n 'Content-Type': 'application/octet-stream',\n Authorization: `Bearer ${options.token}`,\n },\n signal: options.signal,\n timeout: options.timeout,\n method: 'POST',\n body: data,\n });\n const { created_at, is_bargein, probabilities } = predictEndpointResponseSchema.parse(response);\n\n return {\n createdAt: created_at,\n isBargein: is_bargein,\n probabilities,\n predictionDurationInS: (performance.now() - createdAt) / 1000,\n };\n } catch (err) {\n if (isAPIError(err)) throw err;\n if (err instanceof FetchError) {\n if (err.statusCode) {\n throw new APIStatusError({\n message: `error during interruption prediction: ${err.message}`,\n options: { statusCode: err.statusCode, body: err.data },\n });\n }\n if (\n err.cause instanceof Error &&\n (err.cause.name === 'TimeoutError' || err.cause.name === 'AbortError')\n ) {\n throw new APIStatusError({\n message: `interruption inference timeout: ${err.message}`,\n options: { statusCode: 408, retryable: false },\n });\n }\n throw new APIConnectionError({\n message: `interruption inference connection error: ${err.message}`,\n });\n }\n throw new APIError(`error during interruption prediction: ${err}`);\n }\n}\n\nexport interface HttpTransportOptions {\n baseUrl: string;\n apiKey: string;\n apiSecret: string;\n threshold: number;\n minFrames: number;\n timeout: number;\n maxRetries?: number;\n}\n\nexport interface HttpTransportState {\n overlapSpeechStarted: boolean;\n overlapSpeechStartedAt: number | undefined;\n cache: BoundedCache<number, InterruptionCacheEntry>;\n}\n\n/**\n * Creates an HTTP transport TransformStream for interruption detection.\n *\n * This transport receives Int16Array audio slices and outputs InterruptionEvents.\n * Each audio slice triggers an HTTP POST request.\n *\n * @param options - Transport options object. This is read on each request, so mutations\n * to threshold/minFrames will be picked up dynamically.\n */\nexport function createHttpTransport(\n options: HttpTransportOptions,\n getState: () => HttpTransportState,\n setState: (partial: Partial<HttpTransportState>) => void,\n updateUserSpeakingSpan?: (entry: InterruptionCacheEntry) => void,\n getAndResetNumRequests?: () => number,\n): TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent> {\n const logger = log();\n\n return new TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent>(\n {\n async transform(chunk, controller) {\n if (!(chunk instanceof Int16Array)) {\n controller.enqueue(chunk);\n return;\n }\n\n const state = getState();\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (overlapSpeechStartedAt === undefined || !state.overlapSpeechStarted) return;\n\n try {\n const resp = await predictHTTP(\n chunk,\n { threshold: options.threshold, minFrames: options.minFrames },\n {\n baseUrl: options.baseUrl,\n timeout: options.timeout,\n maxRetries: options.maxRetries,\n token: await createAccessToken(options.apiKey, options.apiSecret),\n },\n );\n\n const { createdAt, isBargein, probabilities, predictionDurationInS } = resp;\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n probabilities,\n isInterruption: isBargein,\n speechInput: chunk,\n totalDurationInS: (performance.now() - createdAt) / 1000,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n predictionDurationInS,\n },\n );\n\n if (state.overlapSpeechStarted && entry.isInterruption) {\n if (updateUserSpeakingSpan) {\n updateUserSpeakingSpan(entry);\n }\n const event: OverlappingSpeechEvent = {\n type: 'overlapping_speech',\n detectedAt: Date.now(),\n overlapStartedAt: overlapSpeechStartedAt,\n isInterruption: entry.isInterruption,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n detectionDelayInS: entry.detectionDelayInS,\n probability: entry.probability,\n numRequests: getAndResetNumRequests?.() ?? 0,\n };\n logger.debug(\n {\n detectionDelayInS: entry.detectionDelayInS,\n totalDurationInS: entry.totalDurationInS,\n },\n 'interruption detected',\n );\n setState({ overlapSpeechStarted: false });\n controller.enqueue(event);\n }\n } catch (err) {\n controller.error(err);\n }\n },\n },\n { highWaterMark: 2 },\n { highWaterMark: 2 },\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/inference/interruption/http_transport.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Throws } from '@livekit/throws-transformer/throws';\nimport { FetchError, ofetch } from 'ofetch';\nimport { TransformStream } from 'stream/web';\nimport { z } from 'zod';\nimport { APIConnectionError, APIError, APIStatusError, isAPIError } from '../../_exceptions.js';\nimport { log } from '../../log.js';\nimport { createAccessToken } from '../utils.js';\nimport { InterruptionCacheEntry } from './interruption_cache_entry.js';\nimport type { OverlappingSpeechEvent } from './types.js';\nimport type { BoundedCache } from './utils.js';\n\nexport interface PostOptions {\n baseUrl: string;\n token: string;\n signal?: AbortSignal;\n timeout?: number;\n maxRetries?: number;\n}\n\nexport interface PredictOptions {\n threshold: number;\n minFrames: number;\n}\n\nexport const predictEndpointResponseSchema = z.object({\n created_at: z.number(),\n is_bargein: z.boolean(),\n probabilities: z.array(z.number()),\n});\n\nexport type PredictEndpointResponse = z.infer<typeof predictEndpointResponseSchema>;\n\nexport interface PredictResponse {\n createdAt: number;\n isBargein: boolean;\n probabilities: number[];\n predictionDurationInS: number;\n}\n\nexport async function predictHTTP(\n data: Int16Array,\n predictOptions: PredictOptions,\n options: PostOptions,\n): Promise<Throws<PredictResponse, APIConnectionError | APIStatusError | APIError>> {\n const createdAt = performance.now();\n const url = new URL(`/bargein`, options.baseUrl);\n url.searchParams.append('threshold', predictOptions.threshold.toString());\n url.searchParams.append('min_frames', predictOptions.minFrames.toFixed());\n url.searchParams.append('created_at', createdAt.toFixed());\n\n try {\n const response = await ofetch(url.toString(), {\n retry: 0,\n headers: {\n 'Content-Type': 'application/octet-stream',\n Authorization: `Bearer ${options.token}`,\n },\n signal: options.signal,\n timeout: options.timeout,\n method: 'POST',\n body: data,\n });\n const { created_at, is_bargein, probabilities } = predictEndpointResponseSchema.parse(response);\n\n return {\n createdAt: created_at,\n isBargein: is_bargein,\n probabilities,\n predictionDurationInS: (performance.now() - createdAt) / 1000,\n };\n } catch (err) {\n if (isAPIError(err)) throw err;\n if (err instanceof FetchError) {\n if (err.statusCode) {\n throw new APIStatusError({\n message: `error during interruption prediction: ${err.message}`,\n options: { statusCode: err.statusCode, body: err.data },\n });\n }\n if (\n err.cause instanceof Error &&\n (err.cause.name === 'TimeoutError' || err.cause.name === 'AbortError')\n ) {\n throw new APIStatusError({\n message: `interruption inference timeout: ${err.message}`,\n options: { statusCode: 408, retryable: false },\n });\n }\n throw new APIConnectionError({\n message: `interruption inference connection error: ${err.message}`,\n });\n }\n throw new APIError(`error during interruption prediction: ${err}`);\n }\n}\n\nexport interface HttpTransportOptions {\n baseUrl: string;\n apiKey: string;\n apiSecret: string;\n threshold: number;\n minFrames: number;\n timeout: number;\n maxRetries?: number;\n}\n\nexport interface HttpTransportState {\n overlapSpeechStarted: boolean;\n overlapSpeechStartedAt: number | undefined;\n cache: BoundedCache<number, InterruptionCacheEntry>;\n}\n\n/**\n * Creates an HTTP transport TransformStream for interruption detection.\n *\n * This transport receives Int16Array audio slices and outputs InterruptionEvents.\n * Each audio slice triggers an HTTP POST request.\n *\n * @param options - Transport options object. This is read on each request, so mutations\n * to threshold/minFrames will be picked up dynamically.\n */\nexport function createHttpTransport(\n options: HttpTransportOptions,\n getState: () => HttpTransportState,\n setState: (partial: Partial<HttpTransportState>) => void,\n updateUserSpeakingSpan?: (entry: InterruptionCacheEntry) => void,\n getAndResetNumRequests?: () => number,\n): TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent> {\n const logger = log();\n\n return new TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent>(\n {\n async transform(chunk, controller) {\n if (!(chunk instanceof Int16Array)) {\n controller.enqueue(chunk);\n return;\n }\n\n const state = getState();\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (overlapSpeechStartedAt === undefined || !state.overlapSpeechStarted) return;\n\n try {\n const resp = await predictHTTP(\n chunk,\n { threshold: options.threshold, minFrames: options.minFrames },\n {\n baseUrl: options.baseUrl,\n timeout: options.timeout,\n maxRetries: options.maxRetries,\n token: await createAccessToken(options.apiKey, options.apiSecret),\n },\n );\n\n const { createdAt, isBargein, probabilities, predictionDurationInS } = resp;\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n probabilities,\n isInterruption: isBargein,\n speechInput: chunk,\n totalDurationInS: (performance.now() - createdAt) / 1000,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n predictionDurationInS,\n },\n );\n\n if (state.overlapSpeechStarted && entry.isInterruption) {\n if (updateUserSpeakingSpan) {\n updateUserSpeakingSpan(entry);\n }\n const event: OverlappingSpeechEvent = {\n type: 'overlapping_speech',\n detectedAt: Date.now(),\n overlapStartedAt: overlapSpeechStartedAt,\n isInterruption: entry.isInterruption,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n detectionDelayInS: entry.detectionDelayInS,\n probability: entry.probability,\n numRequests: getAndResetNumRequests?.() ?? 0,\n };\n logger.debug(\n {\n detectionDelayInS: entry.detectionDelayInS,\n totalDurationInS: entry.totalDurationInS,\n },\n 'interruption detected',\n );\n setState({ overlapSpeechStarted: false });\n controller.enqueue(event);\n }\n } catch (err) {\n controller.error(err);\n }\n },\n },\n { highWaterMark: 2 },\n { highWaterMark: 2 },\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -36,6 +36,7 @@ var import_ws = __toESM(require("ws"), 1);
|
|
|
36
36
|
var import_zod = require("zod");
|
|
37
37
|
var import_exceptions = require("../../_exceptions.cjs");
|
|
38
38
|
var import_log = require("../../log.cjs");
|
|
39
|
+
var import_typed_promise = __toESM(require("../../typed_promise.cjs"), 1);
|
|
39
40
|
var import_utils = require("../utils.cjs");
|
|
40
41
|
var import_interruption_cache_entry = require("./interruption_cache_entry.cjs");
|
|
41
42
|
const MSG_SESSION_CREATE = "session.create";
|
|
@@ -79,37 +80,39 @@ async function connectWebSocket(options) {
|
|
|
79
80
|
const ws = new import_ws.default(url, {
|
|
80
81
|
headers: { Authorization: `Bearer ${token}` }
|
|
81
82
|
});
|
|
82
|
-
await new
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
83
|
+
await new import_typed_promise.default(
|
|
84
|
+
(resolve, reject) => {
|
|
85
|
+
const timeout = setTimeout(() => {
|
|
86
|
+
ws.terminate();
|
|
87
|
+
reject(
|
|
88
|
+
new import_exceptions.APITimeoutError({
|
|
89
|
+
message: "WebSocket connection timeout",
|
|
90
|
+
options: { retryable: false }
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
}, options.timeout);
|
|
94
|
+
ws.once("open", () => {
|
|
95
|
+
clearTimeout(timeout);
|
|
96
|
+
resolve();
|
|
97
|
+
});
|
|
98
|
+
ws.once("unexpected-response", (_req, res) => {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
ws.terminate();
|
|
101
|
+
const statusCode = res.statusCode ?? -1;
|
|
102
|
+
reject(
|
|
103
|
+
new import_exceptions.APIStatusError({
|
|
104
|
+
message: `WebSocket connection rejected with status ${statusCode}`,
|
|
105
|
+
options: { statusCode, retryable: false }
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
ws.once("error", (err) => {
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
ws.terminate();
|
|
112
|
+
reject(new import_exceptions.APIConnectionError({ message: `WebSocket connection error: ${err.message}` }));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
);
|
|
113
116
|
return ws;
|
|
114
117
|
}
|
|
115
118
|
function createWsTransport(options, getState, setState, updateUserSpeakingSpan, onRequestSent, getAndResetNumRequests) {
|
|
@@ -295,7 +298,9 @@ function createWsTransport(options, getState, setState, updateUserSpeakingSpan,
|
|
|
295
298
|
{
|
|
296
299
|
async start(controller) {
|
|
297
300
|
outputController = controller;
|
|
298
|
-
await ensureConnection()
|
|
301
|
+
await ensureConnection().catch((e) => {
|
|
302
|
+
controller.error(e);
|
|
303
|
+
});
|
|
299
304
|
},
|
|
300
305
|
transform(chunk, controller) {
|
|
301
306
|
if (!(chunk instanceof Int16Array)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/inference/interruption/ws_transport.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { TransformStream } from 'stream/web';\nimport WebSocket from 'ws';\nimport { z } from 'zod';\nimport { APIConnectionError, APIStatusError, APITimeoutError } from '../../_exceptions.js';\nimport { log } from '../../log.js';\nimport { createAccessToken } from '../utils.js';\nimport { InterruptionCacheEntry } from './interruption_cache_entry.js';\nimport type { OverlappingSpeechEvent } from './types.js';\nimport type { BoundedCache } from './utils.js';\n\n// WebSocket message types\nconst MSG_SESSION_CREATE = 'session.create';\nconst MSG_SESSION_CLOSE = 'session.close';\nconst MSG_SESSION_CREATED = 'session.created';\nconst MSG_SESSION_CLOSED = 'session.closed';\nconst MSG_INTERRUPTION_DETECTED = 'bargein_detected';\nconst MSG_INFERENCE_DONE = 'inference_done';\nconst MSG_ERROR = 'error';\n\nexport interface WsTransportOptions {\n baseUrl: string;\n apiKey: string;\n apiSecret: string;\n sampleRate: number;\n threshold: number;\n minFrames: number;\n timeout: number;\n maxRetries?: number;\n}\n\nexport interface WsTransportState {\n overlapSpeechStarted: boolean;\n overlapSpeechStartedAt: number | undefined;\n cache: BoundedCache<number, InterruptionCacheEntry>;\n}\n\nconst wsMessageSchema = z.discriminatedUnion('type', [\n z.object({\n type: z.literal(MSG_SESSION_CREATED),\n }),\n z.object({\n type: z.literal(MSG_SESSION_CLOSED),\n }),\n z.object({\n type: z.literal(MSG_INTERRUPTION_DETECTED),\n created_at: z.number(),\n probabilities: z.array(z.number()).default([]),\n prediction_duration: z.number().default(0),\n }),\n z.object({\n type: z.literal(MSG_INFERENCE_DONE),\n created_at: z.number(),\n probabilities: z.array(z.number()).default([]),\n prediction_duration: z.number().default(0),\n is_bargein: z.boolean().optional(),\n }),\n z.object({\n type: z.literal(MSG_ERROR),\n message: z.string(),\n code: z.number().optional(),\n session_id: z.string().optional(),\n }),\n]);\n\ntype WsMessage = z.infer<typeof wsMessageSchema>;\n\n/**\n * Creates a WebSocket connection and waits for it to open.\n */\nasync function connectWebSocket(options: WsTransportOptions): Promise<WebSocket> {\n const baseUrl = options.baseUrl.replace(/^http/, 'ws');\n const token = await createAccessToken(options.apiKey, options.apiSecret);\n const url = `${baseUrl}/bargein`;\n\n const ws = new WebSocket(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n ws.terminate();\n reject(\n new APITimeoutError({\n message: 'WebSocket connection timeout',\n options: { retryable: false },\n }),\n );\n }, options.timeout);\n ws.once('open', () => {\n clearTimeout(timeout);\n resolve();\n });\n ws.once('unexpected-response', (_req, res) => {\n clearTimeout(timeout);\n ws.terminate();\n const statusCode = res.statusCode ?? -1;\n reject(\n new APIStatusError({\n message: `WebSocket connection rejected with status ${statusCode}`,\n options: { statusCode, retryable: false },\n }),\n );\n });\n ws.once('error', (err: Error) => {\n clearTimeout(timeout);\n ws.terminate();\n reject(new APIConnectionError({ message: `WebSocket connection error: ${err.message}` }));\n });\n });\n\n return ws;\n}\n\nexport interface WsTransportResult {\n transport: TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent>;\n reconnect: () => Promise<void>;\n}\n\n/**\n * Creates a WebSocket transport TransformStream for interruption detection.\n *\n * This transport receives Int16Array audio slices and outputs InterruptionEvents.\n * It maintains a persistent WebSocket connection with automatic retry on failure.\n * Returns both the transport and a reconnect function for option updates.\n */\nexport function createWsTransport(\n options: WsTransportOptions,\n getState: () => WsTransportState,\n setState: (partial: Partial<WsTransportState>) => void,\n updateUserSpeakingSpan?: (entry: InterruptionCacheEntry) => void,\n onRequestSent?: () => void,\n getAndResetNumRequests?: () => number,\n): WsTransportResult {\n const logger = log();\n let ws: WebSocket | null = null;\n let outputController: TransformStreamDefaultController<OverlappingSpeechEvent> | null = null;\n\n function setupMessageHandler(socket: WebSocket): void {\n socket.on('message', (data: WebSocket.Data) => {\n try {\n const message = wsMessageSchema.parse(JSON.parse(data.toString()));\n handleMessage(message);\n } catch {\n logger.warn({ data: data.toString() }, 'Failed to parse WebSocket message');\n }\n });\n\n socket.on('error', (err: Error) => {\n outputController?.error(\n new APIConnectionError({ message: `WebSocket error: ${err.message}` }),\n );\n });\n\n socket.on('close', (code: number, reason: Buffer) => {\n logger.debug({ code, reason: reason.toString() }, 'WebSocket closed');\n });\n }\n\n async function ensureConnection(): Promise<void> {\n if (ws && ws.readyState === WebSocket.OPEN) return;\n\n ws = await connectWebSocket(options);\n setupMessageHandler(ws);\n\n const sessionCreateMsg = JSON.stringify({\n type: MSG_SESSION_CREATE,\n settings: {\n sample_rate: options.sampleRate,\n num_channels: 1,\n threshold: options.threshold,\n min_frames: options.minFrames,\n encoding: 's16le',\n },\n });\n ws.send(sessionCreateMsg);\n }\n\n function handleMessage(message: WsMessage): void {\n const state = getState();\n\n switch (message.type) {\n case MSG_SESSION_CREATED:\n logger.debug('WebSocket session created');\n break;\n\n case MSG_INTERRUPTION_DETECTED: {\n const createdAt = message.created_at;\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (state.overlapSpeechStarted && overlapSpeechStartedAt !== undefined) {\n const existing = state.cache.get(createdAt);\n\n const totalDurationInS =\n existing?.requestStartedAt !== undefined\n ? (performance.now() - existing.requestStartedAt) / 1000\n : (performance.now() - createdAt) / 1000;\n\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n speechInput: existing?.speechInput,\n requestStartedAt: existing?.requestStartedAt,\n totalDurationInS,\n probabilities: message.probabilities,\n isInterruption: true,\n predictionDurationInS: message.prediction_duration,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n },\n );\n\n if (updateUserSpeakingSpan) {\n updateUserSpeakingSpan(entry);\n }\n\n logger.debug(\n {\n totalDuration: entry.totalDurationInS,\n predictionDuration: entry.predictionDurationInS,\n detectionDelay: entry.detectionDelayInS,\n probability: entry.probability,\n },\n 'interruption detected',\n );\n\n const event: OverlappingSpeechEvent = {\n type: 'overlapping_speech',\n detectedAt: Date.now(),\n isInterruption: true,\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n overlapStartedAt: overlapSpeechStartedAt,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n detectionDelayInS: entry.detectionDelayInS,\n probability: entry.probability,\n numRequests: getAndResetNumRequests?.() ?? 0,\n };\n\n outputController?.enqueue(event);\n setState({ overlapSpeechStarted: false });\n }\n break;\n }\n\n case MSG_INFERENCE_DONE: {\n const createdAt = message.created_at;\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (state.overlapSpeechStarted && overlapSpeechStartedAt !== undefined) {\n const existing = state.cache.get(createdAt);\n const totalDurationInS =\n existing?.requestStartedAt !== undefined\n ? (performance.now() - existing.requestStartedAt) / 1000\n : (performance.now() - createdAt) / 1000;\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n speechInput: existing?.speechInput,\n requestStartedAt: existing?.requestStartedAt,\n totalDurationInS,\n predictionDurationInS: message.prediction_duration,\n probabilities: message.probabilities,\n isInterruption: message.is_bargein ?? false,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n },\n );\n\n logger.debug(\n {\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n },\n 'interruption inference done',\n );\n }\n break;\n }\n\n case MSG_SESSION_CLOSED:\n logger.debug('WebSocket session closed');\n break;\n\n case MSG_ERROR:\n outputController?.error(\n new APIStatusError({\n message: `LiveKit Adaptive Interruption error: ${message.message}`,\n options: { statusCode: message.code ?? -1 },\n }),\n );\n break;\n }\n }\n\n function sendAudioData(audioSlice: Int16Array): void {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket not connected' });\n }\n\n const state = getState();\n const createdAt = Math.floor(performance.now());\n\n state.cache.set(\n createdAt,\n new InterruptionCacheEntry({\n createdAt,\n requestStartedAt: performance.now(),\n speechInput: audioSlice,\n }),\n );\n\n const header = new ArrayBuffer(8);\n const view = new DataView(header);\n view.setUint32(0, createdAt >>> 0, true);\n view.setUint32(4, Math.floor(createdAt / 0x100000000) >>> 0, true);\n\n const audioBytes = new Uint8Array(\n audioSlice.buffer,\n audioSlice.byteOffset,\n audioSlice.byteLength,\n );\n const combined = new Uint8Array(8 + audioBytes.length);\n combined.set(new Uint8Array(header), 0);\n combined.set(audioBytes, 8);\n\n ws.send(combined);\n onRequestSent?.();\n }\n\n function close(): void {\n if (ws?.readyState === WebSocket.OPEN) {\n const closeMsg = JSON.stringify({ type: MSG_SESSION_CLOSE });\n try {\n ws.send(closeMsg);\n } catch (e: unknown) {\n logger.error(e, 'failed to send close message');\n }\n }\n ws?.close(1000); // signal normal websocket closure\n ws = null;\n }\n\n /**\n * Reconnect the WebSocket with updated options.\n * This is called when options are updated via updateOptions().\n */\n async function reconnect(): Promise<void> {\n close();\n }\n\n const transport = new TransformStream<\n Int16Array | OverlappingSpeechEvent,\n OverlappingSpeechEvent\n >(\n {\n async start(controller) {\n outputController = controller;\n await ensureConnection();\n },\n\n transform(chunk, controller) {\n if (!(chunk instanceof Int16Array)) {\n controller.enqueue(chunk);\n return;\n }\n\n // Only forwards buffered audio while overlap speech is actively on.\n const state = getState();\n if (!state.overlapSpeechStartedAt || !state.overlapSpeechStarted) return;\n\n if (options.timeout > 0) {\n const now = performance.now();\n for (const [, entry] of state.cache.entries()) {\n if (entry.totalDurationInS !== 0) continue;\n if (now - entry.createdAt > options.timeout) {\n controller.error(\n new APIStatusError({\n message: `interruption inference timed out after ${((now - entry.createdAt) / 1000).toFixed(1)}s (ws)`,\n options: { statusCode: 408, retryable: false },\n }),\n );\n return;\n }\n break;\n }\n }\n\n try {\n sendAudioData(chunk);\n } catch (err) {\n controller.error(err);\n }\n },\n\n flush() {\n close();\n },\n },\n { highWaterMark: 2 },\n { highWaterMark: 2 },\n );\n\n return { transport, reconnect };\n}\n"],"mappings":"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","names":["WebSocket"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/inference/interruption/ws_transport.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Throws } from '@livekit/throws-transformer/throws';\nimport { TransformStream } from 'stream/web';\nimport WebSocket from 'ws';\nimport { z } from 'zod';\nimport { APIConnectionError, APIStatusError, APITimeoutError } from '../../_exceptions.js';\nimport { log } from '../../log.js';\nimport TypedPromise from '../../typed_promise.js';\nimport { createAccessToken } from '../utils.js';\nimport { InterruptionCacheEntry } from './interruption_cache_entry.js';\nimport type { OverlappingSpeechEvent } from './types.js';\nimport type { BoundedCache } from './utils.js';\n\n// WebSocket message types\nconst MSG_SESSION_CREATE = 'session.create';\nconst MSG_SESSION_CLOSE = 'session.close';\nconst MSG_SESSION_CREATED = 'session.created';\nconst MSG_SESSION_CLOSED = 'session.closed';\nconst MSG_INTERRUPTION_DETECTED = 'bargein_detected';\nconst MSG_INFERENCE_DONE = 'inference_done';\nconst MSG_ERROR = 'error';\n\nexport interface WsTransportOptions {\n baseUrl: string;\n apiKey: string;\n apiSecret: string;\n sampleRate: number;\n threshold: number;\n minFrames: number;\n timeout: number;\n maxRetries?: number;\n}\n\nexport interface WsTransportState {\n overlapSpeechStarted: boolean;\n overlapSpeechStartedAt: number | undefined;\n cache: BoundedCache<number, InterruptionCacheEntry>;\n}\n\nconst wsMessageSchema = z.discriminatedUnion('type', [\n z.object({\n type: z.literal(MSG_SESSION_CREATED),\n }),\n z.object({\n type: z.literal(MSG_SESSION_CLOSED),\n }),\n z.object({\n type: z.literal(MSG_INTERRUPTION_DETECTED),\n created_at: z.number(),\n probabilities: z.array(z.number()).default([]),\n prediction_duration: z.number().default(0),\n }),\n z.object({\n type: z.literal(MSG_INFERENCE_DONE),\n created_at: z.number(),\n probabilities: z.array(z.number()).default([]),\n prediction_duration: z.number().default(0),\n is_bargein: z.boolean().optional(),\n }),\n z.object({\n type: z.literal(MSG_ERROR),\n message: z.string(),\n code: z.number().optional(),\n session_id: z.string().optional(),\n }),\n]);\n\ntype WsMessage = z.infer<typeof wsMessageSchema>;\n\n/**\n * Creates a WebSocket connection and waits for it to open.\n */\nasync function connectWebSocket(\n options: WsTransportOptions,\n): Promise<Throws<WebSocket, APIStatusError | APITimeoutError | APIConnectionError>> {\n const baseUrl = options.baseUrl.replace(/^http/, 'ws');\n const token = await createAccessToken(options.apiKey, options.apiSecret);\n const url = `${baseUrl}/bargein`;\n\n const ws = new WebSocket(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n await new TypedPromise<void, APIStatusError | APITimeoutError | APIConnectionError>(\n (resolve, reject) => {\n const timeout = setTimeout(() => {\n ws.terminate();\n reject(\n new APITimeoutError({\n message: 'WebSocket connection timeout',\n options: { retryable: false },\n }),\n );\n }, options.timeout);\n ws.once('open', () => {\n clearTimeout(timeout);\n resolve();\n });\n ws.once('unexpected-response', (_req, res) => {\n clearTimeout(timeout);\n ws.terminate();\n const statusCode = res.statusCode ?? -1;\n reject(\n new APIStatusError({\n message: `WebSocket connection rejected with status ${statusCode}`,\n options: { statusCode, retryable: false },\n }),\n );\n });\n ws.once('error', (err: Error) => {\n clearTimeout(timeout);\n ws.terminate();\n reject(new APIConnectionError({ message: `WebSocket connection error: ${err.message}` }));\n });\n },\n );\n\n return ws;\n}\n\nexport interface WsTransportResult {\n transport: TransformStream<Int16Array | OverlappingSpeechEvent, OverlappingSpeechEvent>;\n reconnect: () => Promise<void>;\n}\n\n/**\n * Creates a WebSocket transport TransformStream for interruption detection.\n *\n * This transport receives Int16Array audio slices and outputs InterruptionEvents.\n * It maintains a persistent WebSocket connection with automatic retry on failure.\n * Returns both the transport and a reconnect function for option updates.\n */\nexport function createWsTransport(\n options: WsTransportOptions,\n getState: () => WsTransportState,\n setState: (partial: Partial<WsTransportState>) => void,\n updateUserSpeakingSpan?: (entry: InterruptionCacheEntry) => void,\n onRequestSent?: () => void,\n getAndResetNumRequests?: () => number,\n): WsTransportResult {\n const logger = log();\n let ws: WebSocket | null = null;\n let outputController: TransformStreamDefaultController<OverlappingSpeechEvent> | null = null;\n\n function setupMessageHandler(socket: WebSocket): void {\n socket.on('message', (data: WebSocket.Data) => {\n try {\n const message = wsMessageSchema.parse(JSON.parse(data.toString()));\n handleMessage(message);\n } catch {\n logger.warn({ data: data.toString() }, 'Failed to parse WebSocket message');\n }\n });\n\n socket.on('error', (err: Error) => {\n outputController?.error(\n new APIConnectionError({ message: `WebSocket error: ${err.message}` }),\n );\n });\n\n socket.on('close', (code: number, reason: Buffer) => {\n logger.debug({ code, reason: reason.toString() }, 'WebSocket closed');\n });\n }\n\n async function ensureConnection(): Promise<\n Throws<void, APIStatusError | APITimeoutError | APIConnectionError>\n > {\n if (ws && ws.readyState === WebSocket.OPEN) return;\n\n ws = await connectWebSocket(options);\n setupMessageHandler(ws);\n\n const sessionCreateMsg = JSON.stringify({\n type: MSG_SESSION_CREATE,\n settings: {\n sample_rate: options.sampleRate,\n num_channels: 1,\n threshold: options.threshold,\n min_frames: options.minFrames,\n encoding: 's16le',\n },\n });\n ws.send(sessionCreateMsg);\n }\n\n function handleMessage(message: WsMessage): void {\n const state = getState();\n\n switch (message.type) {\n case MSG_SESSION_CREATED:\n logger.debug('WebSocket session created');\n break;\n\n case MSG_INTERRUPTION_DETECTED: {\n const createdAt = message.created_at;\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (state.overlapSpeechStarted && overlapSpeechStartedAt !== undefined) {\n const existing = state.cache.get(createdAt);\n\n const totalDurationInS =\n existing?.requestStartedAt !== undefined\n ? (performance.now() - existing.requestStartedAt) / 1000\n : (performance.now() - createdAt) / 1000;\n\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n speechInput: existing?.speechInput,\n requestStartedAt: existing?.requestStartedAt,\n totalDurationInS,\n probabilities: message.probabilities,\n isInterruption: true,\n predictionDurationInS: message.prediction_duration,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n },\n );\n\n if (updateUserSpeakingSpan) {\n updateUserSpeakingSpan(entry);\n }\n\n logger.debug(\n {\n totalDuration: entry.totalDurationInS,\n predictionDuration: entry.predictionDurationInS,\n detectionDelay: entry.detectionDelayInS,\n probability: entry.probability,\n },\n 'interruption detected',\n );\n\n const event: OverlappingSpeechEvent = {\n type: 'overlapping_speech',\n detectedAt: Date.now(),\n isInterruption: true,\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n overlapStartedAt: overlapSpeechStartedAt,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n detectionDelayInS: entry.detectionDelayInS,\n probability: entry.probability,\n numRequests: getAndResetNumRequests?.() ?? 0,\n };\n\n outputController?.enqueue(event);\n setState({ overlapSpeechStarted: false });\n }\n break;\n }\n\n case MSG_INFERENCE_DONE: {\n const createdAt = message.created_at;\n const overlapSpeechStartedAt = state.overlapSpeechStartedAt;\n if (state.overlapSpeechStarted && overlapSpeechStartedAt !== undefined) {\n const existing = state.cache.get(createdAt);\n const totalDurationInS =\n existing?.requestStartedAt !== undefined\n ? (performance.now() - existing.requestStartedAt) / 1000\n : (performance.now() - createdAt) / 1000;\n const entry = state.cache.setOrUpdate(\n createdAt,\n () => new InterruptionCacheEntry({ createdAt }),\n {\n speechInput: existing?.speechInput,\n requestStartedAt: existing?.requestStartedAt,\n totalDurationInS,\n predictionDurationInS: message.prediction_duration,\n probabilities: message.probabilities,\n isInterruption: message.is_bargein ?? false,\n detectionDelayInS: (Date.now() - overlapSpeechStartedAt) / 1000,\n },\n );\n\n logger.debug(\n {\n totalDurationInS: entry.totalDurationInS,\n predictionDurationInS: entry.predictionDurationInS,\n },\n 'interruption inference done',\n );\n }\n break;\n }\n\n case MSG_SESSION_CLOSED:\n logger.debug('WebSocket session closed');\n break;\n\n case MSG_ERROR:\n outputController?.error(\n new APIStatusError({\n message: `LiveKit Adaptive Interruption error: ${message.message}`,\n options: { statusCode: message.code ?? -1 },\n }),\n );\n break;\n }\n }\n\n function sendAudioData(audioSlice: Int16Array): void {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new APIConnectionError({ message: 'WebSocket not connected' });\n }\n\n const state = getState();\n const createdAt = Math.floor(performance.now());\n\n state.cache.set(\n createdAt,\n new InterruptionCacheEntry({\n createdAt,\n requestStartedAt: performance.now(),\n speechInput: audioSlice,\n }),\n );\n\n const header = new ArrayBuffer(8);\n const view = new DataView(header);\n view.setUint32(0, createdAt >>> 0, true);\n view.setUint32(4, Math.floor(createdAt / 0x100000000) >>> 0, true);\n\n const audioBytes = new Uint8Array(\n audioSlice.buffer,\n audioSlice.byteOffset,\n audioSlice.byteLength,\n );\n const combined = new Uint8Array(8 + audioBytes.length);\n combined.set(new Uint8Array(header), 0);\n combined.set(audioBytes, 8);\n\n ws.send(combined);\n onRequestSent?.();\n }\n\n function close(): void {\n if (ws?.readyState === WebSocket.OPEN) {\n const closeMsg = JSON.stringify({ type: MSG_SESSION_CLOSE });\n try {\n ws.send(closeMsg);\n } catch (e: unknown) {\n logger.error(e, 'failed to send close message');\n }\n }\n ws?.close(1000); // signal normal websocket closure\n ws = null;\n }\n\n /**\n * Reconnect the WebSocket with updated options.\n * This is called when options are updated via updateOptions().\n */\n async function reconnect(): Promise<void> {\n close();\n }\n\n const transport = new TransformStream<\n Int16Array | OverlappingSpeechEvent,\n OverlappingSpeechEvent\n >(\n {\n async start(controller) {\n outputController = controller;\n await ensureConnection().catch((e) => {\n controller.error(e);\n });\n },\n\n transform(chunk, controller) {\n if (!(chunk instanceof Int16Array)) {\n controller.enqueue(chunk);\n return;\n }\n\n // Only forwards buffered audio while overlap speech is actively on.\n const state = getState();\n if (!state.overlapSpeechStartedAt || !state.overlapSpeechStarted) return;\n\n if (options.timeout > 0) {\n const now = performance.now();\n for (const [, entry] of state.cache.entries()) {\n if (entry.totalDurationInS !== 0) continue;\n if (now - entry.createdAt > options.timeout) {\n controller.error(\n new APIStatusError({\n message: `interruption inference timed out after ${((now - entry.createdAt) / 1000).toFixed(1)}s (ws)`,\n options: { statusCode: 408, retryable: false },\n }),\n );\n return;\n }\n break;\n }\n }\n\n try {\n sendAudioData(chunk);\n } catch (err) {\n controller.error(err);\n }\n },\n\n flush() {\n close();\n },\n },\n { highWaterMark: 2 },\n { highWaterMark: 2 },\n );\n\n return { transport, reconnect };\n}\n"],"mappings":"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","names":["WebSocket","TypedPromise"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_transport.d.ts","sourceRoot":"","sources":["../../../src/inference/interruption/ws_transport.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ws_transport.d.ts","sourceRoot":"","sources":["../../../src/inference/interruption/ws_transport.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAO7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAW/C,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACrD;AAmFD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,eAAe,CAAC,UAAU,GAAG,sBAAsB,EAAE,sBAAsB,CAAC,CAAC;IACxF,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,gBAAgB,EAChC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,IAAI,EACtD,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,EAChE,aAAa,CAAC,EAAE,MAAM,IAAI,EAC1B,sBAAsB,CAAC,EAAE,MAAM,MAAM,GACpC,iBAAiB,CAkRnB"}
|