@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.
- package/dist/cli.cjs +14 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -20
- package/dist/cli.js.map +1 -1
- package/dist/ipc/job_proc_lazy_main.cjs +14 -5
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +14 -5
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/llm/chat_context.cjs +19 -0
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.cts +4 -0
- package/dist/llm/chat_context.d.ts +4 -0
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +19 -0
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/provider_format/index.cjs +2 -0
- package/dist/llm/provider_format/index.cjs.map +1 -1
- package/dist/llm/provider_format/index.d.cts +1 -1
- package/dist/llm/provider_format/index.d.ts +1 -1
- package/dist/llm/provider_format/index.d.ts.map +1 -1
- package/dist/llm/provider_format/index.js +6 -1
- package/dist/llm/provider_format/index.js.map +1 -1
- package/dist/llm/provider_format/openai.cjs +82 -2
- package/dist/llm/provider_format/openai.cjs.map +1 -1
- package/dist/llm/provider_format/openai.d.cts +1 -0
- package/dist/llm/provider_format/openai.d.ts +1 -0
- package/dist/llm/provider_format/openai.d.ts.map +1 -1
- package/dist/llm/provider_format/openai.js +80 -1
- package/dist/llm/provider_format/openai.js.map +1 -1
- package/dist/llm/provider_format/openai.test.cjs +326 -0
- package/dist/llm/provider_format/openai.test.cjs.map +1 -1
- package/dist/llm/provider_format/openai.test.js +327 -1
- package/dist/llm/provider_format/openai.test.js.map +1 -1
- package/dist/llm/provider_format/utils.cjs +4 -3
- package/dist/llm/provider_format/utils.cjs.map +1 -1
- package/dist/llm/provider_format/utils.d.ts.map +1 -1
- package/dist/llm/provider_format/utils.js +4 -3
- package/dist/llm/provider_format/utils.js.map +1 -1
- package/dist/llm/realtime.cjs.map +1 -1
- package/dist/llm/realtime.d.cts +1 -0
- package/dist/llm/realtime.d.ts +1 -0
- package/dist/llm/realtime.d.ts.map +1 -1
- package/dist/llm/realtime.js.map +1 -1
- package/dist/log.cjs +5 -2
- package/dist/log.cjs.map +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +5 -2
- package/dist/log.js.map +1 -1
- package/dist/stream/deferred_stream.cjs +15 -6
- package/dist/stream/deferred_stream.cjs.map +1 -1
- package/dist/stream/deferred_stream.d.ts.map +1 -1
- package/dist/stream/deferred_stream.js +15 -6
- package/dist/stream/deferred_stream.js.map +1 -1
- package/dist/stream/index.cjs +3 -0
- package/dist/stream/index.cjs.map +1 -1
- package/dist/stream/index.d.cts +1 -0
- package/dist/stream/index.d.ts +1 -0
- package/dist/stream/index.d.ts.map +1 -1
- package/dist/stream/index.js +2 -0
- package/dist/stream/index.js.map +1 -1
- package/dist/stream/multi_input_stream.cjs +139 -0
- package/dist/stream/multi_input_stream.cjs.map +1 -0
- package/dist/stream/multi_input_stream.d.cts +55 -0
- package/dist/stream/multi_input_stream.d.ts +55 -0
- package/dist/stream/multi_input_stream.d.ts.map +1 -0
- package/dist/stream/multi_input_stream.js +115 -0
- package/dist/stream/multi_input_stream.js.map +1 -0
- package/dist/stream/multi_input_stream.test.cjs +340 -0
- package/dist/stream/multi_input_stream.test.cjs.map +1 -0
- package/dist/stream/multi_input_stream.test.js +339 -0
- package/dist/stream/multi_input_stream.test.js.map +1 -0
- package/dist/telemetry/trace_types.cjs +42 -0
- package/dist/telemetry/trace_types.cjs.map +1 -1
- package/dist/telemetry/trace_types.d.cts +14 -0
- package/dist/telemetry/trace_types.d.ts +14 -0
- package/dist/telemetry/trace_types.d.ts.map +1 -1
- package/dist/telemetry/trace_types.js +28 -0
- package/dist/telemetry/trace_types.js.map +1 -1
- package/dist/utils.cjs +44 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +8 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +44 -2
- package/dist/utils.js.map +1 -1
- package/dist/utils.test.cjs +71 -0
- package/dist/utils.test.cjs.map +1 -1
- package/dist/utils.test.js +71 -0
- package/dist/utils.test.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/voice/agent.cjs +144 -12
- package/dist/voice/agent.cjs.map +1 -1
- package/dist/voice/agent.d.cts +29 -4
- package/dist/voice/agent.d.ts +29 -4
- package/dist/voice/agent.d.ts.map +1 -1
- package/dist/voice/agent.js +140 -11
- package/dist/voice/agent.js.map +1 -1
- package/dist/voice/agent.test.cjs +120 -0
- package/dist/voice/agent.test.cjs.map +1 -1
- package/dist/voice/agent.test.js +122 -2
- package/dist/voice/agent.test.js.map +1 -1
- package/dist/voice/agent_activity.cjs +402 -292
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.cts +35 -7
- package/dist/voice/agent_activity.d.ts +35 -7
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +402 -287
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +156 -44
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +22 -9
- package/dist/voice/agent_session.d.ts +22 -9
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +156 -44
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/audio_recognition.cjs +89 -36
- package/dist/voice/audio_recognition.cjs.map +1 -1
- package/dist/voice/audio_recognition.d.cts +22 -1
- package/dist/voice/audio_recognition.d.ts +22 -1
- package/dist/voice/audio_recognition.d.ts.map +1 -1
- package/dist/voice/audio_recognition.js +93 -36
- package/dist/voice/audio_recognition.js.map +1 -1
- package/dist/voice/audio_recognition_span.test.cjs +233 -0
- package/dist/voice/audio_recognition_span.test.cjs.map +1 -0
- package/dist/voice/audio_recognition_span.test.js +232 -0
- package/dist/voice/audio_recognition_span.test.js.map +1 -0
- package/dist/voice/generation.cjs +39 -19
- package/dist/voice/generation.cjs.map +1 -1
- package/dist/voice/generation.d.ts.map +1 -1
- package/dist/voice/generation.js +44 -20
- package/dist/voice/generation.js.map +1 -1
- package/dist/voice/index.cjs +2 -0
- package/dist/voice/index.cjs.map +1 -1
- package/dist/voice/index.d.cts +1 -1
- package/dist/voice/index.d.ts +1 -1
- package/dist/voice/index.d.ts.map +1 -1
- package/dist/voice/index.js +2 -1
- package/dist/voice/index.js.map +1 -1
- package/dist/voice/io.cjs +6 -3
- package/dist/voice/io.cjs.map +1 -1
- package/dist/voice/io.d.cts +3 -2
- package/dist/voice/io.d.ts +3 -2
- package/dist/voice/io.d.ts.map +1 -1
- package/dist/voice/io.js +6 -3
- package/dist/voice/io.js.map +1 -1
- package/dist/voice/recorder_io/recorder_io.cjs +3 -1
- package/dist/voice/recorder_io/recorder_io.cjs.map +1 -1
- package/dist/voice/recorder_io/recorder_io.d.ts.map +1 -1
- package/dist/voice/recorder_io/recorder_io.js +3 -1
- package/dist/voice/recorder_io/recorder_io.js.map +1 -1
- package/dist/voice/room_io/_input.cjs +17 -17
- package/dist/voice/room_io/_input.cjs.map +1 -1
- package/dist/voice/room_io/_input.d.cts +2 -2
- package/dist/voice/room_io/_input.d.ts +2 -2
- package/dist/voice/room_io/_input.d.ts.map +1 -1
- package/dist/voice/room_io/_input.js +7 -6
- package/dist/voice/room_io/_input.js.map +1 -1
- package/dist/voice/room_io/room_io.cjs +9 -0
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.cts +3 -1
- package/dist/voice/room_io/room_io.d.ts +3 -1
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +9 -0
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/dist/voice/speech_handle.cjs +7 -1
- package/dist/voice/speech_handle.cjs.map +1 -1
- package/dist/voice/speech_handle.d.cts +2 -0
- package/dist/voice/speech_handle.d.ts +2 -0
- package/dist/voice/speech_handle.d.ts.map +1 -1
- package/dist/voice/speech_handle.js +8 -2
- package/dist/voice/speech_handle.js.map +1 -1
- package/dist/voice/testing/run_result.cjs +66 -15
- package/dist/voice/testing/run_result.cjs.map +1 -1
- package/dist/voice/testing/run_result.d.cts +14 -3
- package/dist/voice/testing/run_result.d.ts +14 -3
- package/dist/voice/testing/run_result.d.ts.map +1 -1
- package/dist/voice/testing/run_result.js +66 -15
- package/dist/voice/testing/run_result.js.map +1 -1
- package/dist/voice/utils.cjs +47 -0
- package/dist/voice/utils.cjs.map +1 -0
- package/dist/voice/utils.d.cts +4 -0
- package/dist/voice/utils.d.ts +4 -0
- package/dist/voice/utils.d.ts.map +1 -0
- package/dist/voice/utils.js +23 -0
- package/dist/voice/utils.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +20 -33
- package/src/ipc/job_proc_lazy_main.ts +16 -5
- package/src/llm/chat_context.ts +35 -0
- package/src/llm/provider_format/index.ts +7 -2
- package/src/llm/provider_format/openai.test.ts +385 -1
- package/src/llm/provider_format/openai.ts +103 -0
- package/src/llm/provider_format/utils.ts +6 -4
- package/src/llm/realtime.ts +1 -0
- package/src/log.ts +5 -2
- package/src/stream/deferred_stream.ts +17 -6
- package/src/stream/index.ts +1 -0
- package/src/stream/multi_input_stream.test.ts +540 -0
- package/src/stream/multi_input_stream.ts +172 -0
- package/src/telemetry/trace_types.ts +18 -0
- package/src/utils.test.ts +87 -0
- package/src/utils.ts +52 -2
- package/src/version.ts +1 -1
- package/src/voice/agent.test.ts +140 -2
- package/src/voice/agent.ts +189 -10
- package/src/voice/agent_activity.ts +449 -286
- package/src/voice/agent_session.ts +195 -51
- package/src/voice/audio_recognition.ts +118 -38
- package/src/voice/audio_recognition_span.test.ts +261 -0
- package/src/voice/generation.ts +52 -23
- package/src/voice/index.ts +1 -1
- package/src/voice/io.ts +7 -4
- package/src/voice/recorder_io/recorder_io.ts +2 -1
- package/src/voice/room_io/_input.ts +11 -7
- package/src/voice/room_io/room_io.ts +12 -0
- package/src/voice/speech_handle.ts +9 -2
- package/src/voice/testing/run_result.ts +81 -23
- 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
|