@livekit/agents 1.0.31 → 1.0.33

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 (90) hide show
  1. package/dist/ipc/inference_proc_executor.cjs +6 -3
  2. package/dist/ipc/inference_proc_executor.cjs.map +1 -1
  3. package/dist/ipc/inference_proc_executor.d.ts.map +1 -1
  4. package/dist/ipc/inference_proc_executor.js +6 -3
  5. package/dist/ipc/inference_proc_executor.js.map +1 -1
  6. package/dist/ipc/job_proc_executor.cjs +6 -1
  7. package/dist/ipc/job_proc_executor.cjs.map +1 -1
  8. package/dist/ipc/job_proc_executor.d.ts.map +1 -1
  9. package/dist/ipc/job_proc_executor.js +6 -1
  10. package/dist/ipc/job_proc_executor.js.map +1 -1
  11. package/dist/ipc/job_proc_lazy_main.cjs +1 -1
  12. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  13. package/dist/ipc/job_proc_lazy_main.js +1 -1
  14. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  15. package/dist/ipc/supervised_proc.cjs +29 -7
  16. package/dist/ipc/supervised_proc.cjs.map +1 -1
  17. package/dist/ipc/supervised_proc.d.ts.map +1 -1
  18. package/dist/ipc/supervised_proc.js +29 -7
  19. package/dist/ipc/supervised_proc.js.map +1 -1
  20. package/dist/ipc/supervised_proc.test.cjs +145 -0
  21. package/dist/ipc/supervised_proc.test.cjs.map +1 -0
  22. package/dist/ipc/supervised_proc.test.js +122 -0
  23. package/dist/ipc/supervised_proc.test.js.map +1 -0
  24. package/dist/job.cjs +5 -1
  25. package/dist/job.cjs.map +1 -1
  26. package/dist/job.d.ts.map +1 -1
  27. package/dist/job.js +5 -1
  28. package/dist/job.js.map +1 -1
  29. package/dist/llm/chat_context.cjs +19 -2
  30. package/dist/llm/chat_context.cjs.map +1 -1
  31. package/dist/llm/chat_context.d.cts +8 -0
  32. package/dist/llm/chat_context.d.ts +8 -0
  33. package/dist/llm/chat_context.d.ts.map +1 -1
  34. package/dist/llm/chat_context.js +19 -2
  35. package/dist/llm/chat_context.js.map +1 -1
  36. package/dist/llm/provider_format/google.cjs +6 -2
  37. package/dist/llm/provider_format/google.cjs.map +1 -1
  38. package/dist/llm/provider_format/google.d.ts.map +1 -1
  39. package/dist/llm/provider_format/google.js +6 -2
  40. package/dist/llm/provider_format/google.js.map +1 -1
  41. package/dist/llm/realtime.cjs.map +1 -1
  42. package/dist/llm/realtime.d.cts +4 -0
  43. package/dist/llm/realtime.d.ts +4 -0
  44. package/dist/llm/realtime.d.ts.map +1 -1
  45. package/dist/llm/realtime.js.map +1 -1
  46. package/dist/stream/stream_channel.cjs +8 -1
  47. package/dist/stream/stream_channel.cjs.map +1 -1
  48. package/dist/stream/stream_channel.d.cts +1 -0
  49. package/dist/stream/stream_channel.d.ts +1 -0
  50. package/dist/stream/stream_channel.d.ts.map +1 -1
  51. package/dist/stream/stream_channel.js +8 -1
  52. package/dist/stream/stream_channel.js.map +1 -1
  53. package/dist/telemetry/otel_http_exporter.cjs +13 -10
  54. package/dist/telemetry/otel_http_exporter.cjs.map +1 -1
  55. package/dist/telemetry/otel_http_exporter.d.ts.map +1 -1
  56. package/dist/telemetry/otel_http_exporter.js +13 -10
  57. package/dist/telemetry/otel_http_exporter.js.map +1 -1
  58. package/dist/telemetry/traces.cjs +22 -4
  59. package/dist/telemetry/traces.cjs.map +1 -1
  60. package/dist/telemetry/traces.d.ts.map +1 -1
  61. package/dist/telemetry/traces.js +22 -4
  62. package/dist/telemetry/traces.js.map +1 -1
  63. package/dist/voice/agent_activity.cjs +25 -5
  64. package/dist/voice/agent_activity.cjs.map +1 -1
  65. package/dist/voice/agent_activity.d.cts +1 -0
  66. package/dist/voice/agent_activity.d.ts +1 -0
  67. package/dist/voice/agent_activity.d.ts.map +1 -1
  68. package/dist/voice/agent_activity.js +26 -6
  69. package/dist/voice/agent_activity.js.map +1 -1
  70. package/dist/voice/generation.cjs +3 -1
  71. package/dist/voice/generation.cjs.map +1 -1
  72. package/dist/voice/generation.d.ts.map +1 -1
  73. package/dist/voice/generation.js +3 -1
  74. package/dist/voice/generation.js.map +1 -1
  75. package/package.json +1 -1
  76. package/src/ipc/inference_proc_executor.ts +11 -3
  77. package/src/ipc/job_proc_executor.ts +11 -1
  78. package/src/ipc/job_proc_lazy_main.ts +1 -1
  79. package/src/ipc/supervised_proc.test.ts +153 -0
  80. package/src/ipc/supervised_proc.ts +27 -9
  81. package/src/job.ts +4 -1
  82. package/src/llm/chat_context.ts +28 -2
  83. package/src/llm/provider_format/google.ts +6 -2
  84. package/src/llm/realtime.ts +5 -0
  85. package/src/stream/stream_channel.ts +9 -1
  86. package/src/telemetry/otel_http_exporter.ts +14 -10
  87. package/src/telemetry/traces.ts +28 -4
  88. package/src/voice/agent_activity.ts +27 -2
  89. package/src/voice/generation.ts +2 -0
  90. package/src/llm/__snapshots__/utils.test.ts.snap +0 -65
@@ -45,13 +45,17 @@ async function toChatCtx(chatCtx, injectDummyUserMessage = true) {
45
45
  }
46
46
  }
