@livekit/agents 1.0.0-next.0 → 1.0.0-next.2

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.
Files changed (65) hide show
  1. package/dist/llm/llm.cjs +1 -2
  2. package/dist/llm/llm.cjs.map +1 -1
  3. package/dist/llm/llm.d.ts.map +1 -1
  4. package/dist/llm/llm.js +1 -2
  5. package/dist/llm/llm.js.map +1 -1
  6. package/dist/llm/utils.cjs +5 -4
  7. package/dist/llm/utils.cjs.map +1 -1
  8. package/dist/llm/utils.d.cts +2 -2
  9. package/dist/llm/utils.d.ts +2 -2
  10. package/dist/llm/utils.d.ts.map +1 -1
  11. package/dist/llm/utils.js +5 -4
  12. package/dist/llm/utils.js.map +1 -1
  13. package/dist/stream/deferred_stream.test.cjs +10 -10
  14. package/dist/stream/deferred_stream.test.cjs.map +1 -1
  15. package/dist/stream/deferred_stream.test.js +1 -1
  16. package/dist/stream/deferred_stream.test.js.map +1 -1
  17. package/dist/stt/stt.cjs +1 -2
  18. package/dist/stt/stt.cjs.map +1 -1
  19. package/dist/stt/stt.d.ts.map +1 -1
  20. package/dist/stt/stt.js +1 -2
  21. package/dist/stt/stt.js.map +1 -1
  22. package/dist/tts/tts.cjs +2 -3
  23. package/dist/tts/tts.cjs.map +1 -1
  24. package/dist/tts/tts.d.ts.map +1 -1
  25. package/dist/tts/tts.js +1 -2
  26. package/dist/tts/tts.js.map +1 -1
  27. package/dist/utils.cjs +19 -2
  28. package/dist/utils.cjs.map +1 -1
  29. package/dist/utils.d.cts +11 -0
  30. package/dist/utils.d.ts +11 -0
  31. package/dist/utils.d.ts.map +1 -1
  32. package/dist/utils.js +17 -1
  33. package/dist/utils.js.map +1 -1
  34. package/dist/utils.test.cjs +30 -31
  35. package/dist/utils.test.cjs.map +1 -1
  36. package/dist/utils.test.js +1 -1
  37. package/dist/utils.test.js.map +1 -1
  38. package/dist/voice/agent_session.cjs +3 -0
  39. package/dist/voice/agent_session.cjs.map +1 -1
  40. package/dist/voice/agent_session.d.cts +1 -0
  41. package/dist/voice/agent_session.d.ts +1 -0
  42. package/dist/voice/agent_session.d.ts.map +1 -1
  43. package/dist/voice/agent_session.js +3 -0
  44. package/dist/voice/agent_session.js.map +1 -1
  45. package/dist/voice/audio_recognition.cjs +2 -3
  46. package/dist/voice/audio_recognition.cjs.map +1 -1
  47. package/dist/voice/audio_recognition.d.ts.map +1 -1
  48. package/dist/voice/audio_recognition.js +1 -2
  49. package/dist/voice/audio_recognition.js.map +1 -1
  50. package/dist/voice/transcription/synchronizer.cjs +1 -2
  51. package/dist/voice/transcription/synchronizer.cjs.map +1 -1
  52. package/dist/voice/transcription/synchronizer.d.ts.map +1 -1
  53. package/dist/voice/transcription/synchronizer.js +1 -2
  54. package/dist/voice/transcription/synchronizer.js.map +1 -1
  55. package/package.json +1 -2
  56. package/src/llm/llm.ts +1 -2
  57. package/src/llm/utils.ts +9 -5
  58. package/src/stream/deferred_stream.test.ts +1 -1
  59. package/src/stt/stt.ts +1 -2
  60. package/src/tts/tts.ts +1 -2
  61. package/src/utils.test.ts +1 -1
  62. package/src/utils.ts +28 -1
  63. package/src/voice/agent_session.ts +4 -0
  64. package/src/voice/audio_recognition.ts +1 -2
  65. package/src/voice/transcription/synchronizer.ts +1 -2
package/dist/llm/llm.cjs CHANGED
@@ -22,7 +22,6 @@ __export(llm_exports, {
22
22
  LLMStream: () => LLMStream
23
23
  });
24
24
  module.exports = __toCommonJS(llm_exports);
25
- var import_async = require("@std/async");
26
25
  var import_node_events = require("node:events");
27
26
  var import_exceptions = require("../_exceptions.cjs");
28
27
  var import_log = require("../log.cjs");
@@ -101,7 +100,7 @@ class LLMStream {
101
100
  );
102
101
  }
103
102
  if (retryInterval > 0) {
104
- await (0, import_async.delay)(retryInterval);
103
+ await (0, import_utils.delay)(retryInterval);
105
104
  }
