@livekit/agents 1.0.45 → 1.0.47

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 (225) hide show
  1. package/dist/cli.cjs +14 -20
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +14 -20
  5. package/dist/cli.js.map +1 -1
  6. package/dist/ipc/job_proc_lazy_main.cjs +14 -5
  7. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  8. package/dist/ipc/job_proc_lazy_main.js +14 -5
  9. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  10. package/dist/llm/chat_context.cjs +19 -0
  11. package/dist/llm/chat_context.cjs.map +1 -1
  12. package/dist/llm/chat_context.d.cts +4 -0
  13. package/dist/llm/chat_context.d.ts +4 -0
  14. package/dist/llm/chat_context.d.ts.map +1 -1
  15. package/dist/llm/chat_context.js +19 -0
  16. package/dist/llm/chat_context.js.map +1 -1
  17. package/dist/llm/provider_format/index.cjs +2 -0
  18. package/dist/llm/provider_format/index.cjs.map +1 -1
  19. package/dist/llm/provider_format/index.d.cts +1 -1
  20. package/dist/llm/provider_format/index.d.ts +1 -1
  21. package/dist/llm/provider_format/index.d.ts.map +1 -1
  22. package/dist/llm/provider_format/index.js +6 -1
  23. package/dist/llm/provider_format/index.js.map +1 -1
  24. package/dist/llm/provider_format/openai.cjs +82 -2
  25. package/dist/llm/provider_format/openai.cjs.map +1 -1
  26. package/dist/llm/provider_format/openai.d.cts +1 -0
  27. package/dist/llm/provider_format/openai.d.ts +1 -0
  28. package/dist/llm/provider_format/openai.d.ts.map +1 -1
  29. package/dist/llm/provider_format/openai.js +80 -1
  30. package/dist/llm/provider_format/openai.js.map +1 -1
  31. package/dist/llm/provider_format/openai.test.cjs +326 -0
  32. package/dist/llm/provider_format/openai.test.cjs.map +1 -1
  33. package/dist/llm/provider_format/openai.test.js +327 -1
  34. package/dist/llm/provider_format/openai.test.js.map +1 -1
  35. package/dist/llm/provider_format/utils.cjs +4 -3
  36. package/dist/llm/provider_format/utils.cjs.map +1 -1
  37. package/dist/llm/provider_format/utils.d.ts.map +1 -1
  38. package/dist/llm/provider_format/utils.js +4 -3
  39. package/dist/llm/provider_format/utils.js.map +1 -1
  40. package/dist/llm/realtime.cjs.map +1 -1
  41. package/dist/llm/realtime.d.cts +1 -0
  42. package/dist/llm/realtime.d.ts +1 -0
  43. package/dist/llm/realtime.d.ts.map +1 -1
  44. package/dist/llm/realtime.js.map +1 -1
  45. package/dist/log.cjs +5 -2
  46. package/dist/log.cjs.map +1 -1
  47. package/dist/log.d.ts.map +1 -1
  48. package/dist/log.js +5 -2
  49. package/dist/log.js.map +1 -1
  50. package/dist/stream/deferred_stream.cjs +15 -6
  51. package/dist/stream/deferred_stream.cjs.map +1 -1
  52. package/dist/stream/deferred_stream.d.ts.map +1 -1
  53. package/dist/stream/deferred_stream.js +15 -6
  54. package/dist/stream/deferred_stream.js.map +1 -1
  55. package/dist/stream/index.cjs +3 -0
  56. package/dist/stream/index.cjs.map +1 -1
  57. package/dist/stream/index.d.cts +1 -0
  58. package/dist/stream/index.d.ts +1 -0
  59. package/dist/stream/index.d.ts.map +1 -1
  60. package/dist/stream/index.js +2 -0
  61. package/dist/stream/index.js.map +1 -1
  62. package/dist/stream/multi_input_stream.cjs +139 -0
  63. package/dist/stream/multi_input_stream.cjs.map +1 -0
  64. package/dist/stream/multi_input_stream.d.cts +55 -0
  65. package/dist/stream/multi_input_stream.d.ts +55 -0
  66. package/dist/stream/multi_input_stream.d.ts.map +1 -0
  67. package/dist/stream/multi_input_stream.js +115 -0
  68. package/dist/stream/multi_input_stream.js.map +1 -0
  69. package/dist/stream/multi_input_stream.test.cjs +340 -0
  70. package/dist/stream/multi_input_stream.test.cjs.map +1 -0
  71. package/dist/stream/multi_input_stream.test.js +339 -0
  72. package/dist/stream/multi_input_stream.test.js.map +1 -0
  73. package/dist/telemetry/trace_types.cjs +42 -0
  74. package/dist/telemetry/trace_types.cjs.map +1 -1
  75. package/dist/telemetry/trace_types.d.cts +14 -0
  76. package/dist/telemetry/trace_types.d.ts +14 -0
  77. package/dist/telemetry/trace_types.d.ts.map +1 -1
  78. package/dist/telemetry/trace_types.js +28 -0
  79. package/dist/telemetry/trace_types.js.map +1 -1
  80. package/dist/utils.cjs +44 -2
  81. package/dist/utils.cjs.map +1 -1
  82. package/dist/utils.d.cts +8 -0
  83. package/dist/utils.d.ts +8 -0
  84. package/dist/utils.d.ts.map +1 -1
  85. package/dist/utils.js +44 -2
  86. package/dist/utils.js.map +1 -1
  87. package/dist/utils.test.cjs +71 -0
  88. package/dist/utils.test.cjs.map +1 -1
  89. package/dist/utils.test.js +71 -0
  90. package/dist/utils.test.js.map +1 -1
  91. package/dist/version.cjs +1 -1
  92. package/dist/version.cjs.map +1 -1
  93. package/dist/version.d.cts +1 -1
  94. package/dist/version.d.ts +1 -1
  95. package/dist/version.d.ts.map +1 -1
  96. package/dist/version.js +1 -1
  97. package/dist/version.js.map +1 -1
  98. package/dist/voice/agent.cjs +144 -12
  99. package/dist/voice/agent.cjs.map +1 -1
  100. package/dist/voice/agent.d.cts +29 -4
  101. package/dist/voice/agent.d.ts +29 -4
  102. package/dist/voice/agent.d.ts.map +1 -1
  103. package/dist/voice/agent.js +140 -11
  104. package/dist/voice/agent.js.map +1 -1
  105. package/dist/voice/agent.test.cjs +120 -0
  106. package/dist/voice/agent.test.cjs.map +1 -1
  107. package/dist/voice/agent.test.js +122 -2
  108. package/dist/voice/agent.test.js.map +1 -1
  109. package/dist/voice/agent_activity.cjs +402 -292
  110. package/dist/voice/agent_activity.cjs.map +1 -1
  111. package/dist/voice/agent_activity.d.cts +35 -7
  112. package/dist/voice/agent_activity.d.ts +35 -7
  113. package/dist/voice/agent_activity.d.ts.map +1 -1
  114. package/dist/voice/agent_activity.js +402 -287
  115. package/dist/voice/agent_activity.js.map +1 -1
  116. package/dist/voice/agent_session.cjs +156 -44
  117. package/dist/voice/agent_session.cjs.map +1 -1
  118. package/dist/voice/agent_session.d.cts +22 -9
  119. package/dist/voice/agent_session.d.ts +22 -9
  120. package/dist/voice/agent_session.d.ts.map +1 -1
  121. package/dist/voice/agent_session.js +156 -44
  122. package/dist/voice/agent_session.js.map +1 -1
  123. package/dist/voice/audio_recognition.cjs +89 -36
  124. package/dist/voice/audio_recognition.cjs.map +1 -1
  125. package/dist/voice/audio_recognition.d.cts +22 -1
  126. package/dist/voice/audio_recognition.d.ts +22 -1
  127. package/dist/voice/audio_recognition.d.ts.map +1 -1
  128. package/dist/voice/audio_recognition.js +93 -36
  129. package/dist/voice/audio_recognition.js.map +1 -1
  130. package/dist/voice/audio_recognition_span.test.cjs +233 -0
  131. package/dist/voice/audio_recognition_span.test.cjs.map +1 -0
  132. package/dist/voice/audio_recognition_span.test.js +232 -0
  133. package/dist/voice/audio_recognition_span.test.js.map +1 -0
  134. package/dist/voice/generation.cjs +39 -19
  135. package/dist/voice/generation.cjs.map +1 -1
  136. package/dist/voice/generation.d.ts.map +1 -1
  137. package/dist/voice/generation.js +44 -20
  138. package/dist/voice/generation.js.map +1 -1
  139. package/dist/voice/index.cjs +2 -0
  140. package/dist/voice/index.cjs.map +1 -1
  141. package/dist/voice/index.d.cts +1 -1
  142. package/dist/voice/index.d.ts +1 -1
  143. package/dist/voice/index.d.ts.map +1 -1
  144. package/dist/voice/index.js +2 -1
  145. package/dist/voice/index.js.map +1 -1
  146. package/dist/voice/io.cjs +6 -3
  147. package/dist/voice/io.cjs.map +1 -1
  148. package/dist/voice/io.d.cts +3 -2
  149. package/dist/voice/io.d.ts +3 -2
  150. package/dist/voice/io.d.ts.map +1 -1
  151. package/dist/voice/io.js +6 -3
  152. package/dist/voice/io.js.map +1 -1
  153. package/dist/voice/recorder_io/recorder_io.cjs +3 -1
  154. package/dist/voice/recorder_io/recorder_io.cjs.map +1 -1
  155. package/dist/voice/recorder_io/recorder_io.d.ts.map +1 -1
  156. package/dist/voice/recorder_io/recorder_io.js +3 -1
  157. package/dist/voice/recorder_io/recorder_io.js.map +1 -1
  158. package/dist/voice/room_io/_input.cjs +17 -17
  159. package/dist/voice/room_io/_input.cjs.map +1 -1
  160. package/dist/voice/room_io/_input.d.cts +2 -2
  161. package/dist/voice/room_io/_input.d.ts +2 -2
  162. package/dist/voice/room_io/_input.d.ts.map +1 -1
  163. package/dist/voice/room_io/_input.js +7 -6
  164. package/dist/voice/room_io/_input.js.map +1 -1
  165. package/dist/voice/room_io/room_io.cjs +9 -0
  166. package/dist/voice/room_io/room_io.cjs.map +1 -1
  167. package/dist/voice/room_io/room_io.d.cts +3 -1
  168. package/dist/voice/room_io/room_io.d.ts +3 -1
  169. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  170. package/dist/voice/room_io/room_io.js +9 -0
  171. package/dist/voice/room_io/room_io.js.map +1 -1
  172. package/dist/voice/speech_handle.cjs +7 -1
  173. package/dist/voice/speech_handle.cjs.map +1 -1
  174. package/dist/voice/speech_handle.d.cts +2 -0
  175. package/dist/voice/speech_handle.d.ts +2 -0
  176. package/dist/voice/speech_handle.d.ts.map +1 -1
  177. package/dist/voice/speech_handle.js +8 -2
  178. package/dist/voice/speech_handle.js.map +1 -1
  179. package/dist/voice/testing/run_result.cjs +66 -15
  180. package/dist/voice/testing/run_result.cjs.map +1 -1
  181. package/dist/voice/testing/run_result.d.cts +14 -3
  182. package/dist/voice/testing/run_result.d.ts +14 -3
  183. package/dist/voice/testing/run_result.d.ts.map +1 -1
  184. package/dist/voice/testing/run_result.js +66 -15
  185. package/dist/voice/testing/run_result.js.map +1 -1
  186. package/dist/voice/utils.cjs +47 -0
  187. package/dist/voice/utils.cjs.map +1 -0
  188. package/dist/voice/utils.d.cts +4 -0
  189. package/dist/voice/utils.d.ts +4 -0
  190. package/dist/voice/utils.d.ts.map +1 -0
  191. package/dist/voice/utils.js +23 -0
  192. package/dist/voice/utils.js.map +1 -0
  193. package/package.json +1 -1
  194. package/src/cli.ts +20 -33
  195. package/src/ipc/job_proc_lazy_main.ts +16 -5
  196. package/src/llm/chat_context.ts +35 -0
  197. package/src/llm/provider_format/index.ts +7 -2
  198. package/src/llm/provider_format/openai.test.ts +385 -1
  199. package/src/llm/provider_format/openai.ts +103 -0
  200. package/src/llm/provider_format/utils.ts +6 -4
  201. package/src/llm/realtime.ts +1 -0
  202. package/src/log.ts +5 -2
  203. package/src/stream/deferred_stream.ts +17 -6
  204. package/src/stream/index.ts +1 -0
  205. package/src/stream/multi_input_stream.test.ts +540 -0
  206. package/src/stream/multi_input_stream.ts +172 -0
  207. package/src/telemetry/trace_types.ts +18 -0
  208. package/src/utils.test.ts +87 -0
  209. package/src/utils.ts +52 -2
  210. package/src/version.ts +1 -1
  211. package/src/voice/agent.test.ts +140 -2
  212. package/src/voice/agent.ts +189 -10
  213. package/src/voice/agent_activity.ts +449 -286
  214. package/src/voice/agent_session.ts +195 -51
  215. package/src/voice/audio_recognition.ts +118 -38
  216. package/src/voice/audio_recognition_span.test.ts +261 -0
  217. package/src/voice/generation.ts +52 -23
  218. package/src/voice/index.ts +1 -1
  219. package/src/voice/io.ts +7 -4
  220. package/src/voice/recorder_io/recorder_io.ts +2 -1
  221. package/src/voice/room_io/_input.ts +11 -7
  222. package/src/voice/room_io/room_io.ts +12 -0
  223. package/src/voice/speech_handle.ts +9 -2
  224. package/src/voice/testing/run_result.ts +81 -23
  225. package/src/voice/utils.ts +29 -0
