@livekit/agents 1.0.17 → 1.0.19

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 (216) hide show
  1. package/dist/index.cjs +3 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +2 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/inference/api_protos.d.cts +12 -12
  9. package/dist/inference/api_protos.d.ts +12 -12
  10. package/dist/inference/llm.cjs +35 -13
  11. package/dist/inference/llm.cjs.map +1 -1
  12. package/dist/inference/llm.d.cts +10 -5
  13. package/dist/inference/llm.d.ts +10 -5
  14. package/dist/inference/llm.d.ts.map +1 -1
  15. package/dist/inference/llm.js +35 -13
  16. package/dist/inference/llm.js.map +1 -1
  17. package/dist/inference/tts.cjs +1 -1
  18. package/dist/inference/tts.cjs.map +1 -1
  19. package/dist/inference/tts.js +1 -1
  20. package/dist/inference/tts.js.map +1 -1
  21. package/dist/ipc/job_proc_lazy_main.cjs +6 -2
  22. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  23. package/dist/ipc/job_proc_lazy_main.js +6 -2
  24. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  25. package/dist/job.cjs +31 -0
  26. package/dist/job.cjs.map +1 -1
  27. package/dist/job.d.cts +6 -0
  28. package/dist/job.d.ts +6 -0
  29. package/dist/job.d.ts.map +1 -1
  30. package/dist/job.js +31 -0
  31. package/dist/job.js.map +1 -1
  32. package/dist/llm/chat_context.cjs +33 -0
  33. package/dist/llm/chat_context.cjs.map +1 -1
  34. package/dist/llm/chat_context.d.cts +22 -2
  35. package/dist/llm/chat_context.d.ts +22 -2
  36. package/dist/llm/chat_context.d.ts.map +1 -1
  37. package/dist/llm/chat_context.js +32 -0
  38. package/dist/llm/chat_context.js.map +1 -1
  39. package/dist/llm/index.cjs +2 -0
  40. package/dist/llm/index.cjs.map +1 -1
  41. package/dist/llm/index.d.cts +1 -1
  42. package/dist/llm/index.d.ts +1 -1
  43. package/dist/llm/index.d.ts.map +1 -1
  44. package/dist/llm/index.js +2 -0
  45. package/dist/llm/index.js.map +1 -1
  46. package/dist/llm/llm.cjs.map +1 -1
  47. package/dist/llm/llm.d.cts +1 -1
  48. package/dist/llm/llm.d.ts +1 -1
  49. package/dist/llm/llm.d.ts.map +1 -1
  50. package/dist/llm/llm.js.map +1 -1
  51. package/dist/llm/provider_format/google.cjs.map +1 -1
  52. package/dist/llm/provider_format/google.d.cts +1 -1
  53. package/dist/llm/provider_format/google.d.ts +1 -1
  54. package/dist/llm/provider_format/google.d.ts.map +1 -1
  55. package/dist/llm/provider_format/google.js.map +1 -1
  56. package/dist/llm/provider_format/google.test.cjs +48 -0
  57. package/dist/llm/provider_format/google.test.cjs.map +1 -1
  58. package/dist/llm/provider_format/google.test.js +54 -1
  59. package/dist/llm/provider_format/google.test.js.map +1 -1
  60. package/dist/llm/provider_format/index.d.cts +1 -1
  61. package/dist/llm/provider_format/index.d.ts +1 -1
  62. package/dist/llm/provider_format/index.d.ts.map +1 -1
  63. package/dist/llm/provider_format/openai.cjs +1 -2
  64. package/dist/llm/provider_format/openai.cjs.map +1 -1
  65. package/dist/llm/provider_format/openai.js +1 -2
  66. package/dist/llm/provider_format/openai.js.map +1 -1
  67. package/dist/llm/provider_format/openai.test.cjs +32 -0
  68. package/dist/llm/provider_format/openai.test.cjs.map +1 -1
  69. package/dist/llm/provider_format/openai.test.js +38 -1
  70. package/dist/llm/provider_format/openai.test.js.map +1 -1
  71. package/dist/llm/realtime.cjs.map +1 -1
  72. package/dist/llm/realtime.d.cts +4 -0
  73. package/dist/llm/realtime.d.ts +4 -0
  74. package/dist/llm/realtime.d.ts.map +1 -1
  75. package/dist/llm/realtime.js.map +1 -1
  76. package/dist/llm/utils.cjs +2 -2
  77. package/dist/llm/utils.cjs.map +1 -1
  78. package/dist/llm/utils.d.cts +1 -1
  79. package/dist/llm/utils.d.ts +1 -1
  80. package/dist/llm/utils.d.ts.map +1 -1
  81. package/dist/llm/utils.js +2 -2
  82. package/dist/llm/utils.js.map +1 -1
  83. package/dist/llm/zod-utils.cjs +6 -3
  84. package/dist/llm/zod-utils.cjs.map +1 -1
  85. package/dist/llm/zod-utils.d.cts +1 -1
  86. package/dist/llm/zod-utils.d.ts +1 -1
  87. package/dist/llm/zod-utils.d.ts.map +1 -1
  88. package/dist/llm/zod-utils.js +6 -3
  89. package/dist/llm/zod-utils.js.map +1 -1
  90. package/dist/llm/zod-utils.test.cjs +83 -0
  91. package/dist/llm/zod-utils.test.cjs.map +1 -1
  92. package/dist/llm/zod-utils.test.js +83 -0
  93. package/dist/llm/zod-utils.test.js.map +1 -1
  94. package/dist/log.cjs.map +1 -1
  95. package/dist/log.d.ts.map +1 -1
  96. package/dist/log.js.map +1 -1
  97. package/dist/telemetry/index.cjs +51 -0
  98. package/dist/telemetry/index.cjs.map +1 -0
  99. package/dist/telemetry/index.d.cts +4 -0
  100. package/dist/telemetry/index.d.ts +4 -0
  101. package/dist/telemetry/index.d.ts.map +1 -0
  102. package/dist/telemetry/index.js +12 -0
  103. package/dist/telemetry/index.js.map +1 -0
  104. package/dist/telemetry/trace_types.cjs +191 -0
  105. package/dist/telemetry/trace_types.cjs.map +1 -0
  106. package/dist/telemetry/trace_types.d.cts +56 -0
  107. package/dist/telemetry/trace_types.d.ts +56 -0
  108. package/dist/telemetry/trace_types.d.ts.map +1 -0
  109. package/dist/telemetry/trace_types.js +113 -0
  110. package/dist/telemetry/trace_types.js.map +1 -0
  111. package/dist/telemetry/traces.cjs +196 -0
  112. package/dist/telemetry/traces.cjs.map +1 -0
  113. package/dist/telemetry/traces.d.cts +97 -0
  114. package/dist/telemetry/traces.d.ts +97 -0
  115. package/dist/telemetry/traces.d.ts.map +1 -0
  116. package/dist/telemetry/traces.js +173 -0
  117. package/dist/telemetry/traces.js.map +1 -0
  118. package/dist/telemetry/utils.cjs +86 -0
  119. package/dist/telemetry/utils.cjs.map +1 -0
  120. package/dist/telemetry/utils.d.cts +5 -0
  121. package/dist/telemetry/utils.d.ts +5 -0
  122. package/dist/telemetry/utils.d.ts.map +1 -0
  123. package/dist/telemetry/utils.js +51 -0
  124. package/dist/telemetry/utils.js.map +1 -0
  125. package/dist/tts/tts.cjs.map +1 -1
  126. package/dist/tts/tts.d.ts.map +1 -1
  127. package/dist/tts/tts.js.map +1 -1
  128. package/dist/utils.cjs.map +1 -1
  129. package/dist/utils.d.cts +7 -0
  130. package/dist/utils.d.ts +7 -0
  131. package/dist/utils.d.ts.map +1 -1
  132. package/dist/utils.js.map +1 -1
  133. package/dist/voice/agent.cjs +15 -0
  134. package/dist/voice/agent.cjs.map +1 -1
  135. package/dist/voice/agent.d.cts +4 -1
  136. package/dist/voice/agent.d.ts +4 -1
  137. package/dist/voice/agent.d.ts.map +1 -1
  138. package/dist/voice/agent.js +15 -0
  139. package/dist/voice/agent.js.map +1 -1
  140. package/dist/voice/agent_activity.cjs +71 -20
  141. package/dist/voice/agent_activity.cjs.map +1 -1
  142. package/dist/voice/agent_activity.d.ts.map +1 -1
  143. package/dist/voice/agent_activity.js +71 -20
  144. package/dist/voice/agent_activity.js.map +1 -1
  145. package/dist/voice/agent_session.cjs +69 -2
  146. package/dist/voice/agent_session.cjs.map +1 -1
  147. package/dist/voice/agent_session.d.cts +11 -2
  148. package/dist/voice/agent_session.d.ts +11 -2
  149. package/dist/voice/agent_session.d.ts.map +1 -1
  150. package/dist/voice/agent_session.js +70 -3
  151. package/dist/voice/agent_session.js.map +1 -1
  152. package/dist/voice/audio_recognition.cjs.map +1 -1
  153. package/dist/voice/audio_recognition.d.ts.map +1 -1
  154. package/dist/voice/audio_recognition.js.map +1 -1
  155. package/dist/voice/generation.cjs.map +1 -1
  156. package/dist/voice/generation.d.ts.map +1 -1
  157. package/dist/voice/generation.js.map +1 -1
  158. package/dist/voice/index.cjs +2 -0
  159. package/dist/voice/index.cjs.map +1 -1
  160. package/dist/voice/index.d.cts +1 -0
  161. package/dist/voice/index.d.ts +1 -0
  162. package/dist/voice/index.d.ts.map +1 -1
  163. package/dist/voice/index.js +1 -0
  164. package/dist/voice/index.js.map +1 -1
  165. package/dist/voice/interruption_detection.test.cjs +114 -0
  166. package/dist/voice/interruption_detection.test.cjs.map +1 -0
  167. package/dist/voice/interruption_detection.test.js +113 -0
  168. package/dist/voice/interruption_detection.test.js.map +1 -0
  169. package/dist/voice/report.cjs +69 -0
  170. package/dist/voice/report.cjs.map +1 -0
  171. package/dist/voice/report.d.cts +26 -0
  172. package/dist/voice/report.d.ts +26 -0
  173. package/dist/voice/report.d.ts.map +1 -0
  174. package/dist/voice/report.js +44 -0
  175. package/dist/voice/report.js.map +1 -0
  176. package/dist/voice/room_io/room_io.cjs +3 -0
  177. package/dist/voice/room_io/room_io.cjs.map +1 -1
  178. package/dist/voice/room_io/room_io.d.cts +1 -0
  179. package/dist/voice/room_io/room_io.d.ts +1 -0
  180. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  181. package/dist/voice/room_io/room_io.js +3 -0
  182. package/dist/voice/room_io/room_io.js.map +1 -1
  183. package/package.json +12 -5
  184. package/src/index.ts +2 -1
  185. package/src/inference/llm.ts +53 -21
  186. package/src/inference/tts.ts +1 -1
  187. package/src/ipc/job_proc_lazy_main.ts +10 -2
  188. package/src/job.ts +48 -0
  189. package/src/llm/__snapshots__/zod-utils.test.ts.snap +218 -0
  190. package/src/llm/chat_context.ts +53 -1
  191. package/src/llm/index.ts +1 -0
  192. package/src/llm/llm.ts +3 -1
  193. package/src/llm/provider_format/google.test.ts +72 -1
  194. package/src/llm/provider_format/google.ts +4 -4
  195. package/src/llm/provider_format/openai.test.ts +55 -1
  196. package/src/llm/provider_format/openai.ts +3 -2
  197. package/src/llm/realtime.ts +8 -1
  198. package/src/llm/utils.ts +7 -2
  199. package/src/llm/zod-utils.test.ts +101 -0
  200. package/src/llm/zod-utils.ts +12 -3
  201. package/src/log.ts +1 -0
  202. package/src/telemetry/index.ts +10 -0
  203. package/src/telemetry/trace_types.ts +88 -0
  204. package/src/telemetry/traces.ts +266 -0
  205. package/src/telemetry/utils.ts +61 -0
  206. package/src/tts/tts.ts +4 -0
  207. package/src/utils.ts +17 -0
  208. package/src/voice/agent.ts +22 -0
  209. package/src/voice/agent_activity.ts +102 -24
  210. package/src/voice/agent_session.ts +98 -1
  211. package/src/voice/audio_recognition.ts +2 -0
  212. package/src/voice/generation.ts +3 -0
  213. package/src/voice/index.ts +1 -0
  214. package/src/voice/interruption_detection.test.ts +151 -0
  215. package/src/voice/report.ts +77 -0
  216. package/src/voice/room_io/room_io.ts +4 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/llm/provider_format/openai.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { initializeLogger } from '../../log.js';\nimport { ChatContext, FunctionCall, FunctionCallOutput } from '../chat_context.js';\nimport { serializeImage } from '../utils.js';\nimport { toChatCtx } from './openai.js';\n\n// Mock the serializeImage function\nvi.mock('../utils.js', () => ({\n serializeImage: vi.fn(),\n}));\n\ndescribe('toChatCtx', () => {\n const serializeImageMock = vi.mocked(serializeImage);\n\n // initialize logger at start of test\n initializeLogger({ level: 'silent', pretty: false });\n\n beforeEach(async () => {\n vi.clearAllMocks();\n });\n\n it('should convert simple text messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello' });\n ctx.addMessage({ role: 'assistant', content: 'Hi there!' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'user', content: 'Hello' });\n expect(result[1]).toEqual({ role: 'assistant', content: 'Hi there!' });\n });\n\n it('should handle system messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'system', content: 'You are a helpful assistant' });\n ctx.addMessage({ role: 'user', content: 'Hello' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'system', content: 'You are a helpful assistant' });\n expect(result[1]).toEqual({ role: 'user', content: 'Hello' });\n });\n\n it('should handle multi-line text content', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: ['Line 1', 'Line 2', 'Line 3'] });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toHaveLength(1);\n expect(result[0]).toEqual({ role: 'user', content: 'Line 1\\nLine 2\\nLine 3' });\n });\n\n it('should handle messages with external URL images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Check out this image:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'high',\n },\n },\n { type: 'text', text: 'Check out this image:' },\n ],\n },\n ]);\n });\n\n it('should handle messages with base64 images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n mimeType: 'image/png',\n base64Data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'assistant',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n inferenceDetail: 'auto',\n _cache: {},\n },\n 'Here is the image you requested',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n detail: 'auto',\n },\n },\n { type: 'text', text: 'Here is the image you requested' },\n ],\n },\n ]);\n });\n\n it('should handle VideoFrame images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'low',\n mimeType: 'image/jpeg',\n base64Data: '/9j/4AAQSkZJRg==',\n });\n\n const frameData = new Uint8Array(4 * 4 * 4); // 4x4 RGBA\n const videoFrame = new VideoFrame(frameData, 4, 4, VideoBufferType.RGBA);\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'frame1',\n type: 'image_content',\n image: videoFrame,\n inferenceDetail: 'low',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/jpeg;base64,/9j/4AAQSkZJRg==',\n detail: 'low',\n },\n },\n ],\n },\n ]);\n });\n\n it('should cache serialized images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n mimeType: 'image/png',\n base64Data: 'cached-data',\n });\n\n const imageContent = {\n id: 'img1',\n type: 'image_content' as const,\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high' as const,\n _cache: {},\n };\n\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: [imageContent] });\n\n // Call twice to test caching\n await toChatCtx(ctx);\n await toChatCtx(ctx);\n\n // serializeImage should only be called once due to caching\n expect(serializeImageMock).toHaveBeenCalledTimes(1);\n expect(imageContent._cache).toHaveProperty('serialized_image');\n });\n\n it('should handle tool calls and outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add an assistant message with tool calls\n const msg = ctx.addMessage({ role: 'assistant', content: 'Let me help you with that.' });\n const toolCall = FunctionCall.create({\n id: msg.id + '/tool_1',\n callId: 'call_123',\n name: 'get_weather',\n args: '{\"location\": \"San Francisco\"}',\n });\n const toolOutput = FunctionCallOutput.create({\n callId: 'call_123',\n output: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: 'Let me help you with that.',\n tool_calls: [\n {\n type: 'function',\n id: 'call_123',\n function: {\n name: 'get_weather',\n arguments: '{\"location\": \"San Francisco\"}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_123',\n content: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n },\n ]);\n });\n\n it('should handle multiple tool calls in one message', async () => {\n const ctx = ChatContext.empty();\n\n const msg = ctx.addMessage({ role: 'assistant', content: \"I'll check both locations.\" });\n const toolCall1 = new FunctionCall({\n id: msg.id + '/tool_1',\n callId: 'call_1',\n name: 'get_weather',\n args: '{\"location\": \"NYC\"}',\n });\n const toolCall2 = new FunctionCall({\n id: msg.id + '/tool_2',\n callId: 'call_2',\n name: 'get_weather',\n args: '{\"location\": \"LA\"}',\n });\n const toolOutput1 = new FunctionCallOutput({\n callId: 'call_1',\n output: '{\"temperature\": 65}',\n isError: false,\n });\n const toolOutput2 = new FunctionCallOutput({\n callId: 'call_2',\n output: '{\"temperature\": 78}',\n isError: false,\n });\n\n ctx.insert([toolCall1, toolCall2, toolOutput1, toolOutput2]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: \"I'll check both locations.\",\n tool_calls: [\n {\n type: 'function',\n id: 'call_1',\n function: { name: 'get_weather', arguments: '{\"location\": \"NYC\"}' },\n },\n {\n type: 'function',\n id: 'call_2',\n function: { name: 'get_weather', arguments: '{\"location\": \"LA\"}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_1',\n content: '{\"temperature\": 65}',\n },\n {\n role: 'tool',\n tool_call_id: 'call_2',\n content: '{\"temperature\": 78}',\n },\n ]);\n });\n\n it('should handle tool calls without accompanying message', async () => {\n const ctx = ChatContext.empty();\n\n const toolCall = new FunctionCall({\n id: 'func_123',\n callId: 'call_456',\n name: 'calculate',\n args: '{\"a\": 5, \"b\": 3}',\n });\n const toolOutput = new FunctionCallOutput({\n callId: 'call_456',\n output: '{\"result\": 8}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n type: 'function',\n id: 'call_456',\n function: { name: 'calculate', arguments: '{\"a\": 5, \"b\": 3}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_456',\n content: '{\"result\": 8}',\n },\n ]);\n });\n\n it('should skip empty groups', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello', createdAt: 1000 });\n\n // Create an isolated tool output without corresponding call (will be filtered)\n const orphanOutput = new FunctionCallOutput({\n callId: 'orphan_call',\n output: 'This should be ignored',\n isError: false,\n createdAt: 2000,\n });\n ctx.insert(orphanOutput);\n\n ctx.addMessage({ role: 'assistant', content: 'Hi!', createdAt: 3000 });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, orphan output should be filtered out\n expect(result).toHaveLength(2);\n expect(result).toContainEqual({ role: 'user', content: 'Hello' });\n expect(result).toContainEqual({ role: 'assistant', content: 'Hi!' });\n });\n\n it('should handle mixed content with text and multiple images', async () => {\n serializeImageMock\n .mockResolvedValueOnce({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image1.jpg',\n })\n .mockResolvedValueOnce({\n inferenceDetail: 'low',\n mimeType: 'image/png',\n base64Data: 'base64data',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Here are two images:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image1.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n 'And the second one:',\n {\n id: 'img2',\n type: 'image_content',\n image: 'data:image/png;base64,base64data',\n inferenceDetail: 'low',\n _cache: {},\n },\n 'What do you think?',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image1.jpg',\n detail: 'high',\n },\n },\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,base64data',\n detail: 'low',\n },\n },\n {\n type: 'text',\n text: 'Here are two images:\\nAnd the second one:\\nWhat do you think?',\n },\n ],\n },\n ]);\n });\n\n it('should handle content with only images and no text', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'auto',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'auto',\n },\n },\n ],\n },\n ]);\n });\n\n it('should throw error for unsupported content type', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n type: 'audio_content',\n frame: [],\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Unsupported content type: audio_content');\n });\n\n it('should throw error when serialized image has no data', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n // No base64Data or externalUrl\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'invalid-image',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Serialized image has no data bytes');\n });\n\n it('should filter out standalone function calls without outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add standalone function call without output\n const funcCall = new FunctionCall({\n id: 'func_standalone',\n callId: 'call_999',\n name: 'standalone_function',\n args: '{}',\n });\n\n ctx.insert(funcCall);\n\n const result = await toChatCtx(ctx);\n\n // Standalone function calls without outputs are filtered out by groupToolCalls\n expect(result).toEqual([]);\n });\n\n it('should handle function call output correctly', async () => {\n const ctx = ChatContext.empty();\n\n // First add a function call\n const funcCall = new FunctionCall({\n id: 'func_1',\n callId: 'call_output_test',\n name: 'test_function',\n args: '{}',\n });\n\n // Then add its output\n const funcOutput = new FunctionCallOutput({\n callId: 'call_output_test',\n output: 'Function executed successfully',\n isError: false,\n });\n\n ctx.insert([funcCall, funcOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n id: 'call_output_test',\n type: 'function',\n function: {\n name: 'test_function',\n arguments: '{}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_output_test',\n content: 'Function executed successfully',\n },\n ]);\n });\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/llm/provider_format/openai.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { initializeLogger } from '../../log.js';\nimport {\n AgentHandoffItem,\n ChatContext,\n FunctionCall,\n FunctionCallOutput,\n} from '../chat_context.js';\nimport { serializeImage } from '../utils.js';\nimport { toChatCtx } from './openai.js';\n\n// Mock the serializeImage function\nvi.mock('../utils.js', () => ({\n serializeImage: vi.fn(),\n}));\n\ndescribe('toChatCtx', () => {\n const serializeImageMock = vi.mocked(serializeImage);\n\n // initialize logger at start of test\n initializeLogger({ level: 'silent', pretty: false });\n\n beforeEach(async () => {\n vi.clearAllMocks();\n });\n\n it('should convert simple text messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello' });\n ctx.addMessage({ role: 'assistant', content: 'Hi there!' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'user', content: 'Hello' });\n expect(result[1]).toEqual({ role: 'assistant', content: 'Hi there!' });\n });\n\n it('should handle system messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'system', content: 'You are a helpful assistant' });\n ctx.addMessage({ role: 'user', content: 'Hello' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'system', content: 'You are a helpful assistant' });\n expect(result[1]).toEqual({ role: 'user', content: 'Hello' });\n });\n\n it('should handle multi-line text content', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: ['Line 1', 'Line 2', 'Line 3'] });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toHaveLength(1);\n expect(result[0]).toEqual({ role: 'user', content: 'Line 1\\nLine 2\\nLine 3' });\n });\n\n it('should handle messages with external URL images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Check out this image:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'high',\n },\n },\n { type: 'text', text: 'Check out this image:' },\n ],\n },\n ]);\n });\n\n it('should handle messages with base64 images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n mimeType: 'image/png',\n base64Data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'assistant',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n inferenceDetail: 'auto',\n _cache: {},\n },\n 'Here is the image you requested',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n detail: 'auto',\n },\n },\n { type: 'text', text: 'Here is the image you requested' },\n ],\n },\n ]);\n });\n\n it('should handle VideoFrame images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'low',\n mimeType: 'image/jpeg',\n base64Data: '/9j/4AAQSkZJRg==',\n });\n\n const frameData = new Uint8Array(4 * 4 * 4); // 4x4 RGBA\n const videoFrame = new VideoFrame(frameData, 4, 4, VideoBufferType.RGBA);\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'frame1',\n type: 'image_content',\n image: videoFrame,\n inferenceDetail: 'low',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/jpeg;base64,/9j/4AAQSkZJRg==',\n detail: 'low',\n },\n },\n ],\n },\n ]);\n });\n\n it('should cache serialized images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n mimeType: 'image/png',\n base64Data: 'cached-data',\n });\n\n const imageContent = {\n id: 'img1',\n type: 'image_content' as const,\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high' as const,\n _cache: {},\n };\n\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: [imageContent] });\n\n // Call twice to test caching\n await toChatCtx(ctx);\n await toChatCtx(ctx);\n\n // serializeImage should only be called once due to caching\n expect(serializeImageMock).toHaveBeenCalledTimes(1);\n expect(imageContent._cache).toHaveProperty('serialized_image');\n });\n\n it('should handle tool calls and outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add an assistant message with tool calls\n const msg = ctx.addMessage({ role: 'assistant', content: 'Let me help you with that.' });\n const toolCall = FunctionCall.create({\n id: msg.id + '/tool_1',\n callId: 'call_123',\n name: 'get_weather',\n args: '{\"location\": \"San Francisco\"}',\n });\n const toolOutput = FunctionCallOutput.create({\n callId: 'call_123',\n output: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: 'Let me help you with that.',\n tool_calls: [\n {\n type: 'function',\n id: 'call_123',\n function: {\n name: 'get_weather',\n arguments: '{\"location\": \"San Francisco\"}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_123',\n content: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n },\n ]);\n });\n\n it('should handle multiple tool calls in one message', async () => {\n const ctx = ChatContext.empty();\n\n const msg = ctx.addMessage({ role: 'assistant', content: \"I'll check both locations.\" });\n const toolCall1 = new FunctionCall({\n id: msg.id + '/tool_1',\n callId: 'call_1',\n name: 'get_weather',\n args: '{\"location\": \"NYC\"}',\n });\n const toolCall2 = new FunctionCall({\n id: msg.id + '/tool_2',\n callId: 'call_2',\n name: 'get_weather',\n args: '{\"location\": \"LA\"}',\n });\n const toolOutput1 = new FunctionCallOutput({\n callId: 'call_1',\n output: '{\"temperature\": 65}',\n isError: false,\n });\n const toolOutput2 = new FunctionCallOutput({\n callId: 'call_2',\n output: '{\"temperature\": 78}',\n isError: false,\n });\n\n ctx.insert([toolCall1, toolCall2, toolOutput1, toolOutput2]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: \"I'll check both locations.\",\n tool_calls: [\n {\n type: 'function',\n id: 'call_1',\n function: { name: 'get_weather', arguments: '{\"location\": \"NYC\"}' },\n },\n {\n type: 'function',\n id: 'call_2',\n function: { name: 'get_weather', arguments: '{\"location\": \"LA\"}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_1',\n content: '{\"temperature\": 65}',\n },\n {\n role: 'tool',\n tool_call_id: 'call_2',\n content: '{\"temperature\": 78}',\n },\n ]);\n });\n\n it('should handle tool calls without accompanying message', async () => {\n const ctx = ChatContext.empty();\n\n const toolCall = new FunctionCall({\n id: 'func_123',\n callId: 'call_456',\n name: 'calculate',\n args: '{\"a\": 5, \"b\": 3}',\n });\n const toolOutput = new FunctionCallOutput({\n callId: 'call_456',\n output: '{\"result\": 8}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n type: 'function',\n id: 'call_456',\n function: { name: 'calculate', arguments: '{\"a\": 5, \"b\": 3}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_456',\n content: '{\"result\": 8}',\n },\n ]);\n });\n\n it('should skip empty groups', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello', createdAt: 1000 });\n\n // Create an isolated tool output without corresponding call (will be filtered)\n const orphanOutput = new FunctionCallOutput({\n callId: 'orphan_call',\n output: 'This should be ignored',\n isError: false,\n createdAt: 2000,\n });\n ctx.insert(orphanOutput);\n\n ctx.addMessage({ role: 'assistant', content: 'Hi!', createdAt: 3000 });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, orphan output should be filtered out\n expect(result).toHaveLength(2);\n expect(result).toContainEqual({ role: 'user', content: 'Hello' });\n expect(result).toContainEqual({ role: 'assistant', content: 'Hi!' });\n });\n\n it('should handle mixed content with text and multiple images', async () => {\n serializeImageMock\n .mockResolvedValueOnce({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image1.jpg',\n })\n .mockResolvedValueOnce({\n inferenceDetail: 'low',\n mimeType: 'image/png',\n base64Data: 'base64data',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Here are two images:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image1.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n 'And the second one:',\n {\n id: 'img2',\n type: 'image_content',\n image: 'data:image/png;base64,base64data',\n inferenceDetail: 'low',\n _cache: {},\n },\n 'What do you think?',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image1.jpg',\n detail: 'high',\n },\n },\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,base64data',\n detail: 'low',\n },\n },\n {\n type: 'text',\n text: 'Here are two images:\\nAnd the second one:\\nWhat do you think?',\n },\n ],\n },\n ]);\n });\n\n it('should handle content with only images and no text', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'auto',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'auto',\n },\n },\n ],\n },\n ]);\n });\n\n it('should throw error for unsupported content type', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n type: 'audio_content',\n frame: [],\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Unsupported content type: audio_content');\n });\n\n it('should throw error when serialized image has no data', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n // No base64Data or externalUrl\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'invalid-image',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Serialized image has no data bytes');\n });\n\n it('should filter out standalone function calls without outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add standalone function call without output\n const funcCall = new FunctionCall({\n id: 'func_standalone',\n callId: 'call_999',\n name: 'standalone_function',\n args: '{}',\n });\n\n ctx.insert(funcCall);\n\n const result = await toChatCtx(ctx);\n\n // Standalone function calls without outputs are filtered out by groupToolCalls\n expect(result).toEqual([]);\n });\n\n it('should handle function call output correctly', async () => {\n const ctx = ChatContext.empty();\n\n // First add a function call\n const funcCall = new FunctionCall({\n id: 'func_1',\n callId: 'call_output_test',\n name: 'test_function',\n args: '{}',\n });\n\n // Then add its output\n const funcOutput = new FunctionCallOutput({\n callId: 'call_output_test',\n output: 'Function executed successfully',\n isError: false,\n });\n\n ctx.insert([funcCall, funcOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n id: 'call_output_test',\n type: 'function',\n function: {\n name: 'test_function',\n arguments: '{}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_output_test',\n content: 'Function executed successfully',\n },\n ]);\n });\n\n it('should filter out agent handoff items', async () => {\n const ctx = ChatContext.empty();\n\n ctx.addMessage({ role: 'user', content: 'Hello' });\n\n // Insert an agent handoff item\n const handoff = new AgentHandoffItem({\n oldAgentId: 'agent_1',\n newAgentId: 'agent_2',\n });\n ctx.insert(handoff);\n\n ctx.addMessage({ role: 'assistant', content: 'Hi there!' });\n\n const result = await toChatCtx(ctx);\n\n // Agent handoff should be filtered out, only messages should remain\n expect(result).toEqual([\n { role: 'user', content: 'Hello' },\n { role: 'assistant', content: 'Hi there!' },\n ]);\n });\n\n it('should handle multiple agent handoffs without errors', async () => {\n const ctx = ChatContext.empty();\n\n ctx.addMessage({ role: 'user', content: 'Start' });\n\n // Multiple handoffs\n ctx.insert(new AgentHandoffItem({ oldAgentId: undefined, newAgentId: 'agent_1' }));\n ctx.addMessage({ role: 'assistant', content: 'Response from agent 1' });\n\n ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_1', newAgentId: 'agent_2' }));\n ctx.addMessage({ role: 'assistant', content: 'Response from agent 2' });\n\n ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_2', newAgentId: 'agent_3' }));\n ctx.addMessage({ role: 'assistant', content: 'Response from agent 3' });\n\n const result = await toChatCtx(ctx);\n\n // All handoffs should be filtered out\n expect(result).toEqual([\n { role: 'user', content: 'Start' },\n { role: 'assistant', content: 'Response from agent 1' },\n { role: 'assistant', content: 'Response from agent 2' },\n { role: 'assistant', content: 'Response from agent 3' },\n ]);\n });\n});\n"],"mappings":"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","names":[]}