106
105
  } else {
107
106
  this.emitError({ error: (0, import_utils.toError)(error), recoverable: false });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/llm/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { delay } from '@std/async';\nimport { EventEmitter } from 'node:events';\nimport { APIConnectionError, APIError } from '../_exceptions.js';\nimport { log } from '../log.js';\nimport type { LLMMetrics } from '../metrics/base.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { AsyncIterableQueue, startSoon, toError } from '../utils.js';\nimport { type ChatContext, type ChatRole, type FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport interface ChoiceDelta {\n role: ChatRole;\n content?: string;\n toolCalls?: FunctionCall[];\n}\n\nexport interface CompletionUsage {\n completionTokens: number;\n promptTokens: number;\n promptCachedTokens: number;\n totalTokens: number;\n}\n\nexport interface ChatChunk {\n id: string;\n delta?: ChoiceDelta;\n usage?: CompletionUsage;\n}\n\nexport interface LLMError {\n type: 'llm_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport type LLMCallbacks = {\n ['metrics_collected']: (metrics: LLMMetrics) => void;\n ['error']: (error: LLMError) => void;\n};\n\nexport abstract class LLM extends (EventEmitter as new () => TypedEmitter<LLMCallbacks>) {\n constructor() {\n super();\n }\n\n abstract label(): string;\n\n /**\n * Get the model name/identifier for this LLM instance.\n *\n * @returns The model name if available, \"unknown\" otherwise.\n *\n * @remarks\n * Plugins should override this property to provide their model information.\n */\n get model(): string {\n return 'unknown';\n }\n\n /**\n * Returns a {@link LLMStream} that can be used to push text and receive LLM responses.\n */\n abstract chat({\n chatCtx,\n toolCtx,\n connOptions,\n parallelToolCalls,\n toolChoice,\n extraKwargs,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, any>;\n }): LLMStream;\n\n /**\n * Pre-warm connection to the LLM service\n */\n prewarm(): void {\n // Default implementation - subclasses can override\n }\n\n async aclose(): Promise<void> {\n // Default implementation - subclasses can override\n }\n}\n\nexport abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {\n protected output = new AsyncIterableQueue<ChatChunk>();\n protected queue = new AsyncIterableQueue<ChatChunk>();\n protected closed = false;\n protected abortController = new AbortController();\n protected _connOptions: APIConnectOptions;\n protected logger = log();\n\n #llm: LLM;\n #chatCtx: ChatContext;\n #toolCtx?: ToolContext;\n\n constructor(\n llm: LLM,\n {\n chatCtx,\n toolCtx,\n connOptions,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions: APIConnectOptions;\n },\n ) {\n this.#llm = llm;\n this.#chatCtx = chatCtx;\n this.#toolCtx = toolCtx;\n this._connOptions = connOptions;\n this.monitorMetrics();\n this.abortController.signal.addEventListener('abort', () => {\n // TODO (AJS-37) clean this up when we refactor with streams\n this.output.close();\n this.closed = true;\n });\n\n // this is a hack to immitate asyncio.create_task so that mainTask\n // is run **after** the constructor has finished. Otherwise we get\n // runtime error when trying to access class variables in the\n // `run` method.\n startSoon(() => this.mainTask().then(() => this.queue.close()));\n }\n\n private async mainTask() {\n for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {\n try {\n return await this.run();\n } catch (error) {\n if (error instanceof APIError) {\n const retryInterval = this._connOptions._intervalForRetry(i);\n\n if (this._connOptions.maxRetry === 0 || !error.retryable) {\n this.emitError({ error, recoverable: false });\n throw error;\n } else if (i === this._connOptions.maxRetry) {\n this.emitError({ error, recoverable: false });\n throw new APIConnectionError({\n message: `failed to generate LLM completion after ${this._connOptions.maxRetry + 1} attempts`,\n options: { retryable: false },\n });\n } else {\n this.emitError({ error, recoverable: true });\n this.logger.warn(\n { llm: this.#llm.label(), attempt: i + 1, error },\n `failed to generate LLM completion, retrying in ${retryInterval}s`,\n );\n }\n\n if (retryInterval > 0) {\n await delay(retryInterval);\n }\n } else {\n this.emitError({ error: toError(error), recoverable: false });\n throw error;\n }\n }\n }\n }\n\n private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {\n this.#llm.emit('error', {\n type: 'llm_error',\n timestamp: Date.now(),\n label: this.#llm.label(),\n error,\n recoverable,\n });\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let ttft: bigint = BigInt(-1);\n let requestId = '';\n let usage: CompletionUsage | undefined;\n\n for await (const ev of this.queue) {\n if (this.abortController.signal.aborted) {\n break;\n }\n this.output.put(ev);\n requestId = ev.id;\n if (ttft === BigInt(-1)) {\n ttft = process.hrtime.bigint() - startTime;\n }\n if (ev.usage) {\n usage = ev.usage;\n }\n }\n this.output.close();\n\n const duration = process.hrtime.bigint() - startTime;\n const metrics: LLMMetrics = {\n type: 'llm_metrics',\n timestamp: Date.now(),\n requestId,\n ttft: ttft === BigInt(-1) ? -1 : Math.trunc(Number(ttft / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n cancelled: this.abortController.signal.aborted,\n label: this.#llm.label(),\n completionTokens: usage?.completionTokens || 0,\n promptTokens: usage?.promptTokens || 0,\n promptCachedTokens: usage?.promptCachedTokens || 0,\n totalTokens: usage?.totalTokens || 0,\n tokensPerSecond:\n (usage?.completionTokens || 0) / Math.trunc(Number(duration / BigInt(1000000000))),\n };\n this.#llm.emit('metrics_collected', metrics);\n }\n\n protected abstract run(): Promise<void>;\n\n /** The function context of this stream. */\n get toolCtx(): ToolContext | undefined {\n return this.#toolCtx;\n }\n\n /** The initial chat context of this stream. */\n get chatCtx(): ChatContext {\n return this.#chatCtx;\n }\n\n /** The connection options for this stream. */\n get connOptions(): APIConnectOptions {\n return this._connOptions;\n }\n\n next(): Promise<IteratorResult<ChatChunk>> {\n return this.output.next();\n }\n\n close() {\n this.abortController.abort();\n }\n\n [Symbol.asyncIterator](): LLMStream {\n return this;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/llm/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport { APIConnectionError, APIError } from '../_exceptions.js';\nimport { log } from '../log.js';\nimport type { LLMMetrics } from '../metrics/base.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { AsyncIterableQueue, delay, startSoon, toError } from '../utils.js';\nimport { type ChatContext, type ChatRole, type FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport interface ChoiceDelta {\n role: ChatRole;\n content?: string;\n toolCalls?: FunctionCall[];\n}\n\nexport interface CompletionUsage {\n completionTokens: number;\n promptTokens: number;\n promptCachedTokens: number;\n totalTokens: number;\n}\n\nexport interface ChatChunk {\n id: string;\n delta?: ChoiceDelta;\n usage?: CompletionUsage;\n}\n\nexport interface LLMError {\n type: 'llm_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport type LLMCallbacks = {\n ['metrics_collected']: (metrics: LLMMetrics) => void;\n ['error']: (error: LLMError) => void;\n};\n\nexport abstract class LLM extends (EventEmitter as new () => TypedEmitter<LLMCallbacks>) {\n constructor() {\n super();\n }\n\n abstract label(): string;\n\n /**\n * Get the model name/identifier for this LLM instance.\n *\n * @returns The model name if available, \"unknown\" otherwise.\n *\n * @remarks\n * Plugins should override this property to provide their model information.\n */\n get model(): string {\n return 'unknown';\n }\n\n /**\n * Returns a {@link LLMStream} that can be used to push text and receive LLM responses.\n */\n abstract chat({\n chatCtx,\n toolCtx,\n connOptions,\n parallelToolCalls,\n toolChoice,\n extraKwargs,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, any>;\n }): LLMStream;\n\n /**\n * Pre-warm connection to the LLM service\n */\n prewarm(): void {\n // Default implementation - subclasses can override\n }\n\n async aclose(): Promise<void> {\n // Default implementation - subclasses can override\n }\n}\n\nexport abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {\n protected output = new AsyncIterableQueue<ChatChunk>();\n protected queue = new AsyncIterableQueue<ChatChunk>();\n protected closed = false;\n protected abortController = new AbortController();\n protected _connOptions: APIConnectOptions;\n protected logger = log();\n\n #llm: LLM;\n #chatCtx: ChatContext;\n #toolCtx?: ToolContext;\n\n constructor(\n llm: LLM,\n {\n chatCtx,\n toolCtx,\n connOptions,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions: APIConnectOptions;\n },\n ) {\n this.#llm = llm;\n this.#chatCtx = chatCtx;\n this.#toolCtx = toolCtx;\n this._connOptions = connOptions;\n this.monitorMetrics();\n this.abortController.signal.addEventListener('abort', () => {\n // TODO (AJS-37) clean this up when we refactor with streams\n this.output.close();\n this.closed = true;\n });\n\n // this is a hack to immitate asyncio.create_task so that mainTask\n // is run **after** the constructor has finished. Otherwise we get\n // runtime error when trying to access class variables in the\n // `run` method.\n startSoon(() => this.mainTask().then(() => this.queue.close()));\n }\n\n private async mainTask() {\n for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {\n try {\n return await this.run();\n } catch (error) {\n if (error instanceof APIError) {\n const retryInterval = this._connOptions._intervalForRetry(i);\n\n if (this._connOptions.maxRetry === 0 || !error.retryable) {\n this.emitError({ error, recoverable: false });\n throw error;\n } else if (i === this._connOptions.maxRetry) {\n this.emitError({ error, recoverable: false });\n throw new APIConnectionError({\n message: `failed to generate LLM completion after ${this._connOptions.maxRetry + 1} attempts`,\n options: { retryable: false },\n });\n } else {\n this.emitError({ error, recoverable: true });\n this.logger.warn(\n { llm: this.#llm.label(), attempt: i + 1, error },\n `failed to generate LLM completion, retrying in ${retryInterval}s`,\n );\n }\n\n if (retryInterval > 0) {\n await delay(retryInterval);\n }\n } else {\n this.emitError({ error: toError(error), recoverable: false });\n throw error;\n }\n }\n }\n }\n\n private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {\n this.#llm.emit('error', {\n type: 'llm_error',\n timestamp: Date.now(),\n label: this.#llm.label(),\n error,\n recoverable,\n });\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let ttft: bigint = BigInt(-1);\n let requestId = '';\n let usage: CompletionUsage | undefined;\n\n for await (const ev of this.queue) {\n if (this.abortController.signal.aborted) {\n break;\n }\n this.output.put(ev);\n requestId = ev.id;\n if (ttft === BigInt(-1)) {\n ttft = process.hrtime.bigint() - startTime;\n }\n if (ev.usage) {\n usage = ev.usage;\n }\n }\n this.output.close();\n\n const duration = process.hrtime.bigint() - startTime;\n const metrics: LLMMetrics = {\n type: 'llm_metrics',\n timestamp: Date.now(),\n requestId,\n ttft: ttft === BigInt(-1) ? -1 : Math.trunc(Number(ttft / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n cancelled: this.abortController.signal.aborted,\n label: this.#llm.label(),\n completionTokens: usage?.completionTokens || 0,\n promptTokens: usage?.promptTokens || 0,\n promptCachedTokens: usage?.promptCachedTokens || 0,\n totalTokens: usage?.totalTokens || 0,\n tokensPerSecond:\n (usage?.completionTokens || 0) / Math.trunc(Number(duration / BigInt(1000000000))),\n };\n this.#llm.emit('metrics_collected', metrics);\n }\n\n protected abstract run(): Promise<void>;\n\n /** The function context of this stream. */\n get toolCtx(): ToolContext | undefined {\n return this.#toolCtx;\n }\n\n /** The initial chat context of this stream. */\n get chatCtx(): ChatContext {\n return this.#chatCtx;\n }\n\n /** The connection options for this stream. */\n get connOptions(): APIConnectOptions {\n return this._connOptions;\n }\n\n next(): Promise<IteratorResult<ChatChunk>> {\n return this.output.next();\n }\n\n close() {\n this.abortController.abort();\n }\n\n [Symbol.asyncIterator](): LLMStream {\n return this;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/llm/llm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/llm/llm.ts"],"names":[],"mappings":"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"}
package/dist/llm/llm.js CHANGED
@@ -1,8 +1,7 @@
1
- import { delay } from "@std/async";
2
1
  import { EventEmitter } from "node:events";
3
2
  import { APIConnectionError, APIError } from "../_exceptions.js";
4
3
  import { log } from "../log.js";
5
- import { AsyncIterableQueue, startSoon, toError } from "../utils.js";
4
+ import { AsyncIterableQueue, delay, startSoon, toError } from "../utils.js";
6
5
  import {} from "./chat_context.js";
7
6
  class LLM extends EventEmitter {
8
7
  constructor() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/llm/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { delay } from '@std/async';\nimport { EventEmitter } from 'node:events';\nimport { APIConnectionError, APIError } from '../_exceptions.js';\nimport { log } from '../log.js';\nimport type { LLMMetrics } from '../metrics/base.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { AsyncIterableQueue, startSoon, toError } from '../utils.js';\nimport { type ChatContext, type ChatRole, type FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport interface ChoiceDelta {\n role: ChatRole;\n content?: string;\n toolCalls?: FunctionCall[];\n}\n\nexport interface CompletionUsage {\n completionTokens: number;\n promptTokens: number;\n promptCachedTokens: number;\n totalTokens: number;\n}\n\nexport interface ChatChunk {\n id: string;\n delta?: ChoiceDelta;\n usage?: CompletionUsage;\n}\n\nexport interface LLMError {\n type: 'llm_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport type LLMCallbacks = {\n ['metrics_collected']: (metrics: LLMMetrics) => void;\n ['error']: (error: LLMError) => void;\n};\n\nexport abstract class LLM extends (EventEmitter as new () => TypedEmitter<LLMCallbacks>) {\n constructor() {\n super();\n }\n\n abstract label(): string;\n\n /**\n * Get the model name/identifier for this LLM instance.\n *\n * @returns The model name if available, \"unknown\" otherwise.\n *\n * @remarks\n * Plugins should override this property to provide their model information.\n */\n get model(): string {\n return 'unknown';\n }\n\n /**\n * Returns a {@link LLMStream} that can be used to push text and receive LLM responses.\n */\n abstract chat({\n chatCtx,\n toolCtx,\n connOptions,\n parallelToolCalls,\n toolChoice,\n extraKwargs,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, any>;\n }): LLMStream;\n\n /**\n * Pre-warm connection to the LLM service\n */\n prewarm(): void {\n // Default implementation - subclasses can override\n }\n\n async aclose(): Promise<void> {\n // Default implementation - subclasses can override\n }\n}\n\nexport abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {\n protected output = new AsyncIterableQueue<ChatChunk>();\n protected queue = new AsyncIterableQueue<ChatChunk>();\n protected closed = false;\n protected abortController = new AbortController();\n protected _connOptions: APIConnectOptions;\n protected logger = log();\n\n #llm: LLM;\n #chatCtx: ChatContext;\n #toolCtx?: ToolContext;\n\n constructor(\n llm: LLM,\n {\n chatCtx,\n toolCtx,\n connOptions,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions: APIConnectOptions;\n },\n ) {\n this.#llm = llm;\n this.#chatCtx = chatCtx;\n this.#toolCtx = toolCtx;\n this._connOptions = connOptions;\n this.monitorMetrics();\n this.abortController.signal.addEventListener('abort', () => {\n // TODO (AJS-37) clean this up when we refactor with streams\n this.output.close();\n this.closed = true;\n });\n\n // this is a hack to immitate asyncio.create_task so that mainTask\n // is run **after** the constructor has finished. Otherwise we get\n // runtime error when trying to access class variables in the\n // `run` method.\n startSoon(() => this.mainTask().then(() => this.queue.close()));\n }\n\n private async mainTask() {\n for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {\n try {\n return await this.run();\n } catch (error) {\n if (error instanceof APIError) {\n const retryInterval = this._connOptions._intervalForRetry(i);\n\n if (this._connOptions.maxRetry === 0 || !error.retryable) {\n this.emitError({ error, recoverable: false });\n throw error;\n } else if (i === this._connOptions.maxRetry) {\n this.emitError({ error, recoverable: false });\n throw new APIConnectionError({\n message: `failed to generate LLM completion after ${this._connOptions.maxRetry + 1} attempts`,\n options: { retryable: false },\n });\n } else {\n this.emitError({ error, recoverable: true });\n this.logger.warn(\n { llm: this.#llm.label(), attempt: i + 1, error },\n `failed to generate LLM completion, retrying in ${retryInterval}s`,\n );\n }\n\n if (retryInterval > 0) {\n await delay(retryInterval);\n }\n } else {\n this.emitError({ error: toError(error), recoverable: false });\n throw error;\n }\n }\n }\n }\n\n private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {\n this.#llm.emit('error', {\n type: 'llm_error',\n timestamp: Date.now(),\n label: this.#llm.label(),\n error,\n recoverable,\n });\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let ttft: bigint = BigInt(-1);\n let requestId = '';\n let usage: CompletionUsage | undefined;\n\n for await (const ev of this.queue) {\n if (this.abortController.signal.aborted) {\n break;\n }\n this.output.put(ev);\n requestId = ev.id;\n if (ttft === BigInt(-1)) {\n ttft = process.hrtime.bigint() - startTime;\n }\n if (ev.usage) {\n usage = ev.usage;\n }\n }\n this.output.close();\n\n const duration = process.hrtime.bigint() - startTime;\n const metrics: LLMMetrics = {\n type: 'llm_metrics',\n timestamp: Date.now(),\n requestId,\n ttft: ttft === BigInt(-1) ? -1 : Math.trunc(Number(ttft / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n cancelled: this.abortController.signal.aborted,\n label: this.#llm.label(),\n completionTokens: usage?.completionTokens || 0,\n promptTokens: usage?.promptTokens || 0,\n promptCachedTokens: usage?.promptCachedTokens || 0,\n totalTokens: usage?.totalTokens || 0,\n tokensPerSecond:\n (usage?.completionTokens || 0) / Math.trunc(Number(duration / BigInt(1000000000))),\n };\n this.#llm.emit('metrics_collected', metrics);\n }\n\n protected abstract run(): Promise<void>;\n\n /** The function context of this stream. */\n get toolCtx(): ToolContext | undefined {\n return this.#toolCtx;\n }\n\n /** The initial chat context of this stream. */\n get chatCtx(): ChatContext {\n return this.#chatCtx;\n }\n\n /** The connection options for this stream. */\n get connOptions(): APIConnectOptions {\n return this._connOptions;\n }\n\n next(): Promise<IteratorResult<ChatChunk>> {\n return this.output.next();\n }\n\n close() {\n this.abortController.abort();\n }\n\n [Symbol.asyncIterator](): LLMStream {\n return this;\n }\n}\n"],"mappings":"AAIA,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB,gBAAgB;AAC7C,SAAS,WAAW;AAGpB,SAAS,oBAAoB,WAAW,eAAe;AACvD,eAAmE;AAmC5D,MAAe,YAAa,aAAsD;AAAA,EACvF,cAAc;AACZ,UAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,IAAI,QAAgB;AAClB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAwBA,UAAgB;AAAA,EAEhB;AAAA,EAEA,MAAM,SAAwB;AAAA,EAE9B;AACF;AAEO,MAAe,UAAsD;AAAA,EAChE,SAAS,IAAI,mBAA8B;AAAA,EAC3C,QAAQ,IAAI,mBAA8B;AAAA,EAC1C,SAAS;AAAA,EACT,kBAAkB,IAAI,gBAAgB;AAAA,EACtC;AAAA,EACA,SAAS,IAAI;AAAA,EAEvB;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YACE,KACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKA;AACA,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,WAAW;AAChB,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,gBAAgB,OAAO,iBAAiB,SAAS,MAAM;AAE1D,WAAK,OAAO,MAAM;AAClB,WAAK,SAAS;AAAA,IAChB,CAAC;AAMD,cAAU,MAAM,KAAK,SAAS,EAAE,KAAK,MAAM,KAAK,MAAM,MAAM,CAAC,CAAC;AAAA,EAChE;AAAA,EAEA,MAAc,WAAW;AACvB,aAAS,IAAI,GAAG,IAAI,KAAK,aAAa,WAAW,GAAG,KAAK;AACvD,UAAI;AACF,eAAO,MAAM,KAAK,IAAI;AAAA,MACxB,SAAS,OAAO;AACd,YAAI,iBAAiB,UAAU;AAC7B,gBAAM,gBAAgB,KAAK,aAAa,kBAAkB,CAAC;AAE3D,cAAI,KAAK,aAAa,aAAa,KAAK,CAAC,MAAM,WAAW;AACxD,iBAAK,UAAU,EAAE,OAAO,aAAa,MAAM,CAAC;AAC5C,kBAAM;AAAA,UACR,WAAW,MAAM,KAAK,aAAa,UAAU;AAC3C,iBAAK,UAAU,EAAE,OAAO,aAAa,MAAM,CAAC;AAC5C,kBAAM,IAAI,mBAAmB;AAAA,cAC3B,SAAS,2CAA2C,KAAK,aAAa,WAAW,CAAC;AAAA,cAClF,SAAS,EAAE,WAAW,MAAM;AAAA,YAC9B,CAAC;AAAA,UACH,OAAO;AACL,iBAAK,UAAU,EAAE,OAAO,aAAa,KAAK,CAAC;AAC3C,iBAAK,OAAO;AAAA,cACV,EAAE,KAAK,KAAK,KAAK,MAAM,GAAG,SAAS,IAAI,GAAG,MAAM;AAAA,cAChD,kDAAkD,aAAa;AAAA,YACjE;AAAA,UACF;AAEA,cAAI,gBAAgB,GAAG;AACrB,kBAAM,MAAM,aAAa;AAAA,UAC3B;AAAA,QACF,OAAO;AACL,eAAK,UAAU,EAAE,OAAO,QAAQ,KAAK,GAAG,aAAa,MAAM,CAAC;AAC5D,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,UAAU,EAAE,OAAO,YAAY,GAA2C;AAChF,SAAK,KAAK,KAAK,SAAS;AAAA,MACtB,MAAM;AAAA,MACN,WAAW,KAAK,IAAI;AAAA,MACpB,OAAO,KAAK,KAAK,MAAM;AAAA,MACvB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAgB,iBAAiB;AAC/B,UAAM,YAAY,QAAQ,OAAO,OAAO;AACxC,QAAI,OAAe,OAAO,EAAE;AAC5B,QAAI,YAAY;AAChB,QAAI;AAEJ,qBAAiB,MAAM,KAAK,OAAO;AACjC,UAAI,KAAK,gBAAgB,OAAO,SAAS;AACvC;AAAA,MACF;AACA,WAAK,OAAO,IAAI,EAAE;AAClB,kBAAY,GAAG;AACf,UAAI,SAAS,OAAO,EAAE,GAAG;AACvB,eAAO,QAAQ,OAAO,OAAO,IAAI;AAAA,MACnC;AACA,UAAI,GAAG,OAAO;AACZ,gBAAQ,GAAG;AAAA,MACb;AAAA,IACF;AACA,SAAK,OAAO,MAAM;AAElB,UAAM,WAAW,QAAQ,OAAO,OAAO,IAAI;AAC3C,UAAM,UAAsB;AAAA,MAC1B,MAAM;AAAA,MACN,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,MACA,MAAM,SAAS,OAAO,EAAE,IAAI,KAAK,KAAK,MAAM,OAAO,OAAO,OAAO,GAAO,CAAC,CAAC;AAAA,MAC1E,UAAU,KAAK,MAAM,OAAO,WAAW,OAAO,GAAO,CAAC,CAAC;AAAA,MACvD,WAAW,KAAK,gBAAgB,OAAO;AAAA,MACvC,OAAO,KAAK,KAAK,MAAM;AAAA,MACvB,mBAAkB,+BAAO,qBAAoB;AAAA,MAC7C,eAAc,+BAAO,iBAAgB;AAAA,MACrC,qBAAoB,+BAAO,uBAAsB;AAAA,MACjD,cAAa,+BAAO,gBAAe;AAAA,MACnC,mBACG,+BAAO,qBAAoB,KAAK,KAAK,MAAM,OAAO,WAAW,OAAO,GAAU,CAAC,CAAC;AAAA,IACrF;AACA,SAAK,KAAK,KAAK,qBAAqB,OAAO;AAAA,EAC7C;AAAA;AAAA,EAKA,IAAI,UAAmC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,UAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,OAA2C;AACzC,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA,EAEA,QAAQ;AACN,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AAAA,EAEA,CAAC,OAAO,aAAa,IAAe;AAClC,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/llm/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport { APIConnectionError, APIError } from '../_exceptions.js';\nimport { log } from '../log.js';\nimport type { LLMMetrics } from '../metrics/base.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { AsyncIterableQueue, delay, startSoon, toError } from '../utils.js';\nimport { type ChatContext, type ChatRole, type FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport interface ChoiceDelta {\n role: ChatRole;\n content?: string;\n toolCalls?: FunctionCall[];\n}\n\nexport interface CompletionUsage {\n completionTokens: number;\n promptTokens: number;\n promptCachedTokens: number;\n totalTokens: number;\n}\n\nexport interface ChatChunk {\n id: string;\n delta?: ChoiceDelta;\n usage?: CompletionUsage;\n}\n\nexport interface LLMError {\n type: 'llm_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport type LLMCallbacks = {\n ['metrics_collected']: (metrics: LLMMetrics) => void;\n ['error']: (error: LLMError) => void;\n};\n\nexport abstract class LLM extends (EventEmitter as new () => TypedEmitter<LLMCallbacks>) {\n constructor() {\n super();\n }\n\n abstract label(): string;\n\n /**\n * Get the model name/identifier for this LLM instance.\n *\n * @returns The model name if available, \"unknown\" otherwise.\n *\n * @remarks\n * Plugins should override this property to provide their model information.\n */\n get model(): string {\n return 'unknown';\n }\n\n /**\n * Returns a {@link LLMStream} that can be used to push text and receive LLM responses.\n */\n abstract chat({\n chatCtx,\n toolCtx,\n connOptions,\n parallelToolCalls,\n toolChoice,\n extraKwargs,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, any>;\n }): LLMStream;\n\n /**\n * Pre-warm connection to the LLM service\n */\n prewarm(): void {\n // Default implementation - subclasses can override\n }\n\n async aclose(): Promise<void> {\n // Default implementation - subclasses can override\n }\n}\n\nexport abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {\n protected output = new AsyncIterableQueue<ChatChunk>();\n protected queue = new AsyncIterableQueue<ChatChunk>();\n protected closed = false;\n protected abortController = new AbortController();\n protected _connOptions: APIConnectOptions;\n protected logger = log();\n\n #llm: LLM;\n #chatCtx: ChatContext;\n #toolCtx?: ToolContext;\n\n constructor(\n llm: LLM,\n {\n chatCtx,\n toolCtx,\n connOptions,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions: APIConnectOptions;\n },\n ) {\n this.#llm = llm;\n this.#chatCtx = chatCtx;\n this.#toolCtx = toolCtx;\n this._connOptions = connOptions;\n this.monitorMetrics();\n this.abortController.signal.addEventListener('abort', () => {\n // TODO (AJS-37) clean this up when we refactor with streams\n this.output.close();\n this.closed = true;\n });\n\n // this is a hack to immitate asyncio.create_task so that mainTask\n // is run **after** the constructor has finished. Otherwise we get\n // runtime error when trying to access class variables in the\n // `run` method.\n startSoon(() => this.mainTask().then(() => this.queue.close()));\n }\n\n private async mainTask() {\n for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {\n try {\n return await this.run();\n } catch (error) {\n if (error instanceof APIError) {\n const retryInterval = this._connOptions._intervalForRetry(i);\n\n if (this._connOptions.maxRetry === 0 || !error.retryable) {\n this.emitError({ error, recoverable: false });\n throw error;\n } else if (i === this._connOptions.maxRetry) {\n this.emitError({ error, recoverable: false });\n throw new APIConnectionError({\n message: `failed to generate LLM completion after ${this._connOptions.maxRetry + 1} attempts`,\n options: { retryable: false },\n });\n } else {\n this.emitError({ error, recoverable: true });\n this.logger.warn(\n { llm: this.#llm.label(), attempt: i + 1, error },\n `failed to generate LLM completion, retrying in ${retryInterval}s`,\n );\n }\n\n if (retryInterval > 0) {\n await delay(retryInterval);\n }\n } else {\n this.emitError({ error: toError(error), recoverable: false });\n throw error;\n }\n }\n }\n }\n\n private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {\n this.#llm.emit('error', {\n type: 'llm_error',\n timestamp: Date.now(),\n label: this.#llm.label(),\n error,\n recoverable,\n });\n }\n\n protected async monitorMetrics() {\n const startTime = process.hrtime.bigint();\n let ttft: bigint = BigInt(-1);\n let requestId = '';\n let usage: CompletionUsage | undefined;\n\n for await (const ev of this.queue) {\n if (this.abortController.signal.aborted) {\n break;\n }\n this.output.put(ev);\n requestId = ev.id;\n if (ttft === BigInt(-1)) {\n ttft = process.hrtime.bigint() - startTime;\n }\n if (ev.usage) {\n usage = ev.usage;\n }\n }\n this.output.close();\n\n const duration = process.hrtime.bigint() - startTime;\n const metrics: LLMMetrics = {\n type: 'llm_metrics',\n timestamp: Date.now(),\n requestId,\n ttft: ttft === BigInt(-1) ? -1 : Math.trunc(Number(ttft / BigInt(1000000))),\n duration: Math.trunc(Number(duration / BigInt(1000000))),\n cancelled: this.abortController.signal.aborted,\n label: this.#llm.label(),\n completionTokens: usage?.completionTokens || 0,\n promptTokens: usage?.promptTokens || 0,\n promptCachedTokens: usage?.promptCachedTokens || 0,\n totalTokens: usage?.totalTokens || 0,\n tokensPerSecond:\n (usage?.completionTokens || 0) / Math.trunc(Number(duration / BigInt(1000000000))),\n };\n this.#llm.emit('metrics_collected', metrics);\n }\n\n protected abstract run(): Promise<void>;\n\n /** The function context of this stream. */\n get toolCtx(): ToolContext | undefined {\n return this.#toolCtx;\n }\n\n /** The initial chat context of this stream. */\n get chatCtx(): ChatContext {\n return this.#chatCtx;\n }\n\n /** The connection options for this stream. */\n get connOptions(): APIConnectOptions {\n return this._connOptions;\n }\n\n next(): Promise<IteratorResult<ChatChunk>> {\n return this.output.next();\n }\n\n close() {\n this.abortController.abort();\n }\n\n [Symbol.asyncIterator](): LLMStream {\n return this;\n }\n}\n"],"mappings":"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","names":[]}
@@ -128,9 +128,10 @@ async function serializeImage(image) {
128
128
  const createToolOptions = (toolCallId, userData = {}) => {
129
129
  return { ctx: { userData }, toolCallId };
130
130
  };
131
- const oaiParams = (p) => {
131
+ const oaiParams = (p, isOpenai = true) => {
132
132
  const { properties, required, additionalProperties } = (0, import_zod_to_json_schema.zodToJsonSchema)(p, {
133
- target: "openAi"
133
+ // note: openai mode breaks various gemini conversions
134
+ target: isOpenai ? "openAi" : "jsonSchema7"
134
135
  });
135
136
  return {
136
137
  type: "object",
@@ -241,9 +242,9 @@ function computeChatCtxDiff(oldCtx, newCtx) {
241
242
  toCreate
242
243
  };
243
244
  }
244
- function toJsonSchema(schema) {
245
+ function toJsonSchema(schema, isOpenai = true) {
245
246
  if (schema instanceof import_zod.ZodObject) {
246
- return oaiParams(schema);
247
+ return oaiParams(schema, isOpenai);
247
248
  }
248
249
  return schema;
249
250
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/llm/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport type { JSONSchema7 } from 'json-schema';\nimport sharp from 'sharp';\nimport { ZodObject } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport type { UnknownUserData } from '../voice/run_context.js';\nimport type { ChatContext } from './chat_context.js';\nimport {\n type ChatItem,\n FunctionCall,\n FunctionCallOutput,\n type ImageContent,\n} from './chat_context.js';\nimport type { ToolContext, ToolInputSchema, ToolOptions } from './tool_context.js';\n\nexport interface SerializedImage {\n inferenceDetail: 'auto' | 'high' | 'low';\n mimeType?: string;\n base64Data?: string;\n externalUrl?: string;\n}\n\nfunction getChannelsFromVideoBufferType(type: VideoBufferType): 3 | 4 {\n switch (type) {\n case VideoBufferType.RGBA:\n case VideoBufferType.ABGR:\n case VideoBufferType.ARGB:\n case VideoBufferType.BGRA:\n return 4;\n case VideoBufferType.RGB24:\n return 3;\n default:\n // YUV formats (I420, I420A, I422, I444, I010, NV12) need conversion\n throw new Error(`Unsupported VideoBufferType: ${type}. Only RGB/RGBA formats are supported.`);\n }\n}\n\nfunction ensureRGBCompatible(frame: VideoFrame): VideoFrame {\n // If the frame is already in an RGB/RGBA-compatible format, return it directly\n if (\n frame.type === VideoBufferType.RGBA ||\n frame.type === VideoBufferType.BGRA ||\n frame.type === VideoBufferType.ARGB ||\n frame.type === VideoBufferType.ABGR ||\n frame.type === VideoBufferType.RGB24\n ) {\n return frame;\n }\n\n // Otherwise, attempt conversion for other formats (like YUV)\n try {\n return frame.convert(VideoBufferType.RGBA);\n } catch (error) {\n throw new Error(\n `Failed to convert format ${frame.type} to RGB: ${error}. ` +\n `Consider using RGB/RGBA formats or converting on the client side.`,\n );\n }\n}\n\nexport async function serializeImage(image: ImageContent): Promise<SerializedImage> {\n if (typeof image.image === 'string') {\n if (image.image.startsWith('data:')) {\n const [header, base64Data] = image.image.split(',', 2) as [string, string];\n const headerParts = header.split(';');\n const mimeParts = headerParts[0]?.split(':');\n const headerMime = mimeParts?.[1];\n\n if (!headerMime) {\n throw new Error('Invalid data URL format');\n }\n\n let mimeType: string;\n if (image.mimeType && image.mimeType !== headerMime) {\n console.warn(\n `Provided mimeType '${image.mimeType}' does not match data URL mime type '${headerMime}'. Using provided mimeType.`,\n );\n mimeType = image.mimeType;\n } else {\n mimeType = headerMime;\n }\n\n const supportedTypes = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);\n if (!supportedTypes.has(mimeType)) {\n throw new Error(`Unsupported mimeType ${mimeType}. Must be jpeg, png, webp, or gif`);\n }\n\n return {\n base64Data,\n mimeType: mimeType,\n inferenceDetail: image.inferenceDetail,\n };\n }\n\n // External URL\n return {\n mimeType: image.mimeType,\n inferenceDetail: image.inferenceDetail,\n externalUrl: image.image,\n };\n } else if (image.image instanceof VideoFrame) {\n const frame = ensureRGBCompatible(image.image);\n const channels = getChannelsFromVideoBufferType(frame.type);\n\n // Sharp needs to know the format of raw pixel data\n let encoded = sharp(frame.data, {\n raw: {\n width: frame.width,\n height: frame.height,\n channels,\n },\n });\n\n if (image.inferenceWidth && image.inferenceHeight) {\n encoded = encoded.resize(image.inferenceWidth, image.inferenceHeight);\n }\n\n const base64Data = await encoded\n .png()\n .toBuffer()\n .then((buffer) => buffer.toString('base64'));\n\n return {\n base64Data,\n mimeType: 'image/png',\n inferenceDetail: image.inferenceDetail,\n };\n } else {\n throw new Error('Unsupported image type');\n }\n}\n\n/** Raw OpenAI-adherent function parameters. */\nexport type OpenAIFunctionParameters = {\n type: 'object';\n properties: { [id: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any\n required: string[];\n additionalProperties?: boolean;\n};\n\n// TODO(brian): remove this helper once we have the real RunContext user data\nexport const createToolOptions = <UserData extends UnknownUserData>(\n toolCallId: string,\n userData: UserData = {} as UserData,\n): ToolOptions<UserData> => {\n return { ctx: { userData }, toolCallId } as unknown as ToolOptions<UserData>;\n};\n\n/** @internal */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const oaiParams = (p: ZodObject<any>): OpenAIFunctionParameters => {\n // TODO(AJS-162): make zod to JSON parsing able to handle z.optional(v.field())\n const { properties, required, additionalProperties } = zodToJsonSchema(p, {\n target: 'openAi',\n }) as OpenAIFunctionParameters;\n\n return {\n type: 'object',\n properties,\n required,\n additionalProperties,\n };\n};\n\n/** @internal */\nexport const oaiBuildFunctionInfo = (\n toolCtx: ToolContext,\n toolCallId: string,\n toolName: string,\n rawArgs: string,\n): FunctionCall => {\n const tool = toolCtx[toolName];\n if (!tool) {\n throw new Error(`AI tool ${toolName} not found`);\n }\n\n return FunctionCall.create({\n callId: toolCallId,\n name: toolName,\n args: rawArgs,\n });\n};\n\nexport async function executeToolCall(\n toolCall: FunctionCall,\n toolCtx: ToolContext,\n): Promise<FunctionCallOutput> {\n const tool = toolCtx[toolCall.name]!;\n let args: object | undefined;\n let params: object | undefined;\n\n // Ensure valid JSON\n try {\n args = JSON.parse(toolCall.args);\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Invalid JSON: ${error}`,\n isError: true,\n });\n }\n\n // Ensure valid arguments schema\n try {\n if (tool.parameters instanceof ZodObject) {\n params = tool.parameters.parse(args);\n } else {\n params = args;\n }\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Arguments parsing failed: ${error}`,\n isError: true,\n });\n }\n\n try {\n const result = await tool.execute(params, createToolOptions(toolCall.callId));\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: JSON.stringify(result),\n isError: false,\n });\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Tool execution failed: ${error}`,\n isError: true,\n });\n }\n}\n\n/**\n * Standard dynamic-programming LCS to get the common subsequence\n * of IDs (in order) that appear in both old_ids and new_ids.\n *\n * @param oldIds - The old list of IDs.\n * @param newIds - The new list of IDs.\n * @returns The longest common subsequence of the two lists of IDs.\n */\nfunction computeLCS(oldIds: string[], newIds: string[]): string[] {\n const n = oldIds.length;\n const m = newIds.length;\n const dp: number[][] = Array(n + 1)\n .fill(null)\n .map(() => Array(m + 1).fill(0));\n\n // Fill DP table\n for (let i = 1; i <= n; i++) {\n for (let j = 1; j <= m; j++) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n dp[i]![j] = dp[i - 1]![j - 1]! + 1;\n } else {\n dp[i]![j] = Math.max(dp[i - 1]![j]!, dp[i]![j - 1]!);\n }\n }\n }\n\n // Backtrack to find the actual LCS sequence\n const lcsIds: string[] = [];\n let i = n;\n let j = m;\n while (i > 0 && j > 0) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n lcsIds.push(oldIds[i - 1]!);\n i--;\n j--;\n } else if (dp[i - 1]![j]! > dp[i]![j - 1]!) {\n i--;\n } else {\n j--;\n }\n }\n\n return lcsIds.reverse();\n}\n\ninterface DiffOps {\n toRemove: string[];\n toCreate: Array<[string | null, string]>; // (previous_item_id, id), if previous_item_id is null, add to the root\n}\n\n/**\n * Compute the minimal list of create/remove operations to transform oldCtx into newCtx.\n *\n * @param oldCtx - The old chat context.\n * @param newCtx - The new chat context.\n * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.\n */\nexport function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps {\n const oldIds = oldCtx.items.map((item: ChatItem) => item.id);\n const newIds = newCtx.items.map((item: ChatItem) => item.id);\n const lcsIds = new Set(computeLCS(oldIds, newIds));\n\n const toRemove = oldCtx.items.filter((msg) => !lcsIds.has(msg.id)).map((msg) => msg.id);\n const toCreate: Array<[string | null, string]> = [];\n\n let lastIdInSequence: string | null = null;\n for (const newItem of newCtx.items) {\n if (lcsIds.has(newItem.id)) {\n lastIdInSequence = newItem.id;\n } else {\n const prevId = lastIdInSequence; // null if root\n toCreate.push([prevId, newItem.id]);\n lastIdInSequence = newItem.id;\n }\n }\n\n return {\n toRemove,\n toCreate,\n };\n}\n\nexport function toJsonSchema(schema: ToolInputSchema<any>): JSONSchema7 {\n if (schema instanceof ZodObject) {\n return oaiParams(schema);\n }\n return schema;\n}\n"],"mappings":"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","names":["sharp","i","j"]}
1
+ {"version":3,"sources":["../../src/llm/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport type { JSONSchema7 } from 'json-schema';\nimport sharp from 'sharp';\nimport { ZodObject } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport type { UnknownUserData } from '../voice/run_context.js';\nimport type { ChatContext } from './chat_context.js';\nimport {\n type ChatItem,\n FunctionCall,\n FunctionCallOutput,\n type ImageContent,\n} from './chat_context.js';\nimport type { ToolContext, ToolInputSchema, ToolOptions } from './tool_context.js';\n\nexport interface SerializedImage {\n inferenceDetail: 'auto' | 'high' | 'low';\n mimeType?: string;\n base64Data?: string;\n externalUrl?: string;\n}\n\nfunction getChannelsFromVideoBufferType(type: VideoBufferType): 3 | 4 {\n switch (type) {\n case VideoBufferType.RGBA:\n case VideoBufferType.ABGR:\n case VideoBufferType.ARGB:\n case VideoBufferType.BGRA:\n return 4;\n case VideoBufferType.RGB24:\n return 3;\n default:\n // YUV formats (I420, I420A, I422, I444, I010, NV12) need conversion\n throw new Error(`Unsupported VideoBufferType: ${type}. Only RGB/RGBA formats are supported.`);\n }\n}\n\nfunction ensureRGBCompatible(frame: VideoFrame): VideoFrame {\n // If the frame is already in an RGB/RGBA-compatible format, return it directly\n if (\n frame.type === VideoBufferType.RGBA ||\n frame.type === VideoBufferType.BGRA ||\n frame.type === VideoBufferType.ARGB ||\n frame.type === VideoBufferType.ABGR ||\n frame.type === VideoBufferType.RGB24\n ) {\n return frame;\n }\n\n // Otherwise, attempt conversion for other formats (like YUV)\n try {\n return frame.convert(VideoBufferType.RGBA);\n } catch (error) {\n throw new Error(\n `Failed to convert format ${frame.type} to RGB: ${error}. ` +\n `Consider using RGB/RGBA formats or converting on the client side.`,\n );\n }\n}\n\nexport async function serializeImage(image: ImageContent): Promise<SerializedImage> {\n if (typeof image.image === 'string') {\n if (image.image.startsWith('data:')) {\n const [header, base64Data] = image.image.split(',', 2) as [string, string];\n const headerParts = header.split(';');\n const mimeParts = headerParts[0]?.split(':');\n const headerMime = mimeParts?.[1];\n\n if (!headerMime) {\n throw new Error('Invalid data URL format');\n }\n\n let mimeType: string;\n if (image.mimeType && image.mimeType !== headerMime) {\n console.warn(\n `Provided mimeType '${image.mimeType}' does not match data URL mime type '${headerMime}'. Using provided mimeType.`,\n );\n mimeType = image.mimeType;\n } else {\n mimeType = headerMime;\n }\n\n const supportedTypes = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);\n if (!supportedTypes.has(mimeType)) {\n throw new Error(`Unsupported mimeType ${mimeType}. Must be jpeg, png, webp, or gif`);\n }\n\n return {\n base64Data,\n mimeType: mimeType,\n inferenceDetail: image.inferenceDetail,\n };\n }\n\n // External URL\n return {\n mimeType: image.mimeType,\n inferenceDetail: image.inferenceDetail,\n externalUrl: image.image,\n };\n } else if (image.image instanceof VideoFrame) {\n const frame = ensureRGBCompatible(image.image);\n const channels = getChannelsFromVideoBufferType(frame.type);\n\n // Sharp needs to know the format of raw pixel data\n let encoded = sharp(frame.data, {\n raw: {\n width: frame.width,\n height: frame.height,\n channels,\n },\n });\n\n if (image.inferenceWidth && image.inferenceHeight) {\n encoded = encoded.resize(image.inferenceWidth, image.inferenceHeight);\n }\n\n const base64Data = await encoded\n .png()\n .toBuffer()\n .then((buffer) => buffer.toString('base64'));\n\n return {\n base64Data,\n mimeType: 'image/png',\n inferenceDetail: image.inferenceDetail,\n };\n } else {\n throw new Error('Unsupported image type');\n }\n}\n\n/** Raw OpenAI-adherent function parameters. */\nexport type OpenAIFunctionParameters = {\n type: 'object';\n properties: { [id: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any\n required: string[];\n additionalProperties?: boolean;\n};\n\n// TODO(brian): remove this helper once we have the real RunContext user data\nexport const createToolOptions = <UserData extends UnknownUserData>(\n toolCallId: string,\n userData: UserData = {} as UserData,\n): ToolOptions<UserData> => {\n return { ctx: { userData }, toolCallId } as unknown as ToolOptions<UserData>;\n};\n\n/** @internal */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const oaiParams = (\n p: ZodObject<any>,\n isOpenai: boolean = true,\n): OpenAIFunctionParameters => {\n // Adapted from https://github.com/vercel/ai/blob/56eb0ee9/packages/provider-utils/src/zod-schema.ts\n const { properties, required, additionalProperties } = zodToJsonSchema(p, {\n // note: openai mode breaks various gemini conversions\n target: isOpenai ? 'openAi' : 'jsonSchema7',\n }) as OpenAIFunctionParameters;\n\n return {\n type: 'object',\n properties,\n required,\n additionalProperties,\n };\n};\n\n/** @internal */\nexport const oaiBuildFunctionInfo = (\n toolCtx: ToolContext,\n toolCallId: string,\n toolName: string,\n rawArgs: string,\n): FunctionCall => {\n const tool = toolCtx[toolName];\n if (!tool) {\n throw new Error(`AI tool ${toolName} not found`);\n }\n\n return FunctionCall.create({\n callId: toolCallId,\n name: toolName,\n args: rawArgs,\n });\n};\n\nexport async function executeToolCall(\n toolCall: FunctionCall,\n toolCtx: ToolContext,\n): Promise<FunctionCallOutput> {\n const tool = toolCtx[toolCall.name]!;\n let args: object | undefined;\n let params: object | undefined;\n\n // Ensure valid JSON\n try {\n args = JSON.parse(toolCall.args);\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Invalid JSON: ${error}`,\n isError: true,\n });\n }\n\n // Ensure valid arguments schema\n try {\n if (tool.parameters instanceof ZodObject) {\n params = tool.parameters.parse(args);\n } else {\n params = args;\n }\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Arguments parsing failed: ${error}`,\n isError: true,\n });\n }\n\n try {\n const result = await tool.execute(params, createToolOptions(toolCall.callId));\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: JSON.stringify(result),\n isError: false,\n });\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Tool execution failed: ${error}`,\n isError: true,\n });\n }\n}\n\n/**\n * Standard dynamic-programming LCS to get the common subsequence\n * of IDs (in order) that appear in both old_ids and new_ids.\n *\n * @param oldIds - The old list of IDs.\n * @param newIds - The new list of IDs.\n * @returns The longest common subsequence of the two lists of IDs.\n */\nfunction computeLCS(oldIds: string[], newIds: string[]): string[] {\n const n = oldIds.length;\n const m = newIds.length;\n const dp: number[][] = Array(n + 1)\n .fill(null)\n .map(() => Array(m + 1).fill(0));\n\n // Fill DP table\n for (let i = 1; i <= n; i++) {\n for (let j = 1; j <= m; j++) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n dp[i]![j] = dp[i - 1]![j - 1]! + 1;\n } else {\n dp[i]![j] = Math.max(dp[i - 1]![j]!, dp[i]![j - 1]!);\n }\n }\n }\n\n // Backtrack to find the actual LCS sequence\n const lcsIds: string[] = [];\n let i = n;\n let j = m;\n while (i > 0 && j > 0) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n lcsIds.push(oldIds[i - 1]!);\n i--;\n j--;\n } else if (dp[i - 1]![j]! > dp[i]![j - 1]!) {\n i--;\n } else {\n j--;\n }\n }\n\n return lcsIds.reverse();\n}\n\ninterface DiffOps {\n toRemove: string[];\n toCreate: Array<[string | null, string]>; // (previous_item_id, id), if previous_item_id is null, add to the root\n}\n\n/**\n * Compute the minimal list of create/remove operations to transform oldCtx into newCtx.\n *\n * @param oldCtx - The old chat context.\n * @param newCtx - The new chat context.\n * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.\n */\nexport function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps {\n const oldIds = oldCtx.items.map((item: ChatItem) => item.id);\n const newIds = newCtx.items.map((item: ChatItem) => item.id);\n const lcsIds = new Set(computeLCS(oldIds, newIds));\n\n const toRemove = oldCtx.items.filter((msg) => !lcsIds.has(msg.id)).map((msg) => msg.id);\n const toCreate: Array<[string | null, string]> = [];\n\n let lastIdInSequence: string | null = null;\n for (const newItem of newCtx.items) {\n if (lcsIds.has(newItem.id)) {\n lastIdInSequence = newItem.id;\n } else {\n const prevId = lastIdInSequence; // null if root\n toCreate.push([prevId, newItem.id]);\n lastIdInSequence = newItem.id;\n }\n }\n\n return {\n toRemove,\n toCreate,\n };\n}\n\nexport function toJsonSchema(schema: ToolInputSchema<any>, isOpenai: boolean = true): JSONSchema7 {\n if (schema instanceof ZodObject) {\n return oaiParams(schema, isOpenai);\n }\n return schema;\n}\n"],"mappings":"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","names":["sharp","i","j"]}
@@ -21,7 +21,7 @@ export type OpenAIFunctionParameters = {
21
21
  };
22
22
  export declare const createToolOptions: <UserData extends unknown>(toolCallId: string, userData?: UserData) => ToolOptions<UserData>;
23
23
  /** @internal */
24
- export declare const oaiParams: (p: ZodObject<any>) => OpenAIFunctionParameters;
24
+ export declare const oaiParams: (p: ZodObject<any>, isOpenai?: boolean) => OpenAIFunctionParameters;
25
25
  /** @internal */
26
26
  export declare const oaiBuildFunctionInfo: (toolCtx: ToolContext, toolCallId: string, toolName: string, rawArgs: string) => FunctionCall;
27
27
  export declare function executeToolCall(toolCall: FunctionCall, toolCtx: ToolContext): Promise<FunctionCallOutput>;
@@ -37,6 +37,6 @@ interface DiffOps {
37
37
  * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.
38
38
  */
39
39
  export declare function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps;
40
- export declare function toJsonSchema(schema: ToolInputSchema<any>): JSONSchema7;
40
+ export declare function toJsonSchema(schema: ToolInputSchema<any>, isOpenai?: boolean): JSONSchema7;
41
41
  export {};
42
42
  //# sourceMappingURL=utils.d.ts.map
@@ -21,7 +21,7 @@ export type OpenAIFunctionParameters = {
21
21
  };
22
22
  export declare const createToolOptions: <UserData extends unknown>(toolCallId: string, userData?: UserData) => ToolOptions<UserData>;
23
23
  /** @internal */
24
- export declare const oaiParams: (p: ZodObject<any>) => OpenAIFunctionParameters;
24
+ export declare const oaiParams: (p: ZodObject<any>, isOpenai?: boolean) => OpenAIFunctionParameters;
25
25
  /** @internal */
26
26
  export declare const oaiBuildFunctionInfo: (toolCtx: ToolContext, toolCallId: string, toolName: string, rawArgs: string) => FunctionCall;
27
27
  export declare function executeToolCall(toolCall: FunctionCall, toolCtx: ToolContext): Promise<FunctionCallOutput>;
@@ -37,6 +37,6 @@ interface DiffOps {
37
37
  * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.
38
38
  */
39
39
  export declare function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps;
40
- export declare function toJsonSchema(schema: ToolInputSchema<any>): JSONSchema7;
40
+ export declare function toJsonSchema(schema: ToolInputSchema<any>, isOpenai?: boolean): JSONSchema7;
41
41
  export {};
42
42
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/llm/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAGhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,YAAY,EACZ,kBAAkB,EAClB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEnF,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAwCD,wBAAsB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAsElF;AAED,+CAA+C;AAC/C,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE;QAAE,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAGF,eAAO,MAAM,iBAAiB,yCAChB,MAAM,aACR,QAAQ,KACjB,YAAY,QAAQ,CAEtB,CAAC;AAEF,gBAAgB;AAEhB,eAAO,MAAM,SAAS,MAAO,UAAU,GAAG,CAAC,KAAG,wBAY7C,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,oBAAoB,YACtB,WAAW,cACR,MAAM,YACR,MAAM,WACP,MAAM,KACd,YAWF,CAAC;AAEF,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,kBAAkB,CAAC,CA6C7B;AA+CD,UAAU,OAAO;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAuBpF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,GAAG,WAAW,CAKtE"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/llm/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAGhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAEL,YAAY,EACZ,kBAAkB,EAClB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEnF,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAwCD,wBAAsB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAsElF;AAED,+CAA+C;AAC/C,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE;QAAE,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAGF,eAAO,MAAM,iBAAiB,yCAChB,MAAM,aACR,QAAQ,KACjB,YAAY,QAAQ,CAEtB,CAAC;AAEF,gBAAgB;AAEhB,eAAO,MAAM,SAAS,MACjB,UAAU,GAAG,CAAC,aACP,OAAO,KAChB,wBAaF,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,oBAAoB,YACtB,WAAW,cACR,MAAM,YACR,MAAM,WACP,MAAM,KACd,YAWF,CAAC;AAEF,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,kBAAkB,CAAC,CA6C7B;AA+CD,UAAU,OAAO;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAuBpF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,QAAQ,GAAE,OAAc,GAAG,WAAW,CAKhG"}
package/dist/llm/utils.js CHANGED
@@ -92,9 +92,10 @@ async function serializeImage(image) {
92
92
  const createToolOptions = (toolCallId, userData = {}) => {
93
93
  return { ctx: { userData }, toolCallId };
94
94
  };
95
- const oaiParams = (p) => {
95
+ const oaiParams = (p, isOpenai = true) => {
96
96
  const { properties, required, additionalProperties } = zodToJsonSchema(p, {
97
- target: "openAi"
97
+ // note: openai mode breaks various gemini conversions
98
+ target: isOpenai ? "openAi" : "jsonSchema7"
98
99
  });
99
100
  return {
100
101
  type: "object",
@@ -205,9 +206,9 @@ function computeChatCtxDiff(oldCtx, newCtx) {
205
206
  toCreate
206
207
  };
207
208
  }
208
- function toJsonSchema(schema) {
209
+ function toJsonSchema(schema, isOpenai = true) {
209
210
  if (schema instanceof ZodObject) {
210
- return oaiParams(schema);
211
+ return oaiParams(schema, isOpenai);
211
212
  }
212
213
  return schema;
213
214
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/llm/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport type { JSONSchema7 } from 'json-schema';\nimport sharp from 'sharp';\nimport { ZodObject } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport type { UnknownUserData } from '../voice/run_context.js';\nimport type { ChatContext } from './chat_context.js';\nimport {\n type ChatItem,\n FunctionCall,\n FunctionCallOutput,\n type ImageContent,\n} from './chat_context.js';\nimport type { ToolContext, ToolInputSchema, ToolOptions } from './tool_context.js';\n\nexport interface SerializedImage {\n inferenceDetail: 'auto' | 'high' | 'low';\n mimeType?: string;\n base64Data?: string;\n externalUrl?: string;\n}\n\nfunction getChannelsFromVideoBufferType(type: VideoBufferType): 3 | 4 {\n switch (type) {\n case VideoBufferType.RGBA:\n case VideoBufferType.ABGR:\n case VideoBufferType.ARGB:\n case VideoBufferType.BGRA:\n return 4;\n case VideoBufferType.RGB24:\n return 3;\n default:\n // YUV formats (I420, I420A, I422, I444, I010, NV12) need conversion\n throw new Error(`Unsupported VideoBufferType: ${type}. Only RGB/RGBA formats are supported.`);\n }\n}\n\nfunction ensureRGBCompatible(frame: VideoFrame): VideoFrame {\n // If the frame is already in an RGB/RGBA-compatible format, return it directly\n if (\n frame.type === VideoBufferType.RGBA ||\n frame.type === VideoBufferType.BGRA ||\n frame.type === VideoBufferType.ARGB ||\n frame.type === VideoBufferType.ABGR ||\n frame.type === VideoBufferType.RGB24\n ) {\n return frame;\n }\n\n // Otherwise, attempt conversion for other formats (like YUV)\n try {\n return frame.convert(VideoBufferType.RGBA);\n } catch (error) {\n throw new Error(\n `Failed to convert format ${frame.type} to RGB: ${error}. ` +\n `Consider using RGB/RGBA formats or converting on the client side.`,\n );\n }\n}\n\nexport async function serializeImage(image: ImageContent): Promise<SerializedImage> {\n if (typeof image.image === 'string') {\n if (image.image.startsWith('data:')) {\n const [header, base64Data] = image.image.split(',', 2) as [string, string];\n const headerParts = header.split(';');\n const mimeParts = headerParts[0]?.split(':');\n const headerMime = mimeParts?.[1];\n\n if (!headerMime) {\n throw new Error('Invalid data URL format');\n }\n\n let mimeType: string;\n if (image.mimeType && image.mimeType !== headerMime) {\n console.warn(\n `Provided mimeType '${image.mimeType}' does not match data URL mime type '${headerMime}'. Using provided mimeType.`,\n );\n mimeType = image.mimeType;\n } else {\n mimeType = headerMime;\n }\n\n const supportedTypes = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);\n if (!supportedTypes.has(mimeType)) {\n throw new Error(`Unsupported mimeType ${mimeType}. Must be jpeg, png, webp, or gif`);\n }\n\n return {\n base64Data,\n mimeType: mimeType,\n inferenceDetail: image.inferenceDetail,\n };\n }\n\n // External URL\n return {\n mimeType: image.mimeType,\n inferenceDetail: image.inferenceDetail,\n externalUrl: image.image,\n };\n } else if (image.image instanceof VideoFrame) {\n const frame = ensureRGBCompatible(image.image);\n const channels = getChannelsFromVideoBufferType(frame.type);\n\n // Sharp needs to know the format of raw pixel data\n let encoded = sharp(frame.data, {\n raw: {\n width: frame.width,\n height: frame.height,\n channels,\n },\n });\n\n if (image.inferenceWidth && image.inferenceHeight) {\n encoded = encoded.resize(image.inferenceWidth, image.inferenceHeight);\n }\n\n const base64Data = await encoded\n .png()\n .toBuffer()\n .then((buffer) => buffer.toString('base64'));\n\n return {\n base64Data,\n mimeType: 'image/png',\n inferenceDetail: image.inferenceDetail,\n };\n } else {\n throw new Error('Unsupported image type');\n }\n}\n\n/** Raw OpenAI-adherent function parameters. */\nexport type OpenAIFunctionParameters = {\n type: 'object';\n properties: { [id: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any\n required: string[];\n additionalProperties?: boolean;\n};\n\n// TODO(brian): remove this helper once we have the real RunContext user data\nexport const createToolOptions = <UserData extends UnknownUserData>(\n toolCallId: string,\n userData: UserData = {} as UserData,\n): ToolOptions<UserData> => {\n return { ctx: { userData }, toolCallId } as unknown as ToolOptions<UserData>;\n};\n\n/** @internal */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const oaiParams = (p: ZodObject<any>): OpenAIFunctionParameters => {\n // TODO(AJS-162): make zod to JSON parsing able to handle z.optional(v.field())\n const { properties, required, additionalProperties } = zodToJsonSchema(p, {\n target: 'openAi',\n }) as OpenAIFunctionParameters;\n\n return {\n type: 'object',\n properties,\n required,\n additionalProperties,\n };\n};\n\n/** @internal */\nexport const oaiBuildFunctionInfo = (\n toolCtx: ToolContext,\n toolCallId: string,\n toolName: string,\n rawArgs: string,\n): FunctionCall => {\n const tool = toolCtx[toolName];\n if (!tool) {\n throw new Error(`AI tool ${toolName} not found`);\n }\n\n return FunctionCall.create({\n callId: toolCallId,\n name: toolName,\n args: rawArgs,\n });\n};\n\nexport async function executeToolCall(\n toolCall: FunctionCall,\n toolCtx: ToolContext,\n): Promise<FunctionCallOutput> {\n const tool = toolCtx[toolCall.name]!;\n let args: object | undefined;\n let params: object | undefined;\n\n // Ensure valid JSON\n try {\n args = JSON.parse(toolCall.args);\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Invalid JSON: ${error}`,\n isError: true,\n });\n }\n\n // Ensure valid arguments schema\n try {\n if (tool.parameters instanceof ZodObject) {\n params = tool.parameters.parse(args);\n } else {\n params = args;\n }\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Arguments parsing failed: ${error}`,\n isError: true,\n });\n }\n\n try {\n const result = await tool.execute(params, createToolOptions(toolCall.callId));\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: JSON.stringify(result),\n isError: false,\n });\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Tool execution failed: ${error}`,\n isError: true,\n });\n }\n}\n\n/**\n * Standard dynamic-programming LCS to get the common subsequence\n * of IDs (in order) that appear in both old_ids and new_ids.\n *\n * @param oldIds - The old list of IDs.\n * @param newIds - The new list of IDs.\n * @returns The longest common subsequence of the two lists of IDs.\n */\nfunction computeLCS(oldIds: string[], newIds: string[]): string[] {\n const n = oldIds.length;\n const m = newIds.length;\n const dp: number[][] = Array(n + 1)\n .fill(null)\n .map(() => Array(m + 1).fill(0));\n\n // Fill DP table\n for (let i = 1; i <= n; i++) {\n for (let j = 1; j <= m; j++) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n dp[i]![j] = dp[i - 1]![j - 1]! + 1;\n } else {\n dp[i]![j] = Math.max(dp[i - 1]![j]!, dp[i]![j - 1]!);\n }\n }\n }\n\n // Backtrack to find the actual LCS sequence\n const lcsIds: string[] = [];\n let i = n;\n let j = m;\n while (i > 0 && j > 0) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n lcsIds.push(oldIds[i - 1]!);\n i--;\n j--;\n } else if (dp[i - 1]![j]! > dp[i]![j - 1]!) {\n i--;\n } else {\n j--;\n }\n }\n\n return lcsIds.reverse();\n}\n\ninterface DiffOps {\n toRemove: string[];\n toCreate: Array<[string | null, string]>; // (previous_item_id, id), if previous_item_id is null, add to the root\n}\n\n/**\n * Compute the minimal list of create/remove operations to transform oldCtx into newCtx.\n *\n * @param oldCtx - The old chat context.\n * @param newCtx - The new chat context.\n * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.\n */\nexport function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps {\n const oldIds = oldCtx.items.map((item: ChatItem) => item.id);\n const newIds = newCtx.items.map((item: ChatItem) => item.id);\n const lcsIds = new Set(computeLCS(oldIds, newIds));\n\n const toRemove = oldCtx.items.filter((msg) => !lcsIds.has(msg.id)).map((msg) => msg.id);\n const toCreate: Array<[string | null, string]> = [];\n\n let lastIdInSequence: string | null = null;\n for (const newItem of newCtx.items) {\n if (lcsIds.has(newItem.id)) {\n lastIdInSequence = newItem.id;\n } else {\n const prevId = lastIdInSequence; // null if root\n toCreate.push([prevId, newItem.id]);\n lastIdInSequence = newItem.id;\n }\n }\n\n return {\n toRemove,\n toCreate,\n };\n}\n\nexport function toJsonSchema(schema: ToolInputSchema<any>): JSONSchema7 {\n if (schema instanceof ZodObject) {\n return oaiParams(schema);\n }\n return schema;\n}\n"],"mappings":"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","names":["i","j"]}
1
+ {"version":3,"sources":["../../src/llm/utils.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport type { JSONSchema7 } from 'json-schema';\nimport sharp from 'sharp';\nimport { ZodObject } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport type { UnknownUserData } from '../voice/run_context.js';\nimport type { ChatContext } from './chat_context.js';\nimport {\n type ChatItem,\n FunctionCall,\n FunctionCallOutput,\n type ImageContent,\n} from './chat_context.js';\nimport type { ToolContext, ToolInputSchema, ToolOptions } from './tool_context.js';\n\nexport interface SerializedImage {\n inferenceDetail: 'auto' | 'high' | 'low';\n mimeType?: string;\n base64Data?: string;\n externalUrl?: string;\n}\n\nfunction getChannelsFromVideoBufferType(type: VideoBufferType): 3 | 4 {\n switch (type) {\n case VideoBufferType.RGBA:\n case VideoBufferType.ABGR:\n case VideoBufferType.ARGB:\n case VideoBufferType.BGRA:\n return 4;\n case VideoBufferType.RGB24:\n return 3;\n default:\n // YUV formats (I420, I420A, I422, I444, I010, NV12) need conversion\n throw new Error(`Unsupported VideoBufferType: ${type}. Only RGB/RGBA formats are supported.`);\n }\n}\n\nfunction ensureRGBCompatible(frame: VideoFrame): VideoFrame {\n // If the frame is already in an RGB/RGBA-compatible format, return it directly\n if (\n frame.type === VideoBufferType.RGBA ||\n frame.type === VideoBufferType.BGRA ||\n frame.type === VideoBufferType.ARGB ||\n frame.type === VideoBufferType.ABGR ||\n frame.type === VideoBufferType.RGB24\n ) {\n return frame;\n }\n\n // Otherwise, attempt conversion for other formats (like YUV)\n try {\n return frame.convert(VideoBufferType.RGBA);\n } catch (error) {\n throw new Error(\n `Failed to convert format ${frame.type} to RGB: ${error}. ` +\n `Consider using RGB/RGBA formats or converting on the client side.`,\n );\n }\n}\n\nexport async function serializeImage(image: ImageContent): Promise<SerializedImage> {\n if (typeof image.image === 'string') {\n if (image.image.startsWith('data:')) {\n const [header, base64Data] = image.image.split(',', 2) as [string, string];\n const headerParts = header.split(';');\n const mimeParts = headerParts[0]?.split(':');\n const headerMime = mimeParts?.[1];\n\n if (!headerMime) {\n throw new Error('Invalid data URL format');\n }\n\n let mimeType: string;\n if (image.mimeType && image.mimeType !== headerMime) {\n console.warn(\n `Provided mimeType '${image.mimeType}' does not match data URL mime type '${headerMime}'. Using provided mimeType.`,\n );\n mimeType = image.mimeType;\n } else {\n mimeType = headerMime;\n }\n\n const supportedTypes = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);\n if (!supportedTypes.has(mimeType)) {\n throw new Error(`Unsupported mimeType ${mimeType}. Must be jpeg, png, webp, or gif`);\n }\n\n return {\n base64Data,\n mimeType: mimeType,\n inferenceDetail: image.inferenceDetail,\n };\n }\n\n // External URL\n return {\n mimeType: image.mimeType,\n inferenceDetail: image.inferenceDetail,\n externalUrl: image.image,\n };\n } else if (image.image instanceof VideoFrame) {\n const frame = ensureRGBCompatible(image.image);\n const channels = getChannelsFromVideoBufferType(frame.type);\n\n // Sharp needs to know the format of raw pixel data\n let encoded = sharp(frame.data, {\n raw: {\n width: frame.width,\n height: frame.height,\n channels,\n },\n });\n\n if (image.inferenceWidth && image.inferenceHeight) {\n encoded = encoded.resize(image.inferenceWidth, image.inferenceHeight);\n }\n\n const base64Data = await encoded\n .png()\n .toBuffer()\n .then((buffer) => buffer.toString('base64'));\n\n return {\n base64Data,\n mimeType: 'image/png',\n inferenceDetail: image.inferenceDetail,\n };\n } else {\n throw new Error('Unsupported image type');\n }\n}\n\n/** Raw OpenAI-adherent function parameters. */\nexport type OpenAIFunctionParameters = {\n type: 'object';\n properties: { [id: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any\n required: string[];\n additionalProperties?: boolean;\n};\n\n// TODO(brian): remove this helper once we have the real RunContext user data\nexport const createToolOptions = <UserData extends UnknownUserData>(\n toolCallId: string,\n userData: UserData = {} as UserData,\n): ToolOptions<UserData> => {\n return { ctx: { userData }, toolCallId } as unknown as ToolOptions<UserData>;\n};\n\n/** @internal */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const oaiParams = (\n p: ZodObject<any>,\n isOpenai: boolean = true,\n): OpenAIFunctionParameters => {\n // Adapted from https://github.com/vercel/ai/blob/56eb0ee9/packages/provider-utils/src/zod-schema.ts\n const { properties, required, additionalProperties } = zodToJsonSchema(p, {\n // note: openai mode breaks various gemini conversions\n target: isOpenai ? 'openAi' : 'jsonSchema7',\n }) as OpenAIFunctionParameters;\n\n return {\n type: 'object',\n properties,\n required,\n additionalProperties,\n };\n};\n\n/** @internal */\nexport const oaiBuildFunctionInfo = (\n toolCtx: ToolContext,\n toolCallId: string,\n toolName: string,\n rawArgs: string,\n): FunctionCall => {\n const tool = toolCtx[toolName];\n if (!tool) {\n throw new Error(`AI tool ${toolName} not found`);\n }\n\n return FunctionCall.create({\n callId: toolCallId,\n name: toolName,\n args: rawArgs,\n });\n};\n\nexport async function executeToolCall(\n toolCall: FunctionCall,\n toolCtx: ToolContext,\n): Promise<FunctionCallOutput> {\n const tool = toolCtx[toolCall.name]!;\n let args: object | undefined;\n let params: object | undefined;\n\n // Ensure valid JSON\n try {\n args = JSON.parse(toolCall.args);\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Invalid JSON: ${error}`,\n isError: true,\n });\n }\n\n // Ensure valid arguments schema\n try {\n if (tool.parameters instanceof ZodObject) {\n params = tool.parameters.parse(args);\n } else {\n params = args;\n }\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Arguments parsing failed: ${error}`,\n isError: true,\n });\n }\n\n try {\n const result = await tool.execute(params, createToolOptions(toolCall.callId));\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: JSON.stringify(result),\n isError: false,\n });\n } catch (error) {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Tool execution failed: ${error}`,\n isError: true,\n });\n }\n}\n\n/**\n * Standard dynamic-programming LCS to get the common subsequence\n * of IDs (in order) that appear in both old_ids and new_ids.\n *\n * @param oldIds - The old list of IDs.\n * @param newIds - The new list of IDs.\n * @returns The longest common subsequence of the two lists of IDs.\n */\nfunction computeLCS(oldIds: string[], newIds: string[]): string[] {\n const n = oldIds.length;\n const m = newIds.length;\n const dp: number[][] = Array(n + 1)\n .fill(null)\n .map(() => Array(m + 1).fill(0));\n\n // Fill DP table\n for (let i = 1; i <= n; i++) {\n for (let j = 1; j <= m; j++) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n dp[i]![j] = dp[i - 1]![j - 1]! + 1;\n } else {\n dp[i]![j] = Math.max(dp[i - 1]![j]!, dp[i]![j - 1]!);\n }\n }\n }\n\n // Backtrack to find the actual LCS sequence\n const lcsIds: string[] = [];\n let i = n;\n let j = m;\n while (i > 0 && j > 0) {\n if (oldIds[i - 1] === newIds[j - 1]) {\n lcsIds.push(oldIds[i - 1]!);\n i--;\n j--;\n } else if (dp[i - 1]![j]! > dp[i]![j - 1]!) {\n i--;\n } else {\n j--;\n }\n }\n\n return lcsIds.reverse();\n}\n\ninterface DiffOps {\n toRemove: string[];\n toCreate: Array<[string | null, string]>; // (previous_item_id, id), if previous_item_id is null, add to the root\n}\n\n/**\n * Compute the minimal list of create/remove operations to transform oldCtx into newCtx.\n *\n * @param oldCtx - The old chat context.\n * @param newCtx - The new chat context.\n * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.\n */\nexport function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps {\n const oldIds = oldCtx.items.map((item: ChatItem) => item.id);\n const newIds = newCtx.items.map((item: ChatItem) => item.id);\n const lcsIds = new Set(computeLCS(oldIds, newIds));\n\n const toRemove = oldCtx.items.filter((msg) => !lcsIds.has(msg.id)).map((msg) => msg.id);\n const toCreate: Array<[string | null, string]> = [];\n\n let lastIdInSequence: string | null = null;\n for (const newItem of newCtx.items) {\n if (lcsIds.has(newItem.id)) {\n lastIdInSequence = newItem.id;\n } else {\n const prevId = lastIdInSequence; // null if root\n toCreate.push([prevId, newItem.id]);\n lastIdInSequence = newItem.id;\n }\n }\n\n return {\n toRemove,\n toCreate,\n };\n}\n\nexport function toJsonSchema(schema: ToolInputSchema<any>, isOpenai: boolean = true): JSONSchema7 {\n if (schema instanceof ZodObject) {\n return oaiParams(schema, isOpenai);\n }\n return schema;\n}\n"],"mappings":"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","names":["i","j"]}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
- var import_delay = require("@std/async/delay");
3
2
  var import_web = require("node:stream/web");
4
3
  var import_vitest = require("vitest");
4
+ var import_utils = require("../utils.cjs");
5
5
  var import_deferred_stream = require("./deferred_stream.cjs");
6
6
  (0, import_vitest.describe)("DeferredReadableStream", () => {
7
7
  (0, import_vitest.it)("should create a readable stream that can be read after setting source", async () => {
@@ -29,7 +29,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
29
29
  const deferred = new import_deferred_stream.DeferredReadableStream();
30
30
  const reader = deferred.stream.getReader();
31
31
  const readPromise = reader.read();
32
- await (0, import_delay.delay)(10);
32
+ await (0, import_utils.delay)(10);
33
33
  const source = new import_web.ReadableStream({
34
34
  start(controller) {
35
35
  controller.enqueue("test-value");
@@ -69,7 +69,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
69
69
  const reader2 = stream2.getReader();
70
70
  const read1Promise = reader1.read();
71
71
  const read2Promise = reader2.read();
72
- await (0, import_delay.delay)(10);
72
+ await (0, import_utils.delay)(10);
73
73
  const source = new import_web.ReadableStream({
74
74
  start(controller) {
75
75
  controller.enqueue(42);
@@ -166,7 +166,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
166
166
  const deferred = new import_deferred_stream.DeferredReadableStream();
167
167
  const reader = deferred.stream.getReader();
168
168
  const readPromises = Array.from({ length: 3 }, () => reader.read());
169
- await (0, import_delay.delay)(10);
169
+ await (0, import_utils.delay)(10);
170
170
  const source = new import_web.ReadableStream({
171
171
  start(controller) {
172
172
  controller.enqueue(1);
@@ -269,7 +269,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
269
269
  readCompleted = true;
270
270
  return result4;
271
271
  });
272
- await (0, import_delay.delay)(50);
272
+ await (0, import_utils.delay)(50);
273
273
  (0, import_vitest.expect)(readCompleted).toBe(false);
274
274
  const sourceData = ["hello", "world"];
275
275
  const source = new import_web.ReadableStream({
@@ -310,7 +310,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
310
310
  async start(controller) {
311
311
  for (let i = 0; i < 5; i++) {
312
312
  controller.enqueue(i);
313
- await (0, import_delay.delay)(10);
313
+ await (0, import_utils.delay)(10);
314
314
  }
315
315
  controller.close();
316
316
  }
@@ -463,14 +463,14 @@ var import_deferred_stream = require("./deferred_stream.cjs");
463
463
  let resumeSource = false;
464
464
  const source = new import_web.ReadableStream({
465
465
  async start(controller) {
466
- while (!resumeSource) await (0, import_delay.delay)(10);
466
+ while (!resumeSource) await (0, import_utils.delay)(10);
467
467
  controller.enqueue("data");
468
468
  controller.close();
469
469
  }
470
470
  });
471
471
  deferred.setSource(source);
472
472
  await deferred.detachSource();
473
- await (0, import_delay.delay)(100);
473
+ await (0, import_utils.delay)(100);
474
474
  const result = await readPromise;
475
475
  (0, import_vitest.expect)(result.done).toBe(true);
476
476
  (0, import_vitest.expect)(result.value).toBeUndefined();
@@ -491,7 +491,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
491
491
  async start(controller) {
492
492
  for (let i = 0; i < 4; i++) {
493
493
  controller.enqueue(`chunk-${i}`);
494
- await (0, import_delay.delay)(20);
494
+ await (0, import_utils.delay)(20);
495
495
  }
496
496
  controller.close();
497
497
  }
@@ -504,7 +504,7 @@ var import_deferred_stream = require("./deferred_stream.cjs");
504
504
  const result2 = await reader1.read();
505
505
  (0, import_vitest.expect)(result2.done).toBe(false);
506
506
  (0, import_vitest.expect)(result2.value).toBe("chunk-1");
507
- await (0, import_delay.delay)(10);
507
+ await (0, import_utils.delay)(10);
508
508
  await deferred1.detachSource();
509
509
  const result3 = await reader1.read();
510
510
  (0, import_vitest.expect)(result3.done).toBe(true);