@@ -108,7 +108,86 @@ async function toImageContent(content) {
108
108
  }
109
109
  };
110
110
  }
111
+ async function toResponsesImageContent(content) {
112
+ const cacheKey = "serialized_image";
113
+ let serialized;
114
+ if (content._cache[cacheKey] === void 0) {
115
+ serialized = await serializeImage(content);
116
+ content._cache[cacheKey] = serialized;
117
+ }
118
+ serialized = content._cache[cacheKey];
119
+ if (serialized.externalUrl) {
120
+ return {
121
+ type: "input_image",
122
+ image_url: serialized.externalUrl,
123
+ detail: serialized.inferenceDetail
124
+ };
125
+ }
126
+ if (serialized.base64Data === void 0) {
127
+ throw new Error("Serialized image has no data bytes");
128
+ }
129
+ return {
130
+ type: "input_image",
131
+ image_url: `data:${serialized.mimeType};base64,${serialized.base64Data}`,
132
+ detail: serialized.inferenceDetail
133
+ };
134
+ }
135
+ async function toResponsesChatCtx(chatCtx, injectDummyUserMessage = true) {
136
+ const itemGroups = groupToolCalls(chatCtx);
137
+ const messages = [];
138
+ for (const group of itemGroups) {
139
+ if (group.isEmpty) continue;
140
+ if (group.message) {
141
+ messages.push(await toResponsesChatItem(group.message));
142
+ }
143
+ for (const toolCall of group.toolCalls) {
144
+ messages.push({
145
+ type: "function_call",
146
+ call_id: toolCall.callId,
147
+ name: toolCall.name,
148
+ arguments: toolCall.args
149
+ });
150
+ }
151
+ for (const toolOutput of group.toolOutputs) {
152
+ messages.push(await toResponsesChatItem(toolOutput));
153
+ }
154
+ }
155
+ return messages;
156
+ }
157
+ async function toResponsesChatItem(item) {
158
+ if (item.type === "message") {
159
+ const listContent = [];
160
+ let textContent = "";
161
+ for (const content2 of item.content) {
162
+ if (typeof content2 === "string") {
163
+ if (textContent) textContent += "\n";
164
+ textContent += content2;
165
+ } else if (content2.type === "image_content") {
166
+ listContent.push(await toResponsesImageContent(content2));
167
+ } else {
168
+ throw new Error(`Unsupported content type: ${content2.type}`);
169
+ }
170
+ }
171
+ const content = listContent.length == 0 ? textContent : textContent.length == 0 ? listContent : [...listContent, { type: "input_text", text: textContent }];
172
+ return { role: item.role, content };
173
+ } else if (item.type === "function_call") {
174
+ return {
175
+ type: "function_call",
176
+ call_id: item.callId,
177
+ name: item.name,
178
+ arguments: item.args
179
+ };
180
+ } else if (item.type === "function_call_output") {
181
+ return {
182
+ type: "function_call_output",
183
+ call_id: item.callId,
184
+ output: item.output
185
+ };
186
+ }
187
+ throw new Error(`Unsupported item type: ${item["type"]}`);
188
+ }
111
189
  export {
112
- toChatCtx
190
+ toChatCtx,
191
+ toResponsesChatCtx
113
192
  };