@@ -1,7 +1,12 @@
1
1
  import { VideoBufferType, VideoFrame } from "@livekit/rtc-node";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { initializeLogger } from "../../log.js";
4
- import { ChatContext, FunctionCall, FunctionCallOutput } from "../chat_context.js";
4
+ import {
5
+ AgentHandoffItem,
6
+ ChatContext,
7
+ FunctionCall,
8
+ FunctionCallOutput
9
+ } from "../chat_context.js";
5
10
  import { serializeImage } from "../utils.js";
6
11
  import { toChatCtx } from "./openai.js";
7
12
  vi.mock("../utils.js", () => ({
@@ -485,5 +490,37 @@ describe("toChatCtx", () => {
485
490
  }
486
491
  ]);
487
492
  });
493
+ it("should filter out agent handoff items", async () => {
494
+ const ctx = ChatContext.empty();
495
+ ctx.addMessage({ role: "user", content: "Hello" });
496
+ const handoff = new AgentHandoffItem({
497
+ oldAgentId: "agent_1",
498
+ newAgentId: "agent_2"
499
+ });
500
+ ctx.insert(handoff);
501
+ ctx.addMessage({ role: "assistant", content: "Hi there!" });
502
+ const result = await toChatCtx(ctx);
503
+ expect(result).toEqual([
504
+ { role: "user", content: "Hello" },
505
+ { role: "assistant", content: "Hi there!" }
506
+ ]);
507
+ });
508
+ it("should handle multiple agent handoffs without errors", async () => {
509
+ const ctx = ChatContext.empty();
510
+ ctx.addMessage({ role: "user", content: "Start" });
511
+ ctx.insert(new AgentHandoffItem({ oldAgentId: void 0, newAgentId: "agent_1" }));
512
+ ctx.addMessage({ role: "assistant", content: "Response from agent 1" });
513
+ ctx.insert(new AgentHandoffItem({ oldAgentId: "agent_1", newAgentId: "agent_2" }));
514
+ ctx.addMessage({ role: "assistant", content: "Response from agent 2" });
515
+ ctx.insert(new AgentHandoffItem({ oldAgentId: "agent_2", newAgentId: "agent_3" }));
516
+ ctx.addMessage({ role: "assistant", content: "Response from agent 3" });
517
+ const result = await toChatCtx(ctx);
518
+ expect(result).toEqual([
519
+ { role: "user", content: "Start" },
520
+ { role: "assistant", content: "Response from agent 1" },
521
+ { role: "assistant", content: "Response from agent 2" },
522
+ { role: "assistant", content: "Response from agent 3" }
523
+ ]);
524
+ });
488
525
  });