47
47
  } else if (msg.type === "function_call") {
48
- parts.push({
48
+ const functionCallPart = {
49
49
  functionCall: {
50
50
  id: msg.callId,
51
51
  name: msg.name,
52
52
  args: JSON.parse(msg.args || "{}")
53
53
  }
54
- });
54
+ };
55
+ if (msg.thoughtSignature) {
56
+ functionCallPart.thoughtSignature = msg.thoughtSignature;
57
+ }
58
+ parts.push(functionCallPart);
55
59
  } else if (msg.type === "function_call_output") {
56
60
  const response = msg.isError ? { error: msg.output } : { output: msg.output };
57
61
  parts.push({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/llm/provider_format/google.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext, ChatItem, ImageContent } from '../chat_context.js';\nimport { type SerializedImage, serializeImage } from '../utils.js';\nimport { groupToolCalls } from './utils.js';\n\nexport interface GoogleFormatData {\n systemMessages: string[] | null;\n}\n\nexport async function toChatCtx(\n chatCtx: ChatContext,\n injectDummyUserMessage: boolean = true,\n): Promise<[Record<string, unknown>[], GoogleFormatData]> {\n const turns: Record<string, unknown>[] = [];\n const systemMessages: string[] = [];\n let currentRole: string | null = null;\n let parts: Record<string, unknown>[] = [];\n\n // Flatten all grouped tool calls to get individual messages\n const itemGroups = groupToolCalls(chatCtx);\n const flattenedItems: ChatItem[] = [];\n\n for (const group of itemGroups) {\n flattenedItems.push(...group.flatten());\n }\n\n for (const msg of flattenedItems) {\n // Handle system messages separately\n if (msg.type === 'message' && msg.role === 'system' && msg.textContent) {\n systemMessages.push(msg.textContent);\n continue;\n }\n\n let role: string;\n if (msg.type === 'message') {\n role = msg.role === 'assistant' ? 'model' : 'user';\n } else if (msg.type === 'function_call') {\n role = 'model';\n } else if (msg.type === 'function_call_output') {\n role = 'user';\n } else {\n continue; // Skip unknown message types\n }\n\n // If the effective role changed, finalize the previous turn\n if (role !== currentRole) {\n if (currentRole !== null && parts.length > 0) {\n turns.push({ role: currentRole, parts: [...parts] });\n }\n parts = [];\n currentRole = role;\n }\n\n if (msg.type === 'message') {\n for (const content of msg.content) {\n if (content && typeof content === 'string') {\n parts.push({ text: content });\n } else if (content && typeof content === 'object') {\n if (content.type === 'image_content') {\n parts.push(await toImagePart(content));\n } else {\n // Handle other content types as JSON\n parts.push({ text: JSON.stringify(content) });\n }\n }\n }\n } else if (msg.type === 'function_call') {\n parts.push({\n functionCall: {\n id: msg.callId,\n name: msg.name,\n args: JSON.parse(msg.args || '{}'),\n },\n });\n } else if (msg.type === 'function_call_output') {\n const response = msg.isError ? { error: msg.output } : { output: msg.output };\n parts.push({\n functionResponse: {\n id: msg.callId,\n name: msg.name,\n response,\n },\n });\n }\n }\n\n // Finalize the last turn\n if (currentRole !== null && parts.length > 0) {\n turns.push({ role: currentRole, parts });\n }\n\n // Gemini requires the last message to end with user's turn before they can generate\n if (injectDummyUserMessage && currentRole !== 'user') {\n turns.push({ role: 'user', parts: [{ text: '.' }] });\n }\n\n return [\n turns,\n {\n systemMessages: systemMessages.length > 0 ? systemMessages : null,\n },\n ];\n}\n\nasync function toImagePart(image: ImageContent): Promise<Record<string, unknown>> {\n const cacheKey = 'serialized_image';\n if (!image._cache[cacheKey]) {\n image._cache[cacheKey] = await serializeImage(image);\n }\n const img: SerializedImage = image._cache[cacheKey];\n\n if (img.externalUrl) {\n const mimeType = img.mimeType || 'image/jpeg';\n return {\n fileData: {\n fileUri: img.externalUrl,\n mimeType,\n },\n };\n }\n\n return {\n inlineData: {\n data: img.base64Data,\n mimeType: img.mimeType,\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/llm/provider_format/google.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext, ChatItem, ImageContent } from '../chat_context.js';\nimport { type SerializedImage, serializeImage } from '../utils.js';\nimport { groupToolCalls } from './utils.js';\n\nexport interface GoogleFormatData {\n systemMessages: string[] | null;\n}\n\nexport async function toChatCtx(\n chatCtx: ChatContext,\n injectDummyUserMessage: boolean = true,\n): Promise<[Record<string, unknown>[], GoogleFormatData]> {\n const turns: Record<string, unknown>[] = [];\n const systemMessages: string[] = [];\n let currentRole: string | null = null;\n let parts: Record<string, unknown>[] = [];\n\n // Flatten all grouped tool calls to get individual messages\n const itemGroups = groupToolCalls(chatCtx);\n const flattenedItems: ChatItem[] = [];\n\n for (const group of itemGroups) {\n flattenedItems.push(...group.flatten());\n }\n\n for (const msg of flattenedItems) {\n // Handle system messages separately\n if (msg.type === 'message' && msg.role === 'system' && msg.textContent) {\n systemMessages.push(msg.textContent);\n continue;\n }\n\n let role: string;\n if (msg.type === 'message') {\n role = msg.role === 'assistant' ? 'model' : 'user';\n } else if (msg.type === 'function_call') {\n role = 'model';\n } else if (msg.type === 'function_call_output') {\n role = 'user';\n } else {\n continue; // Skip unknown message types\n }\n\n // If the effective role changed, finalize the previous turn\n if (role !== currentRole) {\n if (currentRole !== null && parts.length > 0) {\n turns.push({ role: currentRole, parts: [...parts] });\n }\n parts = [];\n currentRole = role;\n }\n\n if (msg.type === 'message') {\n for (const content of msg.content) {\n if (content && typeof content === 'string') {\n parts.push({ text: content });\n } else if (content && typeof content === 'object') {\n if (content.type === 'image_content') {\n parts.push(await toImagePart(content));\n } else {\n // Handle other content types as JSON\n parts.push({ text: JSON.stringify(content) });\n }\n }\n }\n } else if (msg.type === 'function_call') {\n const functionCallPart: Record<string, unknown> = {\n functionCall: {\n id: msg.callId,\n name: msg.name,\n args: JSON.parse(msg.args || '{}'),\n },\n };\n if (msg.thoughtSignature) {\n functionCallPart.thoughtSignature = msg.thoughtSignature;\n }\n parts.push(functionCallPart);\n } else if (msg.type === 'function_call_output') {\n const response = msg.isError ? { error: msg.output } : { output: msg.output };\n parts.push({\n functionResponse: {\n id: msg.callId,\n name: msg.name,\n response,\n },\n });\n }\n }\n\n // Finalize the last turn\n if (currentRole !== null && parts.length > 0) {\n turns.push({ role: currentRole, parts });\n }\n\n // Gemini requires the last message to end with user's turn before they can generate\n if (injectDummyUserMessage && currentRole !== 'user') {\n turns.push({ role: 'user', parts: [{ text: '.' }] });\n }\n\n return [\n turns,\n {\n systemMessages: systemMessages.length > 0 ? systemMessages : null,\n },\n ];\n}\n\nasync function toImagePart(image: ImageContent): Promise<Record<string, unknown>> {\n const cacheKey = 'serialized_image';\n if (!image._cache[cacheKey]) {\n image._cache[cacheKey] = await serializeImage(image);\n }\n const img: SerializedImage = image._cache[cacheKey];\n\n if (img.externalUrl) {\n const mimeType = img.mimeType || 'image/jpeg';\n return {\n fileData: {\n fileUri: img.externalUrl,\n mimeType,\n },\n };\n }\n\n return {\n inlineData: {\n data: img.base64Data,\n mimeType: img.mimeType,\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/llm/realtime.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { EventEmitter } from 'events';\nimport type { ReadableStream } from 'node:stream/web';\nimport { DeferredReadableStream } from '../stream/deferred_stream.js';\nimport { Task } from '../utils.js';\nimport type { ChatContext, FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport type InputSpeechStartedEvent = object;\n\nexport interface InputSpeechStoppedEvent {\n userTranscriptionEnabled: boolean;\n}\n\nexport interface MessageGeneration {\n messageId: string;\n textStream: ReadableStream<string>;\n audioStream: ReadableStream<AudioFrame>;\n modalities?: Promise<('text' | 'audio')[]>;\n}\n\nexport interface GenerationCreatedEvent {\n messageStream: ReadableStream<MessageGeneration>;\n functionStream: ReadableStream<FunctionCall>;\n userInitiated: boolean;\n}\n\nexport interface RealtimeModelError {\n type: 'realtime_model_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport interface RealtimeCapabilities {\n messageTruncation: boolean;\n turnDetection: boolean;\n userTranscription: boolean;\n autoToolReplyGeneration: boolean;\n audioOutput: boolean;\n}\n\nexport interface InputTranscriptionCompleted {\n itemId: string;\n transcript: string;\n isFinal: boolean;\n}\n\nexport interface RealtimeSessionReconnectedEvent {}\n\nexport abstract class RealtimeModel {\n private _capabilities: RealtimeCapabilities;\n\n constructor(capabilities: RealtimeCapabilities) {\n this._capabilities = capabilities;\n }\n\n get capabilities() {\n return this._capabilities;\n }\n\n abstract session(): RealtimeSession;\n\n abstract close(): Promise<void>;\n}\n\nexport abstract class RealtimeSession extends EventEmitter {\n protected _realtimeModel: RealtimeModel;\n private deferredInputStream = new DeferredReadableStream<AudioFrame>();\n private _mainTask: Task<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super();\n this._realtimeModel = realtimeModel;\n this._mainTask = Task.from((controller) => this._mainTaskImpl(controller.signal));\n }\n\n get realtimeModel() {\n return this._realtimeModel;\n }\n\n abstract get chatCtx(): ChatContext;\n\n abstract get tools(): ToolContext;\n\n abstract updateInstructions(instructions: string): Promise<void>;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract updateChatCtx(chatCtx: ChatContext): Promise<void>;\n\n abstract updateTools(tools: ToolContext): Promise<void>;\n\n abstract updateOptions(options: { toolChoice?: ToolChoice | null }): void;\n\n abstract pushAudio(frame: AudioFrame): void;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract generateReply(instructions?: string): Promise<GenerationCreatedEvent>;\n\n /**\n * Commit the input audio buffer to the server\n */\n abstract commitAudio(): Promise<void>;\n\n /**\n * Clear the input audio buffer to the server\n */\n abstract clearAudio(): Promise<void>;\n\n /**\n * Cancel the current generation (do nothing if no generation is in progress)\n */\n abstract interrupt(): Promise<void>;\n\n /**\n * Truncate the message at the given audio end time\n */\n abstract truncate(options: {\n messageId: string;\n audioEndMs: number;\n modalities?: ('text' | 'audio')[];\n audioTranscript?: string;\n }): Promise<void>;\n\n async close(): Promise<void> {\n this._mainTask.cancel();\n }\n\n /**\n * Notifies the model that user activity has started\n */\n startUserActivity(): void {\n return;\n }\n\n private async _mainTaskImpl(signal: AbortSignal): Promise<void> {\n const reader = this.deferredInputStream.stream.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done || signal.aborted) {\n break;\n }\n this.pushAudio(value);\n }\n }\n\n setInputAudioStream(audioStream: ReadableStream<AudioFrame>): void {\n this.deferredInputStream.setSource(audioStream);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,oBAA6B;AAE7B,6BAAuC;AACvC,mBAAqB;AA+Cd,MAAe,cAAc;AAAA,EAC1B;AAAA,EAER,YAAY,cAAoC;AAC9C,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAI,eAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAKF;AAEO,MAAe,wBAAwB,2BAAa;AAAA,EAC/C;AAAA,EACF,sBAAsB,IAAI,8CAAmC;AAAA,EAC7D;AAAA,EAER,YAAY,eAA8B;AACxC,UAAM;AACN,SAAK,iBAAiB;AACtB,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,cAAc,WAAW,MAAM,CAAC;AAAA,EAClF;AAAA,EAEA,IAAI,gBAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAiDA,MAAM,QAAuB;AAC3B,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACxB;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAAoC;AAC9D,UAAM,SAAS,KAAK,oBAAoB,OAAO,UAAU;AACzD,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,QAAQ,OAAO,SAAS;AAC1B;AAAA,MACF;AACA,WAAK,UAAU,KAAK;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,oBAAoB,aAA+C;AACjE,SAAK,oBAAoB,UAAU,WAAW;AAAA,EAChD;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/llm/realtime.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { EventEmitter } from 'events';\nimport type { ReadableStream } from 'node:stream/web';\nimport { DeferredReadableStream } from '../stream/deferred_stream.js';\nimport { Task } from '../utils.js';\nimport type { ChatContext, FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport type InputSpeechStartedEvent = object;\n\nexport interface InputSpeechStoppedEvent {\n userTranscriptionEnabled: boolean;\n}\n\nexport interface MessageGeneration {\n messageId: string;\n textStream: ReadableStream<string>;\n audioStream: ReadableStream<AudioFrame>;\n modalities?: Promise<('text' | 'audio')[]>;\n}\n\nexport interface GenerationCreatedEvent {\n messageStream: ReadableStream<MessageGeneration>;\n functionStream: ReadableStream<FunctionCall>;\n userInitiated: boolean;\n /** Response ID for correlating metrics with spans */\n responseId?: string;\n}\n\nexport interface RealtimeModelError {\n type: 'realtime_model_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport interface RealtimeCapabilities {\n messageTruncation: boolean;\n turnDetection: boolean;\n userTranscription: boolean;\n autoToolReplyGeneration: boolean;\n audioOutput: boolean;\n}\n\nexport interface InputTranscriptionCompleted {\n itemId: string;\n transcript: string;\n isFinal: boolean;\n}\n\nexport interface RealtimeSessionReconnectedEvent {}\n\nexport abstract class RealtimeModel {\n private _capabilities: RealtimeCapabilities;\n\n constructor(capabilities: RealtimeCapabilities) {\n this._capabilities = capabilities;\n }\n\n get capabilities() {\n return this._capabilities;\n }\n\n /** The model name/identifier used by this realtime model */\n abstract get model(): string;\n\n abstract session(): RealtimeSession;\n\n abstract close(): Promise<void>;\n}\n\nexport abstract class RealtimeSession extends EventEmitter {\n protected _realtimeModel: RealtimeModel;\n private deferredInputStream = new DeferredReadableStream<AudioFrame>();\n private _mainTask: Task<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super();\n this._realtimeModel = realtimeModel;\n this._mainTask = Task.from((controller) => this._mainTaskImpl(controller.signal));\n }\n\n get realtimeModel() {\n return this._realtimeModel;\n }\n\n abstract get chatCtx(): ChatContext;\n\n abstract get tools(): ToolContext;\n\n abstract updateInstructions(instructions: string): Promise<void>;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract updateChatCtx(chatCtx: ChatContext): Promise<void>;\n\n abstract updateTools(tools: ToolContext): Promise<void>;\n\n abstract updateOptions(options: { toolChoice?: ToolChoice | null }): void;\n\n abstract pushAudio(frame: AudioFrame): void;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract generateReply(instructions?: string): Promise<GenerationCreatedEvent>;\n\n /**\n * Commit the input audio buffer to the server\n */\n abstract commitAudio(): Promise<void>;\n\n /**\n * Clear the input audio buffer to the server\n */\n abstract clearAudio(): Promise<void>;\n\n /**\n * Cancel the current generation (do nothing if no generation is in progress)\n */\n abstract interrupt(): Promise<void>;\n\n /**\n * Truncate the message at the given audio end time\n */\n abstract truncate(options: {\n messageId: string;\n audioEndMs: number;\n modalities?: ('text' | 'audio')[];\n audioTranscript?: string;\n }): Promise<void>;\n\n async close(): Promise<void> {\n this._mainTask.cancel();\n }\n\n /**\n * Notifies the model that user activity has started\n */\n startUserActivity(): void {\n return;\n }\n\n private async _mainTaskImpl(signal: AbortSignal): Promise<void> {\n const reader = this.deferredInputStream.stream.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done || signal.aborted) {\n break;\n }\n this.pushAudio(value);\n }\n }\n\n setInputAudioStream(audioStream: ReadableStream<AudioFrame>): void {\n this.deferredInputStream.setSource(audioStream);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,oBAA6B;AAE7B,6BAAuC;AACvC,mBAAqB;AAiDd,MAAe,cAAc;AAAA,EAC1B;AAAA,EAER,YAAY,cAAoC;AAC9C,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAI,eAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAQF;AAEO,MAAe,wBAAwB,2BAAa;AAAA,EAC/C;AAAA,EACF,sBAAsB,IAAI,8CAAmC;AAAA,EAC7D;AAAA,EAER,YAAY,eAA8B;AACxC,UAAM;AACN,SAAK,iBAAiB;AACtB,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,cAAc,WAAW,MAAM,CAAC;AAAA,EAClF;AAAA,EAEA,IAAI,gBAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAiDA,MAAM,QAAuB;AAC3B,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACxB;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAAoC;AAC9D,UAAM,SAAS,KAAK,oBAAoB,OAAO,UAAU;AACzD,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,QAAQ,OAAO,SAAS;AAC1B;AAAA,MACF;AACA,WAAK,UAAU,KAAK;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,oBAAoB,aAA+C;AACjE,SAAK,oBAAoB,UAAU,WAAW;AAAA,EAChD;AACF;","names":[]}
@@ -19,6 +19,8 @@ export interface GenerationCreatedEvent {
19
19
  messageStream: ReadableStream<MessageGeneration>;
20
20
  functionStream: ReadableStream<FunctionCall>;
21
21
  userInitiated: boolean;
22
+ /** Response ID for correlating metrics with spans */
23
+ responseId?: string;
22
24
  }
23
25
  export interface RealtimeModelError {
24
26
  type: 'realtime_model_error';
@@ -45,6 +47,8 @@ export declare abstract class RealtimeModel {
45
47
  private _capabilities;
46
48
  constructor(capabilities: RealtimeCapabilities);
47
49
  get capabilities(): RealtimeCapabilities;
50
+ /** The model name/identifier used by this realtime model */
51
+ abstract get model(): string;
48
52
  abstract session(): RealtimeSession;
49
53
  abstract close(): Promise<void>;
50
54
  }
@@ -19,6 +19,8 @@ export interface GenerationCreatedEvent {
19
19
  messageStream: ReadableStream<MessageGeneration>;
20
20
  functionStream: ReadableStream<FunctionCall>;
21
21
  userInitiated: boolean;
22
+ /** Response ID for correlating metrics with spans */
23
+ responseId?: string;
22
24
  }
23
25
  export interface RealtimeModelError {
24
26
  type: 'realtime_model_error';
@@ -45,6 +47,8 @@ export declare abstract class RealtimeModel {
45
47
  private _capabilities;
46
48
  constructor(capabilities: RealtimeCapabilities);
47
49
  get capabilities(): RealtimeCapabilities;
50
+ /** The model name/identifier used by this realtime model */
51
+ abstract get model(): string;
48
52
  abstract session(): RealtimeSession;
49
53
  abstract close(): Promise<void>;
50
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/llm/realtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/llm/realtime.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/llm/realtime.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { EventEmitter } from 'events';\nimport type { ReadableStream } from 'node:stream/web';\nimport { DeferredReadableStream } from '../stream/deferred_stream.js';\nimport { Task } from '../utils.js';\nimport type { ChatContext, FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport type InputSpeechStartedEvent = object;\n\nexport interface InputSpeechStoppedEvent {\n userTranscriptionEnabled: boolean;\n}\n\nexport interface MessageGeneration {\n messageId: string;\n textStream: ReadableStream<string>;\n audioStream: ReadableStream<AudioFrame>;\n modalities?: Promise<('text' | 'audio')[]>;\n}\n\nexport interface GenerationCreatedEvent {\n messageStream: ReadableStream<MessageGeneration>;\n functionStream: ReadableStream<FunctionCall>;\n userInitiated: boolean;\n}\n\nexport interface RealtimeModelError {\n type: 'realtime_model_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport interface RealtimeCapabilities {\n messageTruncation: boolean;\n turnDetection: boolean;\n userTranscription: boolean;\n autoToolReplyGeneration: boolean;\n audioOutput: boolean;\n}\n\nexport interface InputTranscriptionCompleted {\n itemId: string;\n transcript: string;\n isFinal: boolean;\n}\n\nexport interface RealtimeSessionReconnectedEvent {}\n\nexport abstract class RealtimeModel {\n private _capabilities: RealtimeCapabilities;\n\n constructor(capabilities: RealtimeCapabilities) {\n this._capabilities = capabilities;\n }\n\n get capabilities() {\n return this._capabilities;\n }\n\n abstract session(): RealtimeSession;\n\n abstract close(): Promise<void>;\n}\n\nexport abstract class RealtimeSession extends EventEmitter {\n protected _realtimeModel: RealtimeModel;\n private deferredInputStream = new DeferredReadableStream<AudioFrame>();\n private _mainTask: Task<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super();\n this._realtimeModel = realtimeModel;\n this._mainTask = Task.from((controller) => this._mainTaskImpl(controller.signal));\n }\n\n get realtimeModel() {\n return this._realtimeModel;\n }\n\n abstract get chatCtx(): ChatContext;\n\n abstract get tools(): ToolContext;\n\n abstract updateInstructions(instructions: string): Promise<void>;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract updateChatCtx(chatCtx: ChatContext): Promise<void>;\n\n abstract updateTools(tools: ToolContext): Promise<void>;\n\n abstract updateOptions(options: { toolChoice?: ToolChoice | null }): void;\n\n abstract pushAudio(frame: AudioFrame): void;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract generateReply(instructions?: string): Promise<GenerationCreatedEvent>;\n\n /**\n * Commit the input audio buffer to the server\n */\n abstract commitAudio(): Promise<void>;\n\n /**\n * Clear the input audio buffer to the server\n */\n abstract clearAudio(): Promise<void>;\n\n /**\n * Cancel the current generation (do nothing if no generation is in progress)\n */\n abstract interrupt(): Promise<void>;\n\n /**\n * Truncate the message at the given audio end time\n */\n abstract truncate(options: {\n messageId: string;\n audioEndMs: number;\n modalities?: ('text' | 'audio')[];\n audioTranscript?: string;\n }): Promise<void>;\n\n async close(): Promise<void> {\n this._mainTask.cancel();\n }\n\n /**\n * Notifies the model that user activity has started\n */\n startUserActivity(): void {\n return;\n }\n\n private async _mainTaskImpl(signal: AbortSignal): Promise<void> {\n const reader = this.deferredInputStream.stream.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done || signal.aborted) {\n break;\n }\n this.pushAudio(value);\n }\n }\n\n setInputAudioStream(audioStream: ReadableStream<AudioFrame>): void {\n this.deferredInputStream.setSource(audioStream);\n }\n}\n"],"mappings":"AAIA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,YAAY;AA+Cd,MAAe,cAAc;AAAA,EAC1B;AAAA,EAER,YAAY,cAAoC;AAC9C,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAI,eAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAKF;AAEO,MAAe,wBAAwB,aAAa;AAAA,EAC/C;AAAA,EACF,sBAAsB,IAAI,uBAAmC;AAAA,EAC7D;AAAA,EAER,YAAY,eAA8B;AACxC,UAAM;AACN,SAAK,iBAAiB;AACtB,SAAK,YAAY,KAAK,KAAK,CAAC,eAAe,KAAK,cAAc,WAAW,MAAM,CAAC;AAAA,EAClF;AAAA,EAEA,IAAI,gBAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAiDA,MAAM,QAAuB;AAC3B,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACxB;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAAoC;AAC9D,UAAM,SAAS,KAAK,oBAAoB,OAAO,UAAU;AACzD,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,QAAQ,OAAO,SAAS;AAC1B;AAAA,MACF;AACA,WAAK,UAAU,KAAK;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,oBAAoB,aAA+C;AACjE,SAAK,oBAAoB,UAAU,WAAW;AAAA,EAChD;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/llm/realtime.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { EventEmitter } from 'events';\nimport type { ReadableStream } from 'node:stream/web';\nimport { DeferredReadableStream } from '../stream/deferred_stream.js';\nimport { Task } from '../utils.js';\nimport type { ChatContext, FunctionCall } from './chat_context.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\nexport type InputSpeechStartedEvent = object;\n\nexport interface InputSpeechStoppedEvent {\n userTranscriptionEnabled: boolean;\n}\n\nexport interface MessageGeneration {\n messageId: string;\n textStream: ReadableStream<string>;\n audioStream: ReadableStream<AudioFrame>;\n modalities?: Promise<('text' | 'audio')[]>;\n}\n\nexport interface GenerationCreatedEvent {\n messageStream: ReadableStream<MessageGeneration>;\n functionStream: ReadableStream<FunctionCall>;\n userInitiated: boolean;\n /** Response ID for correlating metrics with spans */\n responseId?: string;\n}\n\nexport interface RealtimeModelError {\n type: 'realtime_model_error';\n timestamp: number;\n label: string;\n error: Error;\n recoverable: boolean;\n}\n\nexport interface RealtimeCapabilities {\n messageTruncation: boolean;\n turnDetection: boolean;\n userTranscription: boolean;\n autoToolReplyGeneration: boolean;\n audioOutput: boolean;\n}\n\nexport interface InputTranscriptionCompleted {\n itemId: string;\n transcript: string;\n isFinal: boolean;\n}\n\nexport interface RealtimeSessionReconnectedEvent {}\n\nexport abstract class RealtimeModel {\n private _capabilities: RealtimeCapabilities;\n\n constructor(capabilities: RealtimeCapabilities) {\n this._capabilities = capabilities;\n }\n\n get capabilities() {\n return this._capabilities;\n }\n\n /** The model name/identifier used by this realtime model */\n abstract get model(): string;\n\n abstract session(): RealtimeSession;\n\n abstract close(): Promise<void>;\n}\n\nexport abstract class RealtimeSession extends EventEmitter {\n protected _realtimeModel: RealtimeModel;\n private deferredInputStream = new DeferredReadableStream<AudioFrame>();\n private _mainTask: Task<void>;\n\n constructor(realtimeModel: RealtimeModel) {\n super();\n this._realtimeModel = realtimeModel;\n this._mainTask = Task.from((controller) => this._mainTaskImpl(controller.signal));\n }\n\n get realtimeModel() {\n return this._realtimeModel;\n }\n\n abstract get chatCtx(): ChatContext;\n\n abstract get tools(): ToolContext;\n\n abstract updateInstructions(instructions: string): Promise<void>;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract updateChatCtx(chatCtx: ChatContext): Promise<void>;\n\n abstract updateTools(tools: ToolContext): Promise<void>;\n\n abstract updateOptions(options: { toolChoice?: ToolChoice | null }): void;\n\n abstract pushAudio(frame: AudioFrame): void;\n\n /**\n * @throws RealtimeError on Timeout\n */\n abstract generateReply(instructions?: string): Promise<GenerationCreatedEvent>;\n\n /**\n * Commit the input audio buffer to the server\n */\n abstract commitAudio(): Promise<void>;\n\n /**\n * Clear the input audio buffer to the server\n */\n abstract clearAudio(): Promise<void>;\n\n /**\n * Cancel the current generation (do nothing if no generation is in progress)\n */\n abstract interrupt(): Promise<void>;\n\n /**\n * Truncate the message at the given audio end time\n */\n abstract truncate(options: {\n messageId: string;\n audioEndMs: number;\n modalities?: ('text' | 'audio')[];\n audioTranscript?: string;\n }): Promise<void>;\n\n async close(): Promise<void> {\n this._mainTask.cancel();\n }\n\n /**\n * Notifies the model that user activity has started\n */\n startUserActivity(): void {\n return;\n }\n\n private async _mainTaskImpl(signal: AbortSignal): Promise<void> {\n const reader = this.deferredInputStream.stream.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done || signal.aborted) {\n break;\n }\n this.pushAudio(value);\n }\n }\n\n setInputAudioStream(audioStream: ReadableStream<AudioFrame>): void {\n this.deferredInputStream.setSource(audioStream);\n }\n}\n"],"mappings":"AAIA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,YAAY;AAiDd,MAAe,cAAc;AAAA,EAC1B;AAAA,EAER,YAAY,cAAoC;AAC9C,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAI,eAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAQF;AAEO,MAAe,wBAAwB,aAAa;AAAA,EAC/C;AAAA,EACF,sBAAsB,IAAI,uBAAmC;AAAA,EAC7D;AAAA,EAER,YAAY,eAA8B;AACxC,UAAM;AACN,SAAK,iBAAiB;AACtB,SAAK,YAAY,KAAK,KAAK,CAAC,eAAe,KAAK,cAAc,WAAW,MAAM,CAAC;AAAA,EAClF;AAAA,EAEA,IAAI,gBAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAiDA,MAAM,QAAuB;AAC3B,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACxB;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAAoC;AAC9D,UAAM,SAAS,KAAK,oBAAoB,OAAO,UAAU;AACzD,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,QAAQ,OAAO,SAAS;AAC1B;AAAA,MACF;AACA,WAAK,UAAU,KAAK;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,oBAAoB,aAA+C;AACjE,SAAK,oBAAoB,UAAU,WAAW;AAAA,EAChD;AACF;","names":[]}
@@ -25,18 +25,25 @@ var import_identity_transform = require("./identity_transform.cjs");
25
25
  function createStreamChannel() {
26
26
  const transform = new import_identity_transform.IdentityTransform();
27
27
  const writer = transform.writable.getWriter();
28
+ let isClosed = false;
28
29
  return {
29
30
  write: (chunk) => writer.write(chunk),
30
31
  stream: () => transform.readable,
31
32
  close: async () => {
32
33
  try {
33
- return await writer.close();
34
+ const result = await writer.close();
35
+ isClosed = true;
36
+ return result;
34
37
  } catch (e) {
35
38
  if (e instanceof Error && e.name === "TypeError") {
39
+ isClosed = true;
36
40
  return;
37
41
  }
38
42
  throw e;
39
43
  }
44
+ },
45
+ get closed() {
46
+ return isClosed;
40
47
  }
41
48
  };
42
49
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stream/stream_channel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ReadableStream } from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\nexport interface StreamChannel<T> {\n write(chunk: T): Promise<void>;\n close(): Promise<void>;\n stream(): ReadableStream<T>;\n}\n\nexport function createStreamChannel<T>(): StreamChannel<T> {\n const transform = new IdentityTransform<T>();\n const writer = transform.writable.getWriter();\n\n return {\n write: (chunk: T) => writer.write(chunk),\n stream: () => transform.readable,\n close: async () => {\n try {\n return await writer.close();\n } catch (e) {\n if (e instanceof Error && e.name === 'TypeError') {\n // Ignore error if the stream is already closed\n return;\n }\n throw e;\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,gCAAkC;AAQ3B,SAAS,sBAA2C;AACzD,QAAM,YAAY,IAAI,4CAAqB;AAC3C,QAAM,SAAS,UAAU,SAAS,UAAU;AAE5C,SAAO;AAAA,IACL,OAAO,CAAC,UAAa,OAAO,MAAM,KAAK;AAAA,IACvC,QAAQ,MAAM,UAAU;AAAA,IACxB,OAAO,YAAY;AACjB,UAAI;AACF,eAAO,MAAM,OAAO,MAAM;AAAA,MAC5B,SAAS,GAAG;AACV,YAAI,aAAa,SAAS,EAAE,SAAS,aAAa;AAEhD;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/stream/stream_channel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ReadableStream } from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\nexport interface StreamChannel<T> {\n write(chunk: T): Promise<void>;\n close(): Promise<void>;\n stream(): ReadableStream<T>;\n readonly closed: boolean;\n}\n\nexport function createStreamChannel<T>(): StreamChannel<T> {\n const transform = new IdentityTransform<T>();\n const writer = transform.writable.getWriter();\n let isClosed = false;\n\n return {\n write: (chunk: T) => writer.write(chunk),\n stream: () => transform.readable,\n close: async () => {\n try {\n const result = await writer.close();\n isClosed = true;\n return result;\n } catch (e) {\n if (e instanceof Error && e.name === 'TypeError') {\n // Ignore error if the stream is already closed\n isClosed = true;\n return;\n }\n throw e;\n }\n },\n get closed() {\n return isClosed;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,gCAAkC;AAS3B,SAAS,sBAA2C;AACzD,QAAM,YAAY,IAAI,4CAAqB;AAC3C,QAAM,SAAS,UAAU,SAAS,UAAU;AAC5C,MAAI,WAAW;AAEf,SAAO;AAAA,IACL,OAAO,CAAC,UAAa,OAAO,MAAM,KAAK;AAAA,IACvC,QAAQ,MAAM,UAAU;AAAA,IACxB,OAAO,YAAY;AACjB,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,MAAM;AAClC,mBAAW;AACX,eAAO;AAAA,MACT,SAAS,GAAG;AACV,YAAI,aAAa,SAAS,EAAE,SAAS,aAAa;AAEhD,qBAAW;AACX;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -4,6 +4,7 @@ export interface StreamChannel<T> {
4
4
  write(chunk: T): Promise<void>;
5
5
  close(): Promise<void>;
6
6
  stream(): ReadableStream<T>;
7
+ readonly closed: boolean;
7
8
  }
8
9
  export declare function createStreamChannel<T>(): StreamChannel<T>;
9
10
  //# sourceMappingURL=stream_channel.d.ts.map
@@ -4,6 +4,7 @@ export interface StreamChannel<T> {
4
4
  write(chunk: T): Promise<void>;
5
5
  close(): Promise<void>;
6
6
  stream(): ReadableStream<T>;
7
+ readonly closed: boolean;
7
8
  }
8
9
  export declare function createStreamChannel<T>(): StreamChannel<T>;
9
10
  //# sourceMappingURL=stream_channel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream_channel.d.ts","sourceRoot":"","sources":["../../src/stream/stream_channel.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,CAmBzD"}
1
+ {"version":3,"file":"stream_channel.d.ts","sourceRoot":"","sources":["../../src/stream/stream_channel.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,CA0BzD"}
@@ -2,18 +2,25 @@ import { IdentityTransform } from "./identity_transform.js";
2
2
  function createStreamChannel() {
3
3
  const transform = new IdentityTransform();
4
4
  const writer = transform.writable.getWriter();
5
+ let isClosed = false;
5
6
  return {
6
7
  write: (chunk) => writer.write(chunk),
7
8
  stream: () => transform.readable,
8
9
  close: async () => {
9
10
  try {
10
- return await writer.close();
11
+ const result = await writer.close();
12
+ isClosed = true;
13
+ return result;
11
14
  } catch (e) {
12
15
  if (e instanceof Error && e.name === "TypeError") {
16
+ isClosed = true;
13
17
  return;
14
18
  }
15
19
  throw e;
16
20
  }
21
+ },
22
+ get closed() {
23
+ return isClosed;
17
24
  }
18
25
  };
19
26
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stream/stream_channel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ReadableStream } from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\nexport interface StreamChannel<T> {\n write(chunk: T): Promise<void>;\n close(): Promise<void>;\n stream(): ReadableStream<T>;\n}\n\nexport function createStreamChannel<T>(): StreamChannel<T> {\n const transform = new IdentityTransform<T>();\n const writer = transform.writable.getWriter();\n\n return {\n write: (chunk: T) => writer.write(chunk),\n stream: () => transform.readable,\n close: async () => {\n try {\n return await writer.close();\n } catch (e) {\n if (e instanceof Error && e.name === 'TypeError') {\n // Ignore error if the stream is already closed\n return;\n }\n throw e;\n }\n },\n };\n}\n"],"mappings":"AAIA,SAAS,yBAAyB;AAQ3B,SAAS,sBAA2C;AACzD,QAAM,YAAY,IAAI,kBAAqB;AAC3C,QAAM,SAAS,UAAU,SAAS,UAAU;AAE5C,SAAO;AAAA,IACL,OAAO,CAAC,UAAa,OAAO,MAAM,KAAK;AAAA,IACvC,QAAQ,MAAM,UAAU;AAAA,IACxB,OAAO,YAAY;AACjB,UAAI;AACF,eAAO,MAAM,OAAO,MAAM;AAAA,MAC5B,SAAS,GAAG;AACV,YAAI,aAAa,SAAS,EAAE,SAAS,aAAa;AAEhD;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/stream/stream_channel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ReadableStream } from 'node:stream/web';\nimport { IdentityTransform } from './identity_transform.js';\n\nexport interface StreamChannel<T> {\n write(chunk: T): Promise<void>;\n close(): Promise<void>;\n stream(): ReadableStream<T>;\n readonly closed: boolean;\n}\n\nexport function createStreamChannel<T>(): StreamChannel<T> {\n const transform = new IdentityTransform<T>();\n const writer = transform.writable.getWriter();\n let isClosed = false;\n\n return {\n write: (chunk: T) => writer.write(chunk),\n stream: () => transform.readable,\n close: async () => {\n try {\n const result = await writer.close();\n isClosed = true;\n return result;\n } catch (e) {\n if (e instanceof Error && e.name === 'TypeError') {\n // Ignore error if the stream is already closed\n isClosed = true;\n return;\n }\n throw e;\n }\n },\n get closed() {\n return isClosed;\n },\n };\n}\n"],"mappings":"AAIA,SAAS,yBAAyB;AAS3B,SAAS,sBAA2C;AACzD,QAAM,YAAY,IAAI,kBAAqB;AAC3C,QAAM,SAAS,UAAU,SAAS,UAAU;AAC5C,MAAI,WAAW;AAEf,SAAO;AAAA,IACL,OAAO,CAAC,UAAa,OAAO,MAAM,KAAK;AAAA,IACvC,QAAQ,MAAM,UAAU;AAAA,IACxB,OAAO,YAAY;AACjB,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,MAAM;AAClC,mBAAW;AACX,eAAO;AAAA,MACT,SAAS,GAAG;AACV,YAAI,aAAa,SAAS,EAAE,SAAS,aAAa;AAEhD,qBAAW;AACX;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -75,16 +75,19 @@ class SimpleOTLPHttpLogExporter {
75
75
  key,
76
76
  value: { stringValue: value }
77
77
  })) : [];
78
- const logRecords = records.map((record) => ({
79
- timeUnixNano: String(BigInt(Math.floor(record.timestampMs * 1e6))),
80
- observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1e6)),
81
- severityNumber: record.severityNumber ?? import_api_logs.SeverityNumber.UNSPECIFIED,
82
- severityText: record.severityText ?? "unspecified",
83
- body: { stringValue: record.body },
84
- attributes: this.convertAttributes(record.attributes),
85
- traceId: "",
86
- spanId: ""
87
- }));
78
+ const logRecords = records.map((record) => {
79
+ const timestampMs = Number.isFinite(record.timestampMs) ? record.timestampMs : Date.now();
80
+ return {
81
+ timeUnixNano: String(BigInt(Math.floor(timestampMs * 1e6))),
82
+ observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1e6)),
83
+ severityNumber: record.severityNumber ?? import_api_logs.SeverityNumber.UNSPECIFIED,
84
+ severityText: record.severityText ?? "unspecified",
85
+ body: { stringValue: record.body },
86
+ attributes: this.convertAttributes(record.attributes),
87
+ traceId: "",
88
+ spanId: ""
89
+ };
90
+ });
88
91
  return {
89
92
  resourceLogs: [
90
93
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/telemetry/otel_http_exporter.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * OTLP HTTP JSON Log Exporter for LiveKit Cloud\n *\n * This module provides a custom OTLP log exporter that uses HTTP with JSON format\n * instead of the default protobuf format.\n */\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { AccessToken } from 'livekit-server-sdk';\n\nexport interface SimpleLogRecord {\n /** Log message body */\n body: string;\n /** Timestamp in milliseconds since epoch */\n timestampMs: number;\n /** Log attributes */\n attributes: Record<string, unknown>;\n /** Severity number (default: UNSPECIFIED) */\n severityNumber?: SeverityNumber;\n /** Severity text (default: 'unspecified') */\n severityText?: string;\n}\n\nexport interface SimpleOTLPHttpLogExporterConfig {\n /** LiveKit Cloud hostname */\n cloudHostname: string;\n /** Resource attributes (e.g., room_id, job_id) */\n resourceAttributes: Record<string, string>;\n /** Scope name for the logger */\n scopeName: string;\n /** Scope attributes */\n scopeAttributes?: Record<string, string>;\n}\n\n/**\n * Simple OTLP HTTP Log Exporter for direct log export\n *\n * This is a simplified exporter that doesn't require the full SDK infrastructure.\n * Use this when you need to send logs directly without LoggerProvider.\n *\n * @example\n * ```typescript\n * const exporter = new SimpleOTLPHttpLogExporter({\n * cloudHostname: 'cloud.livekit.io',\n * resourceAttributes: { room_id: 'xxx', job_id: 'yyy' },\n * scopeName: 'chat_history',\n * });\n *\n * await exporter.export([\n * { body: 'Hello', timestampMs: Date.now(), attributes: { test: true } },\n * ]);\n * ```\n */\nexport class SimpleOTLPHttpLogExporter {\n private readonly config: SimpleOTLPHttpLogExporterConfig;\n private jwt: string | null = null;\n\n constructor(config: SimpleOTLPHttpLogExporterConfig) {\n this.config = config;\n }\n\n /**\n * Export simple log records\n */\n async export(records: SimpleLogRecord[]): Promise<void> {\n if (records.length === 0) return;\n\n await this.ensureJwt();\n\n const endpoint = `https://${this.config.cloudHostname}/observability/logs/otlp/v0`;\n const payload = this.buildPayload(records);\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.jwt}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `OTLP log export failed: ${response.status} ${response.statusText} - ${text}`,\n );\n }\n }\n\n private async ensureJwt(): Promise<void> {\n if (this.jwt) return;\n\n const apiKey = process.env.LIVEKIT_API_KEY;\n const apiSecret = process.env.LIVEKIT_API_SECRET;\n\n if (!apiKey || !apiSecret) {\n throw new Error('LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set');\n }\n\n const token = new AccessToken(apiKey, apiSecret, { ttl: '6h' });\n token.addObservabilityGrant({ write: true });\n this.jwt = await token.toJwt();\n }\n\n private buildPayload(records: SimpleLogRecord[]): object {\n const resourceAttrs = Object.entries(this.config.resourceAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n\n if (!this.config.resourceAttributes['service.name']) {\n resourceAttrs.push({ key: 'service.name', value: { stringValue: 'livekit-agents' } });\n }\n\n const scopeAttrs = this.config.scopeAttributes\n ? Object.entries(this.config.scopeAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }))\n : [];\n\n const logRecords = records.map((record) => ({\n timeUnixNano: String(BigInt(Math.floor(record.timestampMs * 1_000_000))),\n observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1_000_000)),\n severityNumber: record.severityNumber ?? SeverityNumber.UNSPECIFIED,\n severityText: record.severityText ?? 'unspecified',\n body: { stringValue: record.body },\n attributes: this.convertAttributes(record.attributes),\n traceId: '',\n spanId: '',\n }));\n\n return {\n resourceLogs: [\n {\n resource: { attributes: resourceAttrs },\n scopeLogs: [\n {\n scope: {\n name: this.config.scopeName,\n attributes: scopeAttrs,\n },\n logRecords,\n },\n ],\n },\n ],\n };\n }\n\n private convertAttributes(\n attrs: Record<string, unknown>,\n ): Array<{ key: string; value: unknown }> {\n return Object.entries(attrs).map(([key, value]) => ({\n key,\n value: this.convertValue(value),\n }));\n }\n\n private convertValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return { stringValue: '' };\n }\n if (typeof value === 'string') {\n return { stringValue: value };\n }\n if (typeof value === 'number') {\n return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };\n }\n if (typeof value === 'boolean') {\n return { boolValue: value };\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map((v) => this.convertValue(v)) } };\n }\n if (typeof value === 'object') {\n return {\n kvlistValue: {\n values: Object.entries(value as Record<string, unknown>).map(([k, v]) => ({\n key: k,\n value: this.convertValue(v),\n })),\n },\n };\n }\n return { stringValue: String(value) };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/telemetry/otel_http_exporter.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * OTLP HTTP JSON Log Exporter for LiveKit Cloud\n *\n * This module provides a custom OTLP log exporter that uses HTTP with JSON format\n * instead of the default protobuf format.\n */\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { AccessToken } from 'livekit-server-sdk';\n\nexport interface SimpleLogRecord {\n /** Log message body */\n body: string;\n /** Timestamp in milliseconds since epoch */\n timestampMs: number;\n /** Log attributes */\n attributes: Record<string, unknown>;\n /** Severity number (default: UNSPECIFIED) */\n severityNumber?: SeverityNumber;\n /** Severity text (default: 'unspecified') */\n severityText?: string;\n}\n\nexport interface SimpleOTLPHttpLogExporterConfig {\n /** LiveKit Cloud hostname */\n cloudHostname: string;\n /** Resource attributes (e.g., room_id, job_id) */\n resourceAttributes: Record<string, string>;\n /** Scope name for the logger */\n scopeName: string;\n /** Scope attributes */\n scopeAttributes?: Record<string, string>;\n}\n\n/**\n * Simple OTLP HTTP Log Exporter for direct log export\n *\n * This is a simplified exporter that doesn't require the full SDK infrastructure.\n * Use this when you need to send logs directly without LoggerProvider.\n *\n * @example\n * ```typescript\n * const exporter = new SimpleOTLPHttpLogExporter({\n * cloudHostname: 'cloud.livekit.io',\n * resourceAttributes: { room_id: 'xxx', job_id: 'yyy' },\n * scopeName: 'chat_history',\n * });\n *\n * await exporter.export([\n * { body: 'Hello', timestampMs: Date.now(), attributes: { test: true } },\n * ]);\n * ```\n */\nexport class SimpleOTLPHttpLogExporter {\n private readonly config: SimpleOTLPHttpLogExporterConfig;\n private jwt: string | null = null;\n\n constructor(config: SimpleOTLPHttpLogExporterConfig) {\n this.config = config;\n }\n\n /**\n * Export simple log records\n */\n async export(records: SimpleLogRecord[]): Promise<void> {\n if (records.length === 0) return;\n\n await this.ensureJwt();\n\n const endpoint = `https://${this.config.cloudHostname}/observability/logs/otlp/v0`;\n const payload = this.buildPayload(records);\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.jwt}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `OTLP log export failed: ${response.status} ${response.statusText} - ${text}`,\n );\n }\n }\n\n private async ensureJwt(): Promise<void> {\n if (this.jwt) return;\n\n const apiKey = process.env.LIVEKIT_API_KEY;\n const apiSecret = process.env.LIVEKIT_API_SECRET;\n\n if (!apiKey || !apiSecret) {\n throw new Error('LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set');\n }\n\n const token = new AccessToken(apiKey, apiSecret, { ttl: '6h' });\n token.addObservabilityGrant({ write: true });\n this.jwt = await token.toJwt();\n }\n\n private buildPayload(records: SimpleLogRecord[]): object {\n const resourceAttrs = Object.entries(this.config.resourceAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n\n if (!this.config.resourceAttributes['service.name']) {\n resourceAttrs.push({ key: 'service.name', value: { stringValue: 'livekit-agents' } });\n }\n\n const scopeAttrs = this.config.scopeAttributes\n ? Object.entries(this.config.scopeAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }))\n : [];\n\n const logRecords = records.map((record) => {\n // Ensure timestampMs is a valid number, fallback to current time if NaN/undefined\n const timestampMs = Number.isFinite(record.timestampMs) ? record.timestampMs : Date.now();\n return {\n timeUnixNano: String(BigInt(Math.floor(timestampMs * 1_000_000))),\n observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1_000_000)),\n severityNumber: record.severityNumber ?? SeverityNumber.UNSPECIFIED,\n severityText: record.severityText ?? 'unspecified',\n body: { stringValue: record.body },\n attributes: this.convertAttributes(record.attributes),\n traceId: '',\n spanId: '',\n };\n });\n\n return {\n resourceLogs: [\n {\n resource: { attributes: resourceAttrs },\n scopeLogs: [\n {\n scope: {\n name: this.config.scopeName,\n attributes: scopeAttrs,\n },\n logRecords,\n },\n ],\n },\n ],\n };\n }\n\n private convertAttributes(\n attrs: Record<string, unknown>,\n ): Array<{ key: string; value: unknown }> {\n return Object.entries(attrs).map(([key, value]) => ({\n key,\n value: this.convertValue(value),\n }));\n }\n\n private convertValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return { stringValue: '' };\n }\n if (typeof value === 'string') {\n return { stringValue: value };\n }\n if (typeof value === 'number') {\n return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };\n }\n if (typeof value === 'boolean') {\n return { boolValue: value };\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map((v) => this.convertValue(v)) } };\n }\n if (typeof value === 'object') {\n return {\n kvlistValue: {\n values: Object.entries(value as Record<string, unknown>).map(([k, v]) => ({\n key: k,\n value: this.convertValue(v),\n })),\n },\n };\n }\n return { stringValue: String(value) };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"otel_http_exporter.d.ts","sourceRoot":"","sources":["../../src/telemetry/otel_http_exporter.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,MAAM,WAAW,eAAe;IAC9B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,6CAA6C;IAC7C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,+BAA+B;IAC9C,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,GAAG,CAAuB;gBAEtB,MAAM,EAAE,+BAA+B;IAInD;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAyBzC,SAAS;IAevB,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,YAAY;CA4BrB"}
1
+ {"version":3,"file":"otel_http_exporter.d.ts","sourceRoot":"","sources":["../../src/telemetry/otel_http_exporter.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,MAAM,WAAW,eAAe;IAC9B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,6CAA6C;IAC7C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,+BAA+B;IAC9C,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,GAAG,CAAuB;gBAEtB,MAAM,EAAE,+BAA+B;IAInD;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAyBzC,SAAS;IAevB,OAAO,CAAC,YAAY;IAkDpB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,YAAY;CA4BrB"}
@@ -52,16 +52,19 @@ class SimpleOTLPHttpLogExporter {
52
52
  key,
53
53
  value: { stringValue: value }
54
54
  })) : [];
55
- const logRecords = records.map((record) => ({
56
- timeUnixNano: String(BigInt(Math.floor(record.timestampMs * 1e6))),
57
- observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1e6)),
58
- severityNumber: record.severityNumber ?? SeverityNumber.UNSPECIFIED,
59
- severityText: record.severityText ?? "unspecified",
60
- body: { stringValue: record.body },
61
- attributes: this.convertAttributes(record.attributes),
62
- traceId: "",
63
- spanId: ""
64
- }));
55
+ const logRecords = records.map((record) => {
56
+ const timestampMs = Number.isFinite(record.timestampMs) ? record.timestampMs : Date.now();
57
+ return {
58
+ timeUnixNano: String(BigInt(Math.floor(timestampMs * 1e6))),
59
+ observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1e6)),
60
+ severityNumber: record.severityNumber ?? SeverityNumber.UNSPECIFIED,
61
+ severityText: record.severityText ?? "unspecified",
62
+ body: { stringValue: record.body },
63
+ attributes: this.convertAttributes(record.attributes),
64
+ traceId: "",
65
+ spanId: ""
66
+ };
67
+ });
65
68
  return {
66
69
  resourceLogs: [
67
70
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/telemetry/otel_http_exporter.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * OTLP HTTP JSON Log Exporter for LiveKit Cloud\n *\n * This module provides a custom OTLP log exporter that uses HTTP with JSON format\n * instead of the default protobuf format.\n */\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { AccessToken } from 'livekit-server-sdk';\n\nexport interface SimpleLogRecord {\n /** Log message body */\n body: string;\n /** Timestamp in milliseconds since epoch */\n timestampMs: number;\n /** Log attributes */\n attributes: Record<string, unknown>;\n /** Severity number (default: UNSPECIFIED) */\n severityNumber?: SeverityNumber;\n /** Severity text (default: 'unspecified') */\n severityText?: string;\n}\n\nexport interface SimpleOTLPHttpLogExporterConfig {\n /** LiveKit Cloud hostname */\n cloudHostname: string;\n /** Resource attributes (e.g., room_id, job_id) */\n resourceAttributes: Record<string, string>;\n /** Scope name for the logger */\n scopeName: string;\n /** Scope attributes */\n scopeAttributes?: Record<string, string>;\n}\n\n/**\n * Simple OTLP HTTP Log Exporter for direct log export\n *\n * This is a simplified exporter that doesn't require the full SDK infrastructure.\n * Use this when you need to send logs directly without LoggerProvider.\n *\n * @example\n * ```typescript\n * const exporter = new SimpleOTLPHttpLogExporter({\n * cloudHostname: 'cloud.livekit.io',\n * resourceAttributes: { room_id: 'xxx', job_id: 'yyy' },\n * scopeName: 'chat_history',\n * });\n *\n * await exporter.export([\n * { body: 'Hello', timestampMs: Date.now(), attributes: { test: true } },\n * ]);\n * ```\n */\nexport class SimpleOTLPHttpLogExporter {\n private readonly config: SimpleOTLPHttpLogExporterConfig;\n private jwt: string | null = null;\n\n constructor(config: SimpleOTLPHttpLogExporterConfig) {\n this.config = config;\n }\n\n /**\n * Export simple log records\n */\n async export(records: SimpleLogRecord[]): Promise<void> {\n if (records.length === 0) return;\n\n await this.ensureJwt();\n\n const endpoint = `https://${this.config.cloudHostname}/observability/logs/otlp/v0`;\n const payload = this.buildPayload(records);\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.jwt}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `OTLP log export failed: ${response.status} ${response.statusText} - ${text}`,\n );\n }\n }\n\n private async ensureJwt(): Promise<void> {\n if (this.jwt) return;\n\n const apiKey = process.env.LIVEKIT_API_KEY;\n const apiSecret = process.env.LIVEKIT_API_SECRET;\n\n if (!apiKey || !apiSecret) {\n throw new Error('LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set');\n }\n\n const token = new AccessToken(apiKey, apiSecret, { ttl: '6h' });\n token.addObservabilityGrant({ write: true });\n this.jwt = await token.toJwt();\n }\n\n private buildPayload(records: SimpleLogRecord[]): object {\n const resourceAttrs = Object.entries(this.config.resourceAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n\n if (!this.config.resourceAttributes['service.name']) {\n resourceAttrs.push({ key: 'service.name', value: { stringValue: 'livekit-agents' } });\n }\n\n const scopeAttrs = this.config.scopeAttributes\n ? Object.entries(this.config.scopeAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }))\n : [];\n\n const logRecords = records.map((record) => ({\n timeUnixNano: String(BigInt(Math.floor(record.timestampMs * 1_000_000))),\n observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1_000_000)),\n severityNumber: record.severityNumber ?? SeverityNumber.UNSPECIFIED,\n severityText: record.severityText ?? 'unspecified',\n body: { stringValue: record.body },\n attributes: this.convertAttributes(record.attributes),\n traceId: '',\n spanId: '',\n }));\n\n return {\n resourceLogs: [\n {\n resource: { attributes: resourceAttrs },\n scopeLogs: [\n {\n scope: {\n name: this.config.scopeName,\n attributes: scopeAttrs,\n },\n logRecords,\n },\n ],\n },\n ],\n };\n }\n\n private convertAttributes(\n attrs: Record<string, unknown>,\n ): Array<{ key: string; value: unknown }> {\n return Object.entries(attrs).map(([key, value]) => ({\n key,\n value: this.convertValue(value),\n }));\n }\n\n private convertValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return { stringValue: '' };\n }\n if (typeof value === 'string') {\n return { stringValue: value };\n }\n if (typeof value === 'number') {\n return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };\n }\n if (typeof value === 'boolean') {\n return { boolValue: value };\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map((v) => this.convertValue(v)) } };\n }\n if (typeof value === 'object') {\n return {\n kvlistValue: {\n values: Object.entries(value as Record<string, unknown>).map(([k, v]) => ({\n key: k,\n value: this.convertValue(v),\n })),\n },\n };\n }\n return { stringValue: String(value) };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/telemetry/otel_http_exporter.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * OTLP HTTP JSON Log Exporter for LiveKit Cloud\n *\n * This module provides a custom OTLP log exporter that uses HTTP with JSON format\n * instead of the default protobuf format.\n */\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport { AccessToken } from 'livekit-server-sdk';\n\nexport interface SimpleLogRecord {\n /** Log message body */\n body: string;\n /** Timestamp in milliseconds since epoch */\n timestampMs: number;\n /** Log attributes */\n attributes: Record<string, unknown>;\n /** Severity number (default: UNSPECIFIED) */\n severityNumber?: SeverityNumber;\n /** Severity text (default: 'unspecified') */\n severityText?: string;\n}\n\nexport interface SimpleOTLPHttpLogExporterConfig {\n /** LiveKit Cloud hostname */\n cloudHostname: string;\n /** Resource attributes (e.g., room_id, job_id) */\n resourceAttributes: Record<string, string>;\n /** Scope name for the logger */\n scopeName: string;\n /** Scope attributes */\n scopeAttributes?: Record<string, string>;\n}\n\n/**\n * Simple OTLP HTTP Log Exporter for direct log export\n *\n * This is a simplified exporter that doesn't require the full SDK infrastructure.\n * Use this when you need to send logs directly without LoggerProvider.\n *\n * @example\n * ```typescript\n * const exporter = new SimpleOTLPHttpLogExporter({\n * cloudHostname: 'cloud.livekit.io',\n * resourceAttributes: { room_id: 'xxx', job_id: 'yyy' },\n * scopeName: 'chat_history',\n * });\n *\n * await exporter.export([\n * { body: 'Hello', timestampMs: Date.now(), attributes: { test: true } },\n * ]);\n * ```\n */\nexport class SimpleOTLPHttpLogExporter {\n private readonly config: SimpleOTLPHttpLogExporterConfig;\n private jwt: string | null = null;\n\n constructor(config: SimpleOTLPHttpLogExporterConfig) {\n this.config = config;\n }\n\n /**\n * Export simple log records\n */\n async export(records: SimpleLogRecord[]): Promise<void> {\n if (records.length === 0) return;\n\n await this.ensureJwt();\n\n const endpoint = `https://${this.config.cloudHostname}/observability/logs/otlp/v0`;\n const payload = this.buildPayload(records);\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.jwt}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(\n `OTLP log export failed: ${response.status} ${response.statusText} - ${text}`,\n );\n }\n }\n\n private async ensureJwt(): Promise<void> {\n if (this.jwt) return;\n\n const apiKey = process.env.LIVEKIT_API_KEY;\n const apiSecret = process.env.LIVEKIT_API_SECRET;\n\n if (!apiKey || !apiSecret) {\n throw new Error('LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set');\n }\n\n const token = new AccessToken(apiKey, apiSecret, { ttl: '6h' });\n token.addObservabilityGrant({ write: true });\n this.jwt = await token.toJwt();\n }\n\n private buildPayload(records: SimpleLogRecord[]): object {\n const resourceAttrs = Object.entries(this.config.resourceAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }));\n\n if (!this.config.resourceAttributes['service.name']) {\n resourceAttrs.push({ key: 'service.name', value: { stringValue: 'livekit-agents' } });\n }\n\n const scopeAttrs = this.config.scopeAttributes\n ? Object.entries(this.config.scopeAttributes).map(([key, value]) => ({\n key,\n value: { stringValue: value },\n }))\n : [];\n\n const logRecords = records.map((record) => {\n // Ensure timestampMs is a valid number, fallback to current time if NaN/undefined\n const timestampMs = Number.isFinite(record.timestampMs) ? record.timestampMs : Date.now();\n return {\n timeUnixNano: String(BigInt(Math.floor(timestampMs * 1_000_000))),\n observedTimeUnixNano: String(BigInt(Date.now()) * BigInt(1_000_000)),\n severityNumber: record.severityNumber ?? SeverityNumber.UNSPECIFIED,\n severityText: record.severityText ?? 'unspecified',\n body: { stringValue: record.body },\n attributes: this.convertAttributes(record.attributes),\n traceId: '',\n spanId: '',\n };\n });\n\n return {\n resourceLogs: [\n {\n resource: { attributes: resourceAttrs },\n scopeLogs: [\n {\n scope: {\n name: this.config.scopeName,\n attributes: scopeAttrs,\n },\n logRecords,\n },\n ],\n },\n ],\n };\n }\n\n private convertAttributes(\n attrs: Record<string, unknown>,\n ): Array<{ key: string; value: unknown }> {\n return Object.entries(attrs).map(([key, value]) => ({\n key,\n value: this.convertValue(value),\n }));\n }\n\n private convertValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return { stringValue: '' };\n }\n if (typeof value === 'string') {\n return { stringValue: value };\n }\n if (typeof value === 'number') {\n return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };\n }\n if (typeof value === 'boolean') {\n return { boolValue: value };\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map((v) => this.convertValue(v)) } };\n }\n if (typeof value === 'object') {\n return {\n kvlistValue: {\n values: Object.entries(value as Record<string, unknown>).map(([k, v]) => ({\n key: k,\n value: this.convertValue(v),\n })),\n },\n };\n }\n return { stringValue: String(value) };\n }\n}\n"],"mappings":"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","names":[]}
@@ -306,7 +306,9 @@ async function uploadSessionReport(options) {
306
306
  });
307
307
  let lastTimestamp = 0;
308
308
  for (const item of report.chatHistory.items) {
309
- let itemTimestamp = item.createdAt;
309
+ if (!item) continue;
310
+ const hasValidTimestamp = Number.isFinite(item.createdAt);
311
+ let itemTimestamp = hasValidTimestamp ? item.createdAt : Date.now();
310
312
  if (itemTimestamp <= lastTimestamp) {
311
313
  itemTimestamp = lastTimestamp + 1e-3;
312
314
  }
@@ -404,12 +406,28 @@ async function uploadSessionReport(options) {
404
406
  return;
405
407
  }
406
408
  if (res.statusCode && res.statusCode >= 400) {
407
- reject(
408
- new Error(`Failed to upload session report: ${res.statusCode} ${res.statusMessage}`)
409
- );
409
+ let body = "";
410
+ res.on("data", (chunk) => {
411
+ body += chunk.toString();
412
+ });
413
+ res.on("error", (readErr) => {
414
+ reject(
415
+ new Error(
416
+ `Failed to upload session report: ${res.statusCode} ${res.statusMessage} (body read error: ${readErr.message})`
417
+ )
418
+ );
419
+ });
420
+ res.on("end", () => {
421
+ reject(
422
+ new Error(
423
+ `Failed to upload session report: ${res.statusCode} ${res.statusMessage} - ${body}`
424
+ )
425
+ );
426
+ });
410
427
  return;
411
428
  }
412
429
  res.resume();
430
+ res.on("error", (readErr) => reject(new Error(`Response read error: ${readErr.message}`)));
413
431
  res.on("end", () => resolve());
414
432
  }
415
433
  );