114
193
  //# sourceMappingURL=openai.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/llm/provider_format/openai.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext, ChatItem, ImageContent } from '../chat_context.js';\nimport { type SerializedImage, serializeImage } from '../utils.js';\nimport { groupToolCalls } from './utils.js';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport async function toChatCtx(chatCtx: ChatContext, injectDummyUserMessage: boolean = true) {\n const itemGroups = groupToolCalls(chatCtx);\n const messages: Record<string, any>[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n for (const group of itemGroups) {\n if (group.isEmpty) continue;\n\n const message: Record<string, any> = group.message // eslint-disable-line @typescript-eslint/no-explicit-any\n ? await toChatItem(group.message)\n : { role: 'assistant' };\n\n const toolCalls = group.toolCalls.map((toolCall) => {\n const tc: Record<string, any> = {\n type: 'function',\n id: toolCall.callId,\n function: { name: toolCall.name, arguments: toolCall.args },\n };\n\n // Include provider-specific extra content (e.g., Google thought signatures)\n const googleExtra = getGoogleExtra(toolCall);\n if (googleExtra) {\n tc.extra_content = { google: googleExtra };\n }\n return tc;\n });\n\n if (toolCalls.length > 0) {\n message['tool_calls'] = toolCalls;\n }\n\n messages.push(message);\n\n for (const toolOutput of group.toolOutputs) {\n messages.push(await toChatItem(toolOutput));\n }\n }\n\n return messages;\n}\n\nasync function toChatItem(item: ChatItem) {\n if (item.type === 'message') {\n const listContent: Record<string, any>[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n let textContent = '';\n\n for (const content of item.content) {\n if (typeof content === 'string') {\n if (textContent) textContent += '\\n';\n textContent += content;\n } else if (content.type === 'image_content') {\n listContent.push(await toImageContent(content));\n } else {\n throw new Error(`Unsupported content type: ${content.type}`);\n }\n }\n\n const result: Record<string, any> = { role: item.role };\n if (listContent.length === 0) {\n result.content = textContent;\n } else {\n if (textContent.length > 0) {\n listContent.push({ type: 'text', text: textContent });\n }\n result.content = listContent;\n }\n\n return result;\n } else if (item.type === 'function_call') {\n const tc: Record<string, any> = {\n id: item.callId,\n type: 'function',\n function: { name: item.name, arguments: item.args },\n };\n\n // Include provider-specific extra content (e.g., Google thought signatures)\n const googleExtra = getGoogleExtra(item);\n if (googleExtra) {\n tc.extra_content = { google: googleExtra };\n }\n\n return {\n role: 'assistant',\n tool_calls: [tc],\n };\n } else if (item.type === 'function_call_output') {\n return {\n role: 'tool',\n tool_call_id: item.callId,\n content: item.output,\n };\n }\n // Skip other item types (e.g., agent_handoff)\n // These should be filtered by groupToolCalls, but this is a safety net\n throw new Error(`Unsupported item type: ${item['type']}`);\n}\n\nfunction getGoogleExtra(\n item: Partial<{ extra?: Record<string, unknown>; thoughtSignature?: string }>,\n): Record<string, unknown> | undefined {\n const googleExtra =\n (item.extra?.google as Record<string, unknown> | undefined) ||\n (item.thoughtSignature ? { thoughtSignature: item.thoughtSignature } : undefined);\n return googleExtra;\n}\n\nasync function toImageContent(content: ImageContent) {\n const cacheKey = 'serialized_image'; // TODO: use hash of encoding options if available\n let serialized: SerializedImage;\n\n if (content._cache[cacheKey] === undefined) {\n serialized = await serializeImage(content);\n content._cache[cacheKey] = serialized;\n }\n serialized = content._cache[cacheKey];\n\n // Convert SerializedImage to OpenAI format\n if (serialized.externalUrl) {\n return {\n type: 'image_url',\n image_url: {\n url: serialized.externalUrl,\n detail: serialized.inferenceDetail,\n },\n };\n }\n\n if (serialized.base64Data === undefined) {\n throw new Error('Serialized image has no data bytes');\n }\n\n return {\n type: 'image_url',\n image_url: {\n url: `data:${serialized.mimeType};base64,${serialized.base64Data}`,\n detail: serialized.inferenceDetail,\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/llm/provider_format/openai.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext, ChatItem, ImageContent } from '../chat_context.js';\nimport { type SerializedImage, serializeImage } from '../utils.js';\nimport { groupToolCalls } from './utils.js';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport async function toChatCtx(chatCtx: ChatContext, injectDummyUserMessage: boolean = true) {\n const itemGroups = groupToolCalls(chatCtx);\n const messages: Record<string, any>[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n for (const group of itemGroups) {\n if (group.isEmpty) continue;\n\n const message: Record<string, any> = group.message // eslint-disable-line @typescript-eslint/no-explicit-any\n ? await toChatItem(group.message)\n : { role: 'assistant' };\n\n const toolCalls = group.toolCalls.map((toolCall) => {\n const tc: Record<string, any> = {\n type: 'function',\n id: toolCall.callId,\n function: { name: toolCall.name, arguments: toolCall.args },\n };\n\n // Include provider-specific extra content (e.g., Google thought signatures)\n const googleExtra = getGoogleExtra(toolCall);\n if (googleExtra) {\n tc.extra_content = { google: googleExtra };\n }\n return tc;\n });\n\n if (toolCalls.length > 0) {\n message['tool_calls'] = toolCalls;\n }\n\n messages.push(message);\n\n for (const toolOutput of group.toolOutputs) {\n messages.push(await toChatItem(toolOutput));\n }\n }\n\n return messages;\n}\n\nasync function toChatItem(item: ChatItem) {\n if (item.type === 'message') {\n const listContent: Record<string, any>[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n let textContent = '';\n\n for (const content of item.content) {\n if (typeof content === 'string') {\n if (textContent) textContent += '\\n';\n textContent += content;\n } else if (content.type === 'image_content') {\n listContent.push(await toImageContent(content));\n } else {\n throw new Error(`Unsupported content type: ${content.type}`);\n }\n }\n\n const result: Record<string, any> = { role: item.role };\n if (listContent.length === 0) {\n result.content = textContent;\n } else {\n if (textContent.length > 0) {\n listContent.push({ type: 'text', text: textContent });\n }\n result.content = listContent;\n }\n\n return result;\n } else if (item.type === 'function_call') {\n const tc: Record<string, any> = {\n id: item.callId,\n type: 'function',\n function: { name: item.name, arguments: item.args },\n };\n\n // Include provider-specific extra content (e.g., Google thought signatures)\n const googleExtra = getGoogleExtra(item);\n if (googleExtra) {\n tc.extra_content = { google: googleExtra };\n }\n\n return {\n role: 'assistant',\n tool_calls: [tc],\n };\n } else if (item.type === 'function_call_output') {\n return {\n role: 'tool',\n tool_call_id: item.callId,\n content: item.output,\n };\n }\n // Skip other item types (e.g., agent_handoff)\n // These should be filtered by groupToolCalls, but this is a safety net\n throw new Error(`Unsupported item type: ${item['type']}`);\n}\n\nfunction getGoogleExtra(\n item: Partial<{ extra?: Record<string, unknown>; thoughtSignature?: string }>,\n): Record<string, unknown> | undefined {\n const googleExtra =\n (item.extra?.google as Record<string, unknown> | undefined) ||\n (item.thoughtSignature ? { thoughtSignature: item.thoughtSignature } : undefined);\n return googleExtra;\n}\n\nasync function toImageContent(content: ImageContent) {\n const cacheKey = 'serialized_image'; // TODO: use hash of encoding options if available\n let serialized: SerializedImage;\n\n if (content._cache[cacheKey] === undefined) {\n serialized = await serializeImage(content);\n content._cache[cacheKey] = serialized;\n }\n serialized = content._cache[cacheKey];\n\n // Convert SerializedImage to OpenAI format\n if (serialized.externalUrl) {\n return {\n type: 'image_url',\n image_url: {\n url: serialized.externalUrl,\n detail: serialized.inferenceDetail,\n },\n };\n }\n\n if (serialized.base64Data === undefined) {\n throw new Error('Serialized image has no data bytes');\n }\n\n return {\n type: 'image_url',\n image_url: {\n url: `data:${serialized.mimeType};base64,${serialized.base64Data}`,\n detail: serialized.inferenceDetail,\n },\n };\n}\n\nasync function toResponsesImageContent(content: ImageContent) {\n const cacheKey = 'serialized_image';\n let serialized: SerializedImage;\n\n if (content._cache[cacheKey] === undefined) {\n serialized = await serializeImage(content);\n content._cache[cacheKey] = serialized;\n }\n serialized = content._cache[cacheKey];\n\n if (serialized.externalUrl) {\n return {\n type: 'input_image' as const,\n image_url: serialized.externalUrl,\n detail: serialized.inferenceDetail,\n };\n }\n\n if (serialized.base64Data === undefined) {\n throw new Error('Serialized image has no data bytes');\n }\n\n return {\n type: 'input_image' as const,\n image_url: `data:${serialized.mimeType};base64,${serialized.base64Data}`,\n detail: serialized.inferenceDetail,\n };\n}\n\nexport async function toResponsesChatCtx(\n chatCtx: ChatContext,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n injectDummyUserMessage: boolean = true,\n) {\n const itemGroups = groupToolCalls(chatCtx);\n const messages: Record<string, any>[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n for (const group of itemGroups) {\n if (group.isEmpty) continue;\n\n if (group.message) {\n messages.push(await toResponsesChatItem(group.message));\n }\n\n for (const toolCall of group.toolCalls) {\n messages.push({\n type: 'function_call',\n call_id: toolCall.callId,\n name: toolCall.name,\n arguments: toolCall.args,\n });\n }\n\n for (const toolOutput of group.toolOutputs) {\n messages.push(await toResponsesChatItem(toolOutput));\n }\n }\n\n return messages;\n}\n\nasync function toResponsesChatItem(item: ChatItem) {\n if (item.type === 'message') {\n const listContent: Record<string, any>[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n let textContent = '';\n\n for (const content of item.content) {\n if (typeof content === 'string') {\n if (textContent) textContent += '\\n';\n textContent += content;\n } else if (content.type === 'image_content') {\n listContent.push(await toResponsesImageContent(content));\n } else {\n throw new Error(`Unsupported content type: ${content.type}`);\n }\n }\n\n const content =\n listContent.length == 0\n ? textContent\n : textContent.length == 0\n ? listContent\n : [...listContent, { type: 'input_text', text: textContent }];\n\n return { role: item.role, content };\n } else if (item.type === 'function_call') {\n return {\n type: 'function_call',\n call_id: item.callId,\n name: item.name,\n arguments: item.args,\n };\n } else if (item.type === 'function_call_output') {\n return {\n type: 'function_call_output',\n call_id: item.callId,\n output: item.output,\n };\n }\n\n throw new Error(`Unsupported item type: ${item['type']}`);\n}\n"],"mappings":"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","names":["content"]}