489
526
  //# sourceMappingURL=openai.test.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/llm/provider_format/openai.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { initializeLogger } from '../../log.js';\nimport { ChatContext, FunctionCall, FunctionCallOutput } from '../chat_context.js';\nimport { serializeImage } from '../utils.js';\nimport { toChatCtx } from './openai.js';\n\n// Mock the serializeImage function\nvi.mock('../utils.js', () => ({\n serializeImage: vi.fn(),\n}));\n\ndescribe('toChatCtx', () => {\n const serializeImageMock = vi.mocked(serializeImage);\n\n // initialize logger at start of test\n initializeLogger({ level: 'silent', pretty: false });\n\n beforeEach(async () => {\n vi.clearAllMocks();\n });\n\n it('should convert simple text messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello' });\n ctx.addMessage({ role: 'assistant', content: 'Hi there!' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'user', content: 'Hello' });\n expect(result[1]).toEqual({ role: 'assistant', content: 'Hi there!' });\n });\n\n it('should handle system messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'system', content: 'You are a helpful assistant' });\n ctx.addMessage({ role: 'user', content: 'Hello' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'system', content: 'You are a helpful assistant' });\n expect(result[1]).toEqual({ role: 'user', content: 'Hello' });\n });\n\n it('should handle multi-line text content', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: ['Line 1', 'Line 2', 'Line 3'] });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toHaveLength(1);\n expect(result[0]).toEqual({ role: 'user', content: 'Line 1\\nLine 2\\nLine 3' });\n });\n\n it('should handle messages with external URL images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Check out this image:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'high',\n },\n },\n { type: 'text', text: 'Check out this image:' },\n ],\n },\n ]);\n });\n\n it('should handle messages with base64 images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n mimeType: 'image/png',\n base64Data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'assistant',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n inferenceDetail: 'auto',\n _cache: {},\n },\n 'Here is the image you requested',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n detail: 'auto',\n },\n },\n { type: 'text', text: 'Here is the image you requested' },\n ],\n },\n ]);\n });\n\n it('should handle VideoFrame images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'low',\n mimeType: 'image/jpeg',\n base64Data: '/9j/4AAQSkZJRg==',\n });\n\n const frameData = new Uint8Array(4 * 4 * 4); // 4x4 RGBA\n const videoFrame = new VideoFrame(frameData, 4, 4, VideoBufferType.RGBA);\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'frame1',\n type: 'image_content',\n image: videoFrame,\n inferenceDetail: 'low',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/jpeg;base64,/9j/4AAQSkZJRg==',\n detail: 'low',\n },\n },\n ],\n },\n ]);\n });\n\n it('should cache serialized images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n mimeType: 'image/png',\n base64Data: 'cached-data',\n });\n\n const imageContent = {\n id: 'img1',\n type: 'image_content' as const,\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high' as const,\n _cache: {},\n };\n\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: [imageContent] });\n\n // Call twice to test caching\n await toChatCtx(ctx);\n await toChatCtx(ctx);\n\n // serializeImage should only be called once due to caching\n expect(serializeImageMock).toHaveBeenCalledTimes(1);\n expect(imageContent._cache).toHaveProperty('serialized_image');\n });\n\n it('should handle tool calls and outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add an assistant message with tool calls\n const msg = ctx.addMessage({ role: 'assistant', content: 'Let me help you with that.' });\n const toolCall = FunctionCall.create({\n id: msg.id + '/tool_1',\n callId: 'call_123',\n name: 'get_weather',\n args: '{\"location\": \"San Francisco\"}',\n });\n const toolOutput = FunctionCallOutput.create({\n callId: 'call_123',\n output: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: 'Let me help you with that.',\n tool_calls: [\n {\n type: 'function',\n id: 'call_123',\n function: {\n name: 'get_weather',\n arguments: '{\"location\": \"San Francisco\"}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_123',\n content: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n },\n ]);\n });\n\n it('should handle multiple tool calls in one message', async () => {\n const ctx = ChatContext.empty();\n\n const msg = ctx.addMessage({ role: 'assistant', content: \"I'll check both locations.\" });\n const toolCall1 = new FunctionCall({\n id: msg.id + '/tool_1',\n callId: 'call_1',\n name: 'get_weather',\n args: '{\"location\": \"NYC\"}',\n });\n const toolCall2 = new FunctionCall({\n id: msg.id + '/tool_2',\n callId: 'call_2',\n name: 'get_weather',\n args: '{\"location\": \"LA\"}',\n });\n const toolOutput1 = new FunctionCallOutput({\n callId: 'call_1',\n output: '{\"temperature\": 65}',\n isError: false,\n });\n const toolOutput2 = new FunctionCallOutput({\n callId: 'call_2',\n output: '{\"temperature\": 78}',\n isError: false,\n });\n\n ctx.insert([toolCall1, toolCall2, toolOutput1, toolOutput2]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: \"I'll check both locations.\",\n tool_calls: [\n {\n type: 'function',\n id: 'call_1',\n function: { name: 'get_weather', arguments: '{\"location\": \"NYC\"}' },\n },\n {\n type: 'function',\n id: 'call_2',\n function: { name: 'get_weather', arguments: '{\"location\": \"LA\"}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_1',\n content: '{\"temperature\": 65}',\n },\n {\n role: 'tool',\n tool_call_id: 'call_2',\n content: '{\"temperature\": 78}',\n },\n ]);\n });\n\n it('should handle tool calls without accompanying message', async () => {\n const ctx = ChatContext.empty();\n\n const toolCall = new FunctionCall({\n id: 'func_123',\n callId: 'call_456',\n name: 'calculate',\n args: '{\"a\": 5, \"b\": 3}',\n });\n const toolOutput = new FunctionCallOutput({\n callId: 'call_456',\n output: '{\"result\": 8}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n type: 'function',\n id: 'call_456',\n function: { name: 'calculate', arguments: '{\"a\": 5, \"b\": 3}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_456',\n content: '{\"result\": 8}',\n },\n ]);\n });\n\n it('should skip empty groups', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello', createdAt: 1000 });\n\n // Create an isolated tool output without corresponding call (will be filtered)\n const orphanOutput = new FunctionCallOutput({\n callId: 'orphan_call',\n output: 'This should be ignored',\n isError: false,\n createdAt: 2000,\n });\n ctx.insert(orphanOutput);\n\n ctx.addMessage({ role: 'assistant', content: 'Hi!', createdAt: 3000 });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, orphan output should be filtered out\n expect(result).toHaveLength(2);\n expect(result).toContainEqual({ role: 'user', content: 'Hello' });\n expect(result).toContainEqual({ role: 'assistant', content: 'Hi!' });\n });\n\n it('should handle mixed content with text and multiple images', async () => {\n serializeImageMock\n .mockResolvedValueOnce({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image1.jpg',\n })\n .mockResolvedValueOnce({\n inferenceDetail: 'low',\n mimeType: 'image/png',\n base64Data: 'base64data',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Here are two images:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image1.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n 'And the second one:',\n {\n id: 'img2',\n type: 'image_content',\n image: 'data:image/png;base64,base64data',\n inferenceDetail: 'low',\n _cache: {},\n },\n 'What do you think?',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image1.jpg',\n detail: 'high',\n },\n },\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,base64data',\n detail: 'low',\n },\n },\n {\n type: 'text',\n text: 'Here are two images:\\nAnd the second one:\\nWhat do you think?',\n },\n ],\n },\n ]);\n });\n\n it('should handle content with only images and no text', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'auto',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'auto',\n },\n },\n ],\n },\n ]);\n });\n\n it('should throw error for unsupported content type', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n type: 'audio_content',\n frame: [],\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Unsupported content type: audio_content');\n });\n\n it('should throw error when serialized image has no data', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n // No base64Data or externalUrl\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'invalid-image',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Serialized image has no data bytes');\n });\n\n it('should filter out standalone function calls without outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add standalone function call without output\n const funcCall = new FunctionCall({\n id: 'func_standalone',\n callId: 'call_999',\n name: 'standalone_function',\n args: '{}',\n });\n\n ctx.insert(funcCall);\n\n const result = await toChatCtx(ctx);\n\n // Standalone function calls without outputs are filtered out by groupToolCalls\n expect(result).toEqual([]);\n });\n\n it('should handle function call output correctly', async () => {\n const ctx = ChatContext.empty();\n\n // First add a function call\n const funcCall = new FunctionCall({\n id: 'func_1',\n callId: 'call_output_test',\n name: 'test_function',\n args: '{}',\n });\n\n // Then add its output\n const funcOutput = new FunctionCallOutput({\n callId: 'call_output_test',\n output: 'Function executed successfully',\n isError: false,\n });\n\n ctx.insert([funcCall, funcOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n id: 'call_output_test',\n type: 'function',\n function: {\n name: 'test_function',\n arguments: '{}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_output_test',\n content: 'Function executed successfully',\n },\n ]);\n });\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/llm/provider_format/openai.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VideoBufferType, VideoFrame } from '@livekit/rtc-node';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport { initializeLogger } from '../../log.js';\nimport {\n AgentHandoffItem,\n ChatContext,\n FunctionCall,\n FunctionCallOutput,\n} from '../chat_context.js';\nimport { serializeImage } from '../utils.js';\nimport { toChatCtx } from './openai.js';\n\n// Mock the serializeImage function\nvi.mock('../utils.js', () => ({\n serializeImage: vi.fn(),\n}));\n\ndescribe('toChatCtx', () => {\n const serializeImageMock = vi.mocked(serializeImage);\n\n // initialize logger at start of test\n initializeLogger({ level: 'silent', pretty: false });\n\n beforeEach(async () => {\n vi.clearAllMocks();\n });\n\n it('should convert simple text messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello' });\n ctx.addMessage({ role: 'assistant', content: 'Hi there!' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'user', content: 'Hello' });\n expect(result[1]).toEqual({ role: 'assistant', content: 'Hi there!' });\n });\n\n it('should handle system messages', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'system', content: 'You are a helpful assistant' });\n ctx.addMessage({ role: 'user', content: 'Hello' });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, order may vary due to ID-based sorting\n expect(result).toHaveLength(2);\n expect(result[0]).toEqual({ role: 'system', content: 'You are a helpful assistant' });\n expect(result[1]).toEqual({ role: 'user', content: 'Hello' });\n });\n\n it('should handle multi-line text content', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: ['Line 1', 'Line 2', 'Line 3'] });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toHaveLength(1);\n expect(result[0]).toEqual({ role: 'user', content: 'Line 1\\nLine 2\\nLine 3' });\n });\n\n it('should handle messages with external URL images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Check out this image:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'high',\n },\n },\n { type: 'text', text: 'Check out this image:' },\n ],\n },\n ]);\n });\n\n it('should handle messages with base64 images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n mimeType: 'image/png',\n base64Data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'assistant',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n inferenceDetail: 'auto',\n _cache: {},\n },\n 'Here is the image you requested',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB',\n detail: 'auto',\n },\n },\n { type: 'text', text: 'Here is the image you requested' },\n ],\n },\n ]);\n });\n\n it('should handle VideoFrame images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'low',\n mimeType: 'image/jpeg',\n base64Data: '/9j/4AAQSkZJRg==',\n });\n\n const frameData = new Uint8Array(4 * 4 * 4); // 4x4 RGBA\n const videoFrame = new VideoFrame(frameData, 4, 4, VideoBufferType.RGBA);\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'frame1',\n type: 'image_content',\n image: videoFrame,\n inferenceDetail: 'low',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/jpeg;base64,/9j/4AAQSkZJRg==',\n detail: 'low',\n },\n },\n ],\n },\n ]);\n });\n\n it('should cache serialized images', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n mimeType: 'image/png',\n base64Data: 'cached-data',\n });\n\n const imageContent = {\n id: 'img1',\n type: 'image_content' as const,\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'high' as const,\n _cache: {},\n };\n\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: [imageContent] });\n\n // Call twice to test caching\n await toChatCtx(ctx);\n await toChatCtx(ctx);\n\n // serializeImage should only be called once due to caching\n expect(serializeImageMock).toHaveBeenCalledTimes(1);\n expect(imageContent._cache).toHaveProperty('serialized_image');\n });\n\n it('should handle tool calls and outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add an assistant message with tool calls\n const msg = ctx.addMessage({ role: 'assistant', content: 'Let me help you with that.' });\n const toolCall = FunctionCall.create({\n id: msg.id + '/tool_1',\n callId: 'call_123',\n name: 'get_weather',\n args: '{\"location\": \"San Francisco\"}',\n });\n const toolOutput = FunctionCallOutput.create({\n callId: 'call_123',\n output: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: 'Let me help you with that.',\n tool_calls: [\n {\n type: 'function',\n id: 'call_123',\n function: {\n name: 'get_weather',\n arguments: '{\"location\": \"San Francisco\"}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_123',\n content: '{\"temperature\": 72, \"condition\": \"sunny\"}',\n },\n ]);\n });\n\n it('should handle multiple tool calls in one message', async () => {\n const ctx = ChatContext.empty();\n\n const msg = ctx.addMessage({ role: 'assistant', content: \"I'll check both locations.\" });\n const toolCall1 = new FunctionCall({\n id: msg.id + '/tool_1',\n callId: 'call_1',\n name: 'get_weather',\n args: '{\"location\": \"NYC\"}',\n });\n const toolCall2 = new FunctionCall({\n id: msg.id + '/tool_2',\n callId: 'call_2',\n name: 'get_weather',\n args: '{\"location\": \"LA\"}',\n });\n const toolOutput1 = new FunctionCallOutput({\n callId: 'call_1',\n output: '{\"temperature\": 65}',\n isError: false,\n });\n const toolOutput2 = new FunctionCallOutput({\n callId: 'call_2',\n output: '{\"temperature\": 78}',\n isError: false,\n });\n\n ctx.insert([toolCall1, toolCall2, toolOutput1, toolOutput2]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n content: \"I'll check both locations.\",\n tool_calls: [\n {\n type: 'function',\n id: 'call_1',\n function: { name: 'get_weather', arguments: '{\"location\": \"NYC\"}' },\n },\n {\n type: 'function',\n id: 'call_2',\n function: { name: 'get_weather', arguments: '{\"location\": \"LA\"}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_1',\n content: '{\"temperature\": 65}',\n },\n {\n role: 'tool',\n tool_call_id: 'call_2',\n content: '{\"temperature\": 78}',\n },\n ]);\n });\n\n it('should handle tool calls without accompanying message', async () => {\n const ctx = ChatContext.empty();\n\n const toolCall = new FunctionCall({\n id: 'func_123',\n callId: 'call_456',\n name: 'calculate',\n args: '{\"a\": 5, \"b\": 3}',\n });\n const toolOutput = new FunctionCallOutput({\n callId: 'call_456',\n output: '{\"result\": 8}',\n isError: false,\n });\n\n ctx.insert([toolCall, toolOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n type: 'function',\n id: 'call_456',\n function: { name: 'calculate', arguments: '{\"a\": 5, \"b\": 3}' },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_456',\n content: '{\"result\": 8}',\n },\n ]);\n });\n\n it('should skip empty groups', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({ role: 'user', content: 'Hello', createdAt: 1000 });\n\n // Create an isolated tool output without corresponding call (will be filtered)\n const orphanOutput = new FunctionCallOutput({\n callId: 'orphan_call',\n output: 'This should be ignored',\n isError: false,\n createdAt: 2000,\n });\n ctx.insert(orphanOutput);\n\n ctx.addMessage({ role: 'assistant', content: 'Hi!', createdAt: 3000 });\n\n const result = await toChatCtx(ctx);\n\n // Messages should be in the result, orphan output should be filtered out\n expect(result).toHaveLength(2);\n expect(result).toContainEqual({ role: 'user', content: 'Hello' });\n expect(result).toContainEqual({ role: 'assistant', content: 'Hi!' });\n });\n\n it('should handle mixed content with text and multiple images', async () => {\n serializeImageMock\n .mockResolvedValueOnce({\n inferenceDetail: 'high',\n externalUrl: 'https://example.com/image1.jpg',\n })\n .mockResolvedValueOnce({\n inferenceDetail: 'low',\n mimeType: 'image/png',\n base64Data: 'base64data',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n 'Here are two images:',\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image1.jpg',\n inferenceDetail: 'high',\n _cache: {},\n },\n 'And the second one:',\n {\n id: 'img2',\n type: 'image_content',\n image: 'data:image/png;base64,base64data',\n inferenceDetail: 'low',\n _cache: {},\n },\n 'What do you think?',\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image1.jpg',\n detail: 'high',\n },\n },\n {\n type: 'image_url',\n image_url: {\n url: 'data:image/png;base64,base64data',\n detail: 'low',\n },\n },\n {\n type: 'text',\n text: 'Here are two images:\\nAnd the second one:\\nWhat do you think?',\n },\n ],\n },\n ]);\n });\n\n it('should handle content with only images and no text', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'auto',\n externalUrl: 'https://example.com/image.jpg',\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'https://example.com/image.jpg',\n inferenceDetail: 'auto',\n _cache: {},\n },\n ],\n });\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: 'https://example.com/image.jpg',\n detail: 'auto',\n },\n },\n ],\n },\n ]);\n });\n\n it('should throw error for unsupported content type', async () => {\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n type: 'audio_content',\n frame: [],\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Unsupported content type: audio_content');\n });\n\n it('should throw error when serialized image has no data', async () => {\n serializeImageMock.mockResolvedValue({\n inferenceDetail: 'high',\n // No base64Data or externalUrl\n });\n\n const ctx = ChatContext.empty();\n ctx.addMessage({\n role: 'user',\n content: [\n {\n id: 'img1',\n type: 'image_content',\n image: 'invalid-image',\n inferenceDetail: 'high',\n _cache: {},\n },\n ],\n });\n\n await expect(toChatCtx(ctx)).rejects.toThrow('Serialized image has no data bytes');\n });\n\n it('should filter out standalone function calls without outputs', async () => {\n const ctx = ChatContext.empty();\n\n // Add standalone function call without output\n const funcCall = new FunctionCall({\n id: 'func_standalone',\n callId: 'call_999',\n name: 'standalone_function',\n args: '{}',\n });\n\n ctx.insert(funcCall);\n\n const result = await toChatCtx(ctx);\n\n // Standalone function calls without outputs are filtered out by groupToolCalls\n expect(result).toEqual([]);\n });\n\n it('should handle function call output correctly', async () => {\n const ctx = ChatContext.empty();\n\n // First add a function call\n const funcCall = new FunctionCall({\n id: 'func_1',\n callId: 'call_output_test',\n name: 'test_function',\n args: '{}',\n });\n\n // Then add its output\n const funcOutput = new FunctionCallOutput({\n callId: 'call_output_test',\n output: 'Function executed successfully',\n isError: false,\n });\n\n ctx.insert([funcCall, funcOutput]);\n\n const result = await toChatCtx(ctx);\n\n expect(result).toEqual([\n {\n role: 'assistant',\n tool_calls: [\n {\n id: 'call_output_test',\n type: 'function',\n function: {\n name: 'test_function',\n arguments: '{}',\n },\n },\n ],\n },\n {\n role: 'tool',\n tool_call_id: 'call_output_test',\n content: 'Function executed successfully',\n },\n ]);\n });\n\n it('should filter out agent handoff items', async () => {\n const ctx = ChatContext.empty();\n\n ctx.addMessage({ role: 'user', content: 'Hello' });\n\n // Insert an agent handoff item\n const handoff = new AgentHandoffItem({\n oldAgentId: 'agent_1',\n newAgentId: 'agent_2',\n });\n ctx.insert(handoff);\n\n ctx.addMessage({ role: 'assistant', content: 'Hi there!' });\n\n const result = await toChatCtx(ctx);\n\n // Agent handoff should be filtered out, only messages should remain\n expect(result).toEqual([\n { role: 'user', content: 'Hello' },\n { role: 'assistant', content: 'Hi there!' },\n ]);\n });\n\n it('should handle multiple agent handoffs without errors', async () => {\n const ctx = ChatContext.empty();\n\n ctx.addMessage({ role: 'user', content: 'Start' });\n\n // Multiple handoffs\n ctx.insert(new AgentHandoffItem({ oldAgentId: undefined, newAgentId: 'agent_1' }));\n ctx.addMessage({ role: 'assistant', content: 'Response from agent 1' });\n\n ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_1', newAgentId: 'agent_2' }));\n ctx.addMessage({ role: 'assistant', content: 'Response from agent 2' });\n\n ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_2', newAgentId: 'agent_3' }));\n ctx.addMessage({ role: 'assistant', content: 'Response from agent 3' });\n\n const result = await toChatCtx(ctx);\n\n // All handoffs should be filtered out\n expect(result).toEqual([\n { role: 'user', content: 'Start' },\n { role: 'assistant', content: 'Response from agent 1' },\n { role: 'assistant', content: 'Response from agent 2' },\n { role: 'assistant', content: 'Response from agent 3' },\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}\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}\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: { messageId: string; audioEndMs: number }): 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;AA6Cd,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,EA4CA,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}\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":[]}
@@ -13,6 +13,7 @@ export interface MessageGeneration {
13
13
  messageId: string;
14
14
  textStream: ReadableStream<string>;
15
15
  audioStream: ReadableStream<AudioFrame>;
16
+ modalities?: Promise<('text' | 'audio')[]>;
16
17
  }
17
18
  export interface GenerationCreatedEvent {
18
19
  messageStream: ReadableStream<MessageGeneration>;
@@ -31,6 +32,7 @@ export interface RealtimeCapabilities {
31
32
  turnDetection: boolean;
32
33
  userTranscription: boolean;
33
34
  autoToolReplyGeneration: boolean;
35
+ audioOutput: boolean;
34
36
  }
35
37
  export interface InputTranscriptionCompleted {
36
38
  itemId: string;
@@ -86,6 +88,8 @@ export declare abstract class RealtimeSession extends EventEmitter {
86
88
  abstract truncate(options: {
87
89
  messageId: string;
88
90
  audioEndMs: number;
91
+ modalities?: ('text' | 'audio')[];
92
+ audioTranscript?: string;
89
93
  }): Promise<void>;
90
94
  close(): Promise<void>;
91
95
  /**
@@ -13,6 +13,7 @@ export interface MessageGeneration {
13
13
  messageId: string;
14
14
  textStream: ReadableStream<string>;
15
15
  audioStream: ReadableStream<AudioFrame>;
16
+ modalities?: Promise<('text' | 'audio')[]>;
16
17
  }
17
18
  export interface GenerationCreatedEvent {
18
19
  messageStream: ReadableStream<MessageGeneration>;
@@ -31,6 +32,7 @@ export interface RealtimeCapabilities {
31
32
  turnDetection: boolean;
32
33
  userTranscription: boolean;
33
34
  autoToolReplyGeneration: boolean;
35
+ audioOutput: boolean;
34
36
  }
35
37
  export interface InputTranscriptionCompleted {
36
38
  itemId: string;
@@ -86,6 +88,8 @@ export declare abstract class RealtimeSession extends EventEmitter {
86
88
  abstract truncate(options: {
87
89
  messageId: string;
88
90
  audioEndMs: number;
91
+ modalities?: ('text' | 'audio')[];
92
+ audioTranscript?: string;
89
93
  }): Promise<void>;
90
94
  close(): Promise<void>;
91
95
  /**
@@ -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":";;AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAC;IACjD,cAAc,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;IAC7C,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uBAAuB,EAAE,OAAO,CAAC;IACjC,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,+BAA+B;CAAG;AAEnD,8BAAsB,aAAa;IACjC,OAAO,CAAC,aAAa,CAAuB;gBAEhC,YAAY,EAAE,oBAAoB;IAI9C,IAAI,YAAY,yBAEf;IAED,QAAQ,CAAC,OAAO,IAAI,eAAe;IAEnC,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAChC;AAED,8BAAsB,eAAgB,SAAQ,YAAY;IACxD,SAAS,CAAC,cAAc,EAAE,aAAa,CAAC;IACxC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,SAAS,CAAa;gBAElB,aAAa,EAAE,aAAa;IAMxC,IAAI,aAAa,kBAEhB;IAED,QAAQ,KAAK,OAAO,IAAI,WAAW,CAAC;IAEpC,QAAQ,KAAK,KAAK,IAAI,WAAW,CAAC;IAElC,QAAQ,CAAC,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEhE;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3D,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAEvD,QAAQ,CAAC,aAAa,CAAC,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAEzE,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAE3C;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAE9E;;OAEG;IACH,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAErC;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpC;;OAEG;IACH,QAAQ,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEnC;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;QAClC,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,GAAG,OAAO,CAAC,IAAI,CAAC;IAEX,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,iBAAiB,IAAI,IAAI;YAIX,aAAa;IAW3B,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI;CAGnE"}
@@ -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}\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}\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: { messageId: string; audioEndMs: number }): 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;AA6Cd,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,EA4CA,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}\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":[]}
@@ -248,9 +248,9 @@ function computeChatCtxDiff(oldCtx, newCtx) {
248
248
  toCreate
249
249
  };
250
250
  }
251
- function toJsonSchema(schema, isOpenai = true) {
251
+ function toJsonSchema(schema, isOpenai = true, strict = false) {
252
252
  if ((0, import_zod_utils.isZodSchema)(schema)) {
253
- return (0, import_zod_utils.zodSchemaToJsonSchema)(schema, isOpenai);
253
+ return (0, import_zod_utils.zodSchemaToJsonSchema)(schema, isOpenai, strict);
254
254
  }
255
255
  return schema;
256
256
  }
@@ -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 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';\nimport { isZodSchema, parseZodSchema, zodSchemaToJsonSchema } from './zod-utils.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 = (schema: any, isOpenai: boolean = true): OpenAIFunctionParameters => {\n // Adapted from https://github.com/vercel/ai/blob/56eb0ee9/packages/provider-utils/src/zod-schema.ts\n const jsonSchema = zodSchemaToJsonSchema(schema, isOpenai);\n const { properties, required, additionalProperties } = jsonSchema 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 (isZodSchema(tool.parameters)) {\n const result = await parseZodSchema<object>(tool.parameters, args);\n if (result.success) {\n params = result.data;\n } else {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Arguments parsing failed: ${result.error}`,\n isError: true,\n });\n }\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 (isZodSchema(schema)) {\n return zodSchemaToJsonSchema(schema, isOpenai);\n }\n return schema as JSONSchema7;\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 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';\nimport { isZodSchema, parseZodSchema, zodSchemaToJsonSchema } from './zod-utils.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 = (schema: any, isOpenai: boolean = true): OpenAIFunctionParameters => {\n // Adapted from https://github.com/vercel/ai/blob/56eb0ee9/packages/provider-utils/src/zod-schema.ts\n const jsonSchema = zodSchemaToJsonSchema(schema, isOpenai);\n const { properties, required, additionalProperties } = jsonSchema 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 (isZodSchema(tool.parameters)) {\n const result = await parseZodSchema<object>(tool.parameters, args);\n if (result.success) {\n params = result.data;\n } else {\n return FunctionCallOutput.create({\n callId: toolCall.callId,\n output: `Arguments parsing failed: ${result.error}`,\n isError: true,\n });\n }\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(\n schema: ToolInputSchema<any>,\n isOpenai: boolean = true,\n strict: boolean = false,\n): JSONSchema7 {\n if (isZodSchema(schema)) {\n return zodSchemaToJsonSchema(schema, isOpenai, strict);\n }\n\n return schema as JSONSchema7;\n}\n"],"mappings":"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","names":["sharp","i","j"]}
@@ -36,6 +36,6 @@ interface DiffOps {
36
36
  * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.
37
37
  */
38
38
  export declare function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps;
39
- export declare function toJsonSchema(schema: ToolInputSchema<any>, isOpenai?: boolean): JSONSchema7;
39
+ export declare function toJsonSchema(schema: ToolInputSchema<any>, isOpenai?: boolean, strict?: boolean): JSONSchema7;
40
40
  export {};
41
41
  //# sourceMappingURL=utils.d.ts.map
@@ -36,6 +36,6 @@ interface DiffOps {
36
36
  * @returns The minimal list of create/remove operations to transform oldCtx into newCtx.
37
37
  */
38
38
  export declare function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): DiffOps;
39
- export declare function toJsonSchema(schema: ToolInputSchema<any>, isOpenai?: boolean): JSONSchema7;
39
+ export declare function toJsonSchema(schema: ToolInputSchema<any>, isOpenai?: boolean, strict?: boolean): JSONSchema7;
40
40
  export {};
41
41
  //# 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;AAG/C,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;AAGnF,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,WAAY,GAAG,aAAY,OAAO,KAAU,wBAWjE,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,CAsD7B;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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/llm/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,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;AAGnF,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,WAAY,GAAG,aAAY,OAAO,KAAU,wBAWjE,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,CAsD7B;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,CAC1B,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,EAC5B,QAAQ,GAAE,OAAc,EACxB,MAAM,GAAE,OAAe,GACtB,WAAW,CAMb"}
package/dist/llm/utils.js CHANGED
@@ -212,9 +212,9 @@ function computeChatCtxDiff(oldCtx, newCtx) {
212
212
  toCreate
213
213
  };
214
214
  }
215
- function toJsonSchema(schema, isOpenai = true) {
215
+ function toJsonSchema(schema, isOpenai = true, strict = false) {
216
216
  if (isZodSchema(schema)) {
217
- return zodSchemaToJsonSchema(schema, isOpenai);
217
+ return zodSchemaToJsonSchema(schema, isOpenai, strict);
218
218
  }
219
219
  return schema;
220
220
  }