@@ -554,4 +554,330 @@ import_vitest.vi.mock("../utils.js", () => ({
554
554
  ]);
555
555
  });
556
556
  });
557
+ (0, import_vitest.describe)("toResponsesChatCtx", () => {
558
+ const serializeImageMock = import_vitest.vi.mocked(import_utils.serializeImage);
559
+ (0, import_log.initializeLogger)({ level: "silent", pretty: false });
560
+ (0, import_vitest.beforeEach)(async () => {
561
+ import_vitest.vi.clearAllMocks();
562
+ });
563
+ (0, import_vitest.it)("should convert simple text messages", async () => {
564
+ const ctx = import_chat_context.ChatContext.empty();
565
+ ctx.addMessage({ role: "user", content: "Hello" });
566
+ ctx.addMessage({ role: "assistant", content: "Hi there!" });
567
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
568
+ (0, import_vitest.expect)(result).toHaveLength(2);
569
+ (0, import_vitest.expect)(result[0]).toEqual({ role: "user", content: "Hello" });
570
+ (0, import_vitest.expect)(result[1]).toEqual({ role: "assistant", content: "Hi there!" });
571
+ });
572
+ (0, import_vitest.it)("should handle system messages", async () => {
573
+ const ctx = import_chat_context.ChatContext.empty();
574
+ ctx.addMessage({ role: "system", content: "You are a helpful assistant" });
575
+ ctx.addMessage({ role: "user", content: "Hello" });
576
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
577
+ (0, import_vitest.expect)(result).toHaveLength(2);
578
+ (0, import_vitest.expect)(result[0]).toEqual({ role: "system", content: "You are a helpful assistant" });
579
+ (0, import_vitest.expect)(result[1]).toEqual({ role: "user", content: "Hello" });
580
+ });
581
+ (0, import_vitest.it)("should handle multi-line text content", async () => {
582
+ const ctx = import_chat_context.ChatContext.empty();
583
+ ctx.addMessage({ role: "user", content: ["Line 1", "Line 2", "Line 3"] });
584
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
585
+ (0, import_vitest.expect)(result).toHaveLength(1);
586
+ (0, import_vitest.expect)(result[0]).toEqual({ role: "user", content: "Line 1\nLine 2\nLine 3" });
587
+ });
588
+ (0, import_vitest.it)("should convert images to input_image format with external URL", async () => {
589
+ serializeImageMock.mockResolvedValue({
590
+ inferenceDetail: "high",
591
+ externalUrl: "https://example.com/image.jpg"
592
+ });
593
+ const ctx = import_chat_context.ChatContext.empty();
594
+ ctx.addMessage({
595
+ role: "user",
596
+ content: [
597
+ {
598
+ id: "img1",
599
+ type: "image_content",
600
+ image: "https://example.com/image.jpg",
601
+ inferenceDetail: "high",
602
+ _cache: {}
603
+ }
604
+ ]
605
+ });
606
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
607
+ (0, import_vitest.expect)(result).toEqual([
608
+ {
609
+ role: "user",
610
+ content: [
611
+ {
612
+ type: "input_image",
613
+ image_url: "https://example.com/image.jpg",
614
+ detail: "high"
615
+ }
616
+ ]
617
+ }
618
+ ]);
619
+ });
620
+ (0, import_vitest.it)("should convert images to input_image format with base64 data", async () => {
621
+ serializeImageMock.mockResolvedValue({
622
+ inferenceDetail: "auto",
623
+ mimeType: "image/png",
624
+ base64Data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
625
+ });
626
+ const ctx = import_chat_context.ChatContext.empty();
627
+ ctx.addMessage({
628
+ role: "user",
629
+ content: [
630
+ {
631
+ id: "img1",
632
+ type: "image_content",
633
+ image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB",
634
+ inferenceDetail: "auto",
635
+ _cache: {}
636
+ }
637
+ ]
638
+ });
639
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
640
+ (0, import_vitest.expect)(result).toEqual([
641
+ {
642
+ role: "user",
643
+ content: [
644
+ {
645
+ type: "input_image",
646
+ image_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB",
647
+ detail: "auto"
648
+ }
649
+ ]
650
+ }
651
+ ]);
652
+ });
653
+ (0, import_vitest.it)("should handle mixed content with text and image using input_text", async () => {
654
+ serializeImageMock.mockResolvedValue({
655
+ inferenceDetail: "high",
656
+ externalUrl: "https://example.com/image.jpg"
657
+ });
658
+ const ctx = import_chat_context.ChatContext.empty();
659
+ ctx.addMessage({
660
+ role: "user",
661
+ content: [
662
+ "Check this out:",
663
+ {
664
+ id: "img1",
665
+ type: "image_content",
666
+ image: "https://example.com/image.jpg",
667
+ inferenceDetail: "high",
668
+ _cache: {}
669
+ }
670
+ ]
671
+ });
672
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
673
+ (0, import_vitest.expect)(result).toEqual([
674
+ {
675
+ role: "user",
676
+ content: [
677
+ {
678
+ type: "input_image",
679
+ image_url: "https://example.com/image.jpg",
680
+ detail: "high"
681
+ },
682
+ { type: "input_text", text: "Check this out:" }
683
+ ]
684
+ }
685
+ ]);
686
+ });
687
+ (0, import_vitest.it)("should handle tool calls as top-level function_call items", async () => {
688
+ const ctx = import_chat_context.ChatContext.empty();
689
+ const msg = ctx.addMessage({ role: "assistant", content: "Let me help you." });
690
+ const toolCall = import_chat_context.FunctionCall.create({
691
+ id: msg.id + "/tool_1",
692
+ callId: "call_123",
693
+ name: "get_weather",
694
+ args: '{"location": "Paris"}'
695
+ });
696
+ const toolOutput = import_chat_context.FunctionCallOutput.create({
697
+ callId: "call_123",
698
+ output: '{"temperature": 20}',
699
+ isError: false
700
+ });
701
+ ctx.insert([toolCall, toolOutput]);
702
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
703
+ (0, import_vitest.expect)(result).toEqual([
704
+ { role: "assistant", content: "Let me help you." },
705
+ {
706
+ type: "function_call",
707
+ call_id: "call_123",
708
+ name: "get_weather",
709
+ arguments: '{"location": "Paris"}'
710
+ },
711
+ {
712
+ type: "function_call_output",
713
+ call_id: "call_123",
714
+ output: '{"temperature": 20}'
715
+ }
716
+ ]);
717
+ });
718
+ (0, import_vitest.it)("should handle tool calls without an accompanying message", async () => {
719
+ const ctx = import_chat_context.ChatContext.empty();
720
+ const toolCall = new import_chat_context.FunctionCall({
721
+ id: "func_1",
722
+ callId: "call_456",
723
+ name: "calculate",
724
+ args: '{"a": 5, "b": 3}'
725
+ });
726
+ const toolOutput = new import_chat_context.FunctionCallOutput({
727
+ callId: "call_456",
728
+ output: '{"result": 8}',
729
+ isError: false
730
+ });
731
+ ctx.insert([toolCall, toolOutput]);
732
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
733
+ (0, import_vitest.expect)(result).toEqual([
734
+ {
735
+ type: "function_call",
736
+ call_id: "call_456",
737
+ name: "calculate",
738
+ arguments: '{"a": 5, "b": 3}'
739
+ },
740
+ {
741
+ type: "function_call_output",
742
+ call_id: "call_456",
743
+ output: '{"result": 8}'
744
+ }
745
+ ]);
746
+ });
747
+ (0, import_vitest.it)("should handle multiple tool calls as separate function_call items", async () => {
748
+ const ctx = import_chat_context.ChatContext.empty();
749
+ const msg = ctx.addMessage({ role: "assistant", content: "I'll check both." });
750
+ const toolCall1 = new import_chat_context.FunctionCall({
751
+ id: msg.id + "/tool_1",
752
+ callId: "call_1",
753
+ name: "get_weather",
754
+ args: '{"location": "NYC"}'
755
+ });
756
+ const toolCall2 = new import_chat_context.FunctionCall({
757
+ id: msg.id + "/tool_2",
758
+ callId: "call_2",
759
+ name: "get_weather",
760
+ args: '{"location": "LA"}'
761
+ });
762
+ const toolOutput1 = new import_chat_context.FunctionCallOutput({
763
+ callId: "call_1",
764
+ output: '{"temperature": 65}',
765
+ isError: false
766
+ });
767
+ const toolOutput2 = new import_chat_context.FunctionCallOutput({
768
+ callId: "call_2",
769
+ output: '{"temperature": 78}',
770
+ isError: false
771
+ });
772
+ ctx.insert([toolCall1, toolCall2, toolOutput1, toolOutput2]);
773
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
774
+ (0, import_vitest.expect)(result).toEqual([
775
+ { role: "assistant", content: "I'll check both." },
776
+ {
777
+ type: "function_call",
778
+ call_id: "call_1",
779
+ name: "get_weather",
780
+ arguments: '{"location": "NYC"}'
781
+ },
782
+ {
783
+ type: "function_call",
784
+ call_id: "call_2",
785
+ name: "get_weather",
786
+ arguments: '{"location": "LA"}'
787
+ },
788
+ {
789
+ type: "function_call_output",
790
+ call_id: "call_1",
791
+ output: '{"temperature": 65}'
792
+ },
793
+ {
794
+ type: "function_call_output",
795
+ call_id: "call_2",
796
+ output: '{"temperature": 78}'
797
+ }
798
+ ]);
799
+ });
800
+ (0, import_vitest.it)("should skip empty groups", async () => {
801
+ const ctx = import_chat_context.ChatContext.empty();
802
+ ctx.addMessage({ role: "user", content: "Hello", createdAt: 1e3 });
803
+ const orphanOutput = new import_chat_context.FunctionCallOutput({
804
+ callId: "orphan_call",
805
+ output: "This should be ignored",
806
+ isError: false,
807
+ createdAt: 2e3
808
+ });
809
+ ctx.insert(orphanOutput);
810
+ ctx.addMessage({ role: "assistant", content: "Hi!", createdAt: 3e3 });
811
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
812
+ (0, import_vitest.expect)(result).toHaveLength(2);
813
+ (0, import_vitest.expect)(result).toContainEqual({ role: "user", content: "Hello" });
814
+ (0, import_vitest.expect)(result).toContainEqual({ role: "assistant", content: "Hi!" });
815
+ });
816
+ (0, import_vitest.it)("should filter out agent handoff items", async () => {
817
+ const ctx = import_chat_context.ChatContext.empty();
818
+ ctx.addMessage({ role: "user", content: "Hello" });
819
+ ctx.insert(new import_chat_context.AgentHandoffItem({ oldAgentId: "agent_1", newAgentId: "agent_2" }));
820
+ ctx.addMessage({ role: "assistant", content: "Hi there!" });
821
+ const result = await (0, import_openai.toResponsesChatCtx)(ctx);
822
+ (0, import_vitest.expect)(result).toEqual([
823
+ { role: "user", content: "Hello" },
824
+ { role: "assistant", content: "Hi there!" }
825
+ ]);
826
+ });
827
+ (0, import_vitest.it)("should cache serialized images", async () => {
828
+ serializeImageMock.mockResolvedValue({
829
+ inferenceDetail: "high",
830
+ mimeType: "image/png",
831
+ base64Data: "cached-data"
832
+ });
833
+ const imageContent = {
834
+ id: "img1",
835
+ type: "image_content",
836
+ image: "https://example.com/image.jpg",
837
+ inferenceDetail: "high",
838
+ _cache: {}
839
+ };
840
+ const ctx = import_chat_context.ChatContext.empty();
841
+ ctx.addMessage({ role: "user", content: [imageContent] });
842
+ await (0, import_openai.toResponsesChatCtx)(ctx);
843
+ await (0, import_openai.toResponsesChatCtx)(ctx);
844
+ (0, import_vitest.expect)(serializeImageMock).toHaveBeenCalledTimes(1);
845
+ (0, import_vitest.expect)(imageContent._cache).toHaveProperty("serialized_image");
846
+ });
847
+ (0, import_vitest.it)("should throw error for unsupported content type", async () => {
848
+ const ctx = import_chat_context.ChatContext.empty();
849
+ ctx.addMessage({
850
+ role: "user",
851
+ content: [
852
+ {
853
+ type: "audio_content",
854
+ frame: []
855
+ }
856
+ ]
857
+ });
858
+ await (0, import_vitest.expect)((0, import_openai.toResponsesChatCtx)(ctx)).rejects.toThrow(
859
+ "Unsupported content type: audio_content"
860
+ );
861
+ });
862
+ (0, import_vitest.it)("should throw error when serialized image has no data", async () => {
863
+ serializeImageMock.mockResolvedValue({
864
+ inferenceDetail: "high"
865
+ // No base64Data or externalUrl
866
+ });
867
+ const ctx = import_chat_context.ChatContext.empty();
868
+ ctx.addMessage({
869
+ role: "user",
870
+ content: [
871
+ {
872
+ id: "img1",
873
+ type: "image_content",
874
+ image: "invalid-image",
875
+ inferenceDetail: "high",
876
+ _cache: {}
877
+ }
878
+ ]
879
+ });
880
+ await (0, import_vitest.expect)((0, import_openai.toResponsesChatCtx)(ctx)).rejects.toThrow("Serialized image has no data bytes");
881
+ });
882
+ });
557
883
  //# sourceMappingURL=openai.test.cjs.map