@livekit/agents 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exceptions.cjs.map +1 -1
- package/dist/_exceptions.d.ts.map +1 -1
- package/dist/_exceptions.js.map +1 -1
- package/dist/audio.cjs +10 -0
- package/dist/audio.cjs.map +1 -1
- package/dist/audio.d.cts +1 -1
- package/dist/audio.d.ts +1 -1
- package/dist/audio.d.ts.map +1 -1
- package/dist/audio.js +10 -0
- package/dist/audio.js.map +1 -1
- package/dist/beta/workflows/task_group.cjs +7 -4
- package/dist/beta/workflows/task_group.cjs.map +1 -1
- package/dist/beta/workflows/task_group.d.ts.map +1 -1
- package/dist/beta/workflows/task_group.js +7 -4
- package/dist/beta/workflows/task_group.js.map +1 -1
- package/dist/inference/api_protos.d.cts +26 -26
- package/dist/inference/api_protos.d.ts +26 -26
- package/dist/inference/interruption/http_transport.cjs.map +1 -1
- package/dist/inference/interruption/http_transport.d.cts +3 -1
- package/dist/inference/interruption/http_transport.d.ts +3 -1
- package/dist/inference/interruption/http_transport.d.ts.map +1 -1
- package/dist/inference/interruption/http_transport.js.map +1 -1
- package/dist/inference/interruption/ws_transport.cjs +37 -32
- package/dist/inference/interruption/ws_transport.cjs.map +1 -1
- package/dist/inference/interruption/ws_transport.d.ts.map +1 -1
- package/dist/inference/interruption/ws_transport.js +37 -32
- package/dist/inference/interruption/ws_transport.js.map +1 -1
- package/dist/inference/tts.cjs +14 -1
- package/dist/inference/tts.cjs.map +1 -1
- package/dist/inference/tts.d.cts +42 -4
- package/dist/inference/tts.d.ts +42 -4
- package/dist/inference/tts.d.ts.map +1 -1
- package/dist/inference/tts.js +24 -3
- package/dist/inference/tts.js.map +1 -1
- package/dist/inference/tts.test.cjs +72 -0
- package/dist/inference/tts.test.cjs.map +1 -1
- package/dist/inference/tts.test.js +72 -0
- package/dist/inference/tts.test.js.map +1 -1
- package/dist/ipc/job_proc_lazy_main.cjs +7 -2
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +7 -2
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/ipc/supervised_proc.cjs +4 -1
- package/dist/ipc/supervised_proc.cjs.map +1 -1
- package/dist/ipc/supervised_proc.d.ts.map +1 -1
- package/dist/ipc/supervised_proc.js +4 -1
- package/dist/ipc/supervised_proc.js.map +1 -1
- package/dist/ipc/supervised_proc.test.cjs +82 -0
- package/dist/ipc/supervised_proc.test.cjs.map +1 -1
- package/dist/ipc/supervised_proc.test.js +82 -0
- package/dist/ipc/supervised_proc.test.js.map +1 -1
- package/dist/job.cjs +2 -1
- package/dist/job.cjs.map +1 -1
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +2 -1
- package/dist/job.js.map +1 -1
- package/dist/llm/chat_context.cjs +102 -31
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +102 -31
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +123 -5
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +123 -5
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/fallback_adapter.cjs +2 -0
- package/dist/llm/fallback_adapter.cjs.map +1 -1
- package/dist/llm/fallback_adapter.d.ts.map +1 -1
- package/dist/llm/fallback_adapter.js +2 -0
- package/dist/llm/fallback_adapter.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +2 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/utils.cjs +89 -0
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +8 -0
- package/dist/llm/utils.d.ts +8 -0
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +88 -0
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/utils.test.cjs +90 -0
- package/dist/llm/utils.test.cjs.map +1 -1
- package/dist/llm/utils.test.js +98 -2
- package/dist/llm/utils.test.js.map +1 -1
- package/dist/stt/stt.cjs +8 -0
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.cts +8 -0
- package/dist/stt/stt.d.ts +8 -0
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +8 -0
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/fallback_adapter.cjs +6 -0
- package/dist/tts/fallback_adapter.cjs.map +1 -1
- package/dist/tts/fallback_adapter.d.ts.map +1 -1
- package/dist/tts/fallback_adapter.js +6 -0
- package/dist/tts/fallback_adapter.js.map +1 -1
- package/dist/typed_promise.cjs +48 -0
- package/dist/typed_promise.cjs.map +1 -0
- package/dist/typed_promise.d.cts +24 -0
- package/dist/typed_promise.d.ts +24 -0
- package/dist/typed_promise.d.ts.map +1 -0
- package/dist/typed_promise.js +28 -0
- package/dist/typed_promise.js.map +1 -0
- package/dist/utils.cjs +30 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +18 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +27 -2
- package/dist/utils.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/agent_activity.cjs +10 -0
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +11 -0
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +1 -1
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +4 -2
- package/dist/voice/agent_session.d.ts +4 -2
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +1 -1
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/events.cjs +11 -0
- package/dist/voice/events.cjs.map +1 -1
- package/dist/voice/events.d.cts +12 -1
- package/dist/voice/events.d.ts +12 -1
- package/dist/voice/events.d.ts.map +1 -1
- package/dist/voice/events.js +10 -0
- package/dist/voice/events.js.map +1 -1
- package/dist/voice/generation.cjs +23 -4
- package/dist/voice/generation.cjs.map +1 -1
- package/dist/voice/generation.d.ts.map +1 -1
- package/dist/voice/generation.js +32 -5
- package/dist/voice/generation.js.map +1 -1
- package/dist/voice/generation_tts_timeout.test.cjs +85 -0
- package/dist/voice/generation_tts_timeout.test.cjs.map +1 -0
- package/dist/voice/generation_tts_timeout.test.js +84 -0
- package/dist/voice/generation_tts_timeout.test.js.map +1 -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 +3 -1
- package/dist/voice/index.js.map +1 -1
- package/dist/voice/recorder_io/recorder_io.cjs +1 -2
- 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 +2 -3
- package/dist/voice/recorder_io/recorder_io.js.map +1 -1
- package/dist/voice/report.cjs +1 -1
- package/dist/voice/report.cjs.map +1 -1
- package/dist/voice/report.js +1 -1
- package/dist/voice/report.js.map +1 -1
- package/dist/voice/report.test.cjs +70 -0
- package/dist/voice/report.test.cjs.map +1 -1
- package/dist/voice/report.test.js +70 -0
- package/dist/voice/report.test.js.map +1 -1
- package/dist/voice/room_io/room_io.cjs +5 -1
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +5 -1
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/dist/voice/room_io/room_io.test.cjs +18 -0
- package/dist/voice/room_io/room_io.test.cjs.map +1 -0
- package/dist/voice/room_io/room_io.test.js +17 -0
- package/dist/voice/room_io/room_io.test.js.map +1 -0
- package/package.json +4 -2
- package/src/_exceptions.ts +5 -0
- package/src/audio.ts +12 -1
- package/src/beta/workflows/task_group.ts +14 -5
- package/src/inference/interruption/http_transport.ts +2 -1
- package/src/inference/interruption/ws_transport.ts +44 -34
- package/src/inference/tts.test.ts +87 -0
- package/src/inference/tts.ts +71 -9
- package/src/ipc/job_proc_lazy_main.ts +7 -2
- package/src/ipc/supervised_proc.test.ts +96 -0
- package/src/ipc/supervised_proc.ts +8 -1
- package/src/job.ts +1 -0
- package/src/llm/chat_context.test.ts +137 -5
- package/src/llm/chat_context.ts +119 -38
- package/src/llm/fallback_adapter.ts +5 -2
- package/src/llm/index.ts +2 -0
- package/src/llm/utils.test.ts +103 -2
- package/src/llm/utils.ts +128 -0
- package/src/stt/stt.ts +9 -1
- package/src/tts/fallback_adapter.ts +9 -2
- package/src/typed_promise.ts +67 -0
- package/src/utils.ts +45 -2
- package/src/voice/agent_activity.ts +11 -0
- package/src/voice/agent_session.ts +13 -7
- package/src/voice/events.ts +21 -0
- package/src/voice/generation.ts +35 -8
- package/src/voice/generation_tts_timeout.test.ts +112 -0
- package/src/voice/index.ts +6 -1
- package/src/voice/recorder_io/recorder_io.ts +2 -7
- package/src/voice/report.test.ts +78 -0
- package/src/voice/report.ts +1 -1
- package/src/voice/room_io/room_io.test.ts +38 -0
- package/src/voice/room_io/room_io.ts +7 -2
package/dist/llm/utils.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(utils_exports, {
|
|
|
31
31
|
computeChatCtxDiff: () => computeChatCtxDiff,
|
|
32
32
|
createToolOptions: () => createToolOptions,
|
|
33
33
|
executeToolCall: () => executeToolCall,
|
|
34
|
+
formatChatHistory: () => formatChatHistory,
|
|
34
35
|
oaiBuildFunctionInfo: () => oaiBuildFunctionInfo,
|
|
35
36
|
oaiParams: () => oaiParams,
|
|
36
37
|
serializeImage: () => serializeImage,
|
|
@@ -198,6 +199,93 @@ async function executeToolCall(toolCall, toolCtx) {
|
|
|
198
199
|
});
|
|
199
200
|
}
|
|
200
201
|
}
|
|
202
|
+
function formatChatHistory(chatCtx, options = {}) {
|
|
203
|
+
const { includeIds = false, includeTimestamps = false } = options;
|
|
204
|
+
if (chatCtx.items.length === 0) {
|
|
205
|
+
return "Chat history (0 items)";
|
|
206
|
+
}
|
|
207
|
+
const formattedItems = chatCtx.items.map(
|
|
208
|
+
(item, index) => formatChatHistoryItem(item, index, {
|
|
209
|
+
includeIds,
|
|
210
|
+
includeTimestamps
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
return [
|
|
214
|
+
`Chat history (${chatCtx.items.length} items)`,
|
|
215
|
+
...formattedItems.flatMap((item) => ["", item])
|
|
216
|
+
].join("\n");
|
|
217
|
+
}
|
|
218
|
+
function formatChatHistoryItem(item, index, options) {
|
|
219
|
+
const headerParts = [`[${index}]`];
|
|
220
|
+
if (item.type === "message") {
|
|
221
|
+
headerParts.push("message", item.role);
|
|
222
|
+
} else if (item.type === "function_call") {
|
|
223
|
+
headerParts.push("function_call", item.name, `call_id=${item.callId}`);
|
|
224
|
+
} else if (item.type === "function_call_output") {
|
|
225
|
+
headerParts.push("function_call_output", item.name || "(unnamed)", `call_id=${item.callId}`);
|
|
226
|
+
if (item.isError) {
|
|
227
|
+
headerParts.push("error=true");
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
headerParts.push("agent_handoff");
|
|
231
|
+
}
|
|
232
|
+
if (options.includeIds) {
|
|
233
|
+
headerParts.push(`id=${item.id}`);
|
|
234
|
+
}
|
|
235
|
+
if (options.includeTimestamps) {
|
|
236
|
+
headerParts.push(`created_at=${item.createdAt.toFixed(3)}`);
|
|
237
|
+
}
|
|
238
|
+
const body = formatChatHistoryItemBody(item);
|
|
239
|
+
if (!body) {
|
|
240
|
+
return headerParts.join(" ");
|
|
241
|
+
}
|
|
242
|
+
return `${headerParts.join(" ")}
|
|
243
|
+
${indentBlock(body, " ")}`;
|
|
244
|
+
}
|
|
245
|
+
function formatChatHistoryItemBody(item) {
|
|
246
|
+
if (item.type === "message") {
|
|
247
|
+
const content = item.content.map((part) => formatMessageContentPart(part)).join("\n");
|
|
248
|
+
return content.trim() ? content : "(empty)";
|
|
249
|
+
}
|
|
250
|
+
if (item.type === "function_call") {
|
|
251
|
+
return prettyJsonText(item.args);
|
|
252
|
+
}
|
|
253
|
+
if (item.type === "function_call_output") {
|
|
254
|
+
return prettyJsonText(item.output);
|
|
255
|
+
}
|
|
256
|
+
return `${item.oldAgentId ?? "(none)"} -> ${item.newAgentId}`;
|
|
257
|
+
}
|
|
258
|
+
function formatMessageContentPart(part) {
|
|
259
|
+
if (typeof part === "string") {
|
|
260
|
+
return part;
|
|
261
|
+
}
|
|
262
|
+
if (part.type === "image_content") {
|
|
263
|
+
if (typeof part.image === "string") {
|
|
264
|
+
return `[image url=${truncateText(part.image, 120)}]`;
|
|
265
|
+
}
|
|
266
|
+
return `[image frame=${part.image.width}x${part.image.height}]`;
|
|
267
|
+
}
|
|
268
|
+
if (part.transcript) {
|
|
269
|
+
return `[audio transcript=${JSON.stringify(truncateText(part.transcript, 120))}]`;
|
|
270
|
+
}
|
|
271
|
+
return `[audio frames=${part.frame.length}]`;
|
|
272
|
+
}
|
|
273
|
+
function prettyJsonText(text) {
|
|
274
|
+
try {
|
|
275
|
+
return JSON.stringify(JSON.parse(text), null, 2);
|
|
276
|
+
} catch {
|
|
277
|
+
return text;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function truncateText(text, maxLength) {
|
|
281
|
+
if (text.length <= maxLength) {
|
|
282
|
+
return text;
|
|
283
|
+
}
|
|
284
|
+
return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
285
|
+
}
|
|
286
|
+
function indentBlock(text, indent) {
|
|
287
|
+
return text.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
288
|
+
}
|
|
201
289
|
function computeLCS(oldIds, newIds) {
|
|
202
290
|
const n = oldIds.length;
|
|
203
291
|
const m = newIds.length;
|
|
@@ -259,6 +347,7 @@ function toJsonSchema(schema, isOpenai = true, strict = false) {
|
|
|
259
347
|
computeChatCtxDiff,
|
|
260
348
|
createToolOptions,
|
|
261
349
|
executeToolCall,
|
|
350
|
+
formatChatHistory,
|
|
262
351
|
oaiBuildFunctionInfo,
|
|
263
352
|
oaiParams,
|
|
264
353
|
serializeImage,
|
package/dist/llm/utils.cjs.map
CHANGED
|
@@ -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(\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"]}
|
|
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 ChatContent,\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\nexport interface FormatChatHistoryOptions {\n includeIds?: boolean;\n includeTimestamps?: boolean;\n}\n\n/**\n * Render a chat context into a readable multiline string for debugging and logging.\n */\nexport function formatChatHistory(\n chatCtx: ChatContext,\n options: FormatChatHistoryOptions = {},\n): string {\n const { includeIds = false, includeTimestamps = false } = options;\n\n if (chatCtx.items.length === 0) {\n return 'Chat history (0 items)';\n }\n\n const formattedItems = chatCtx.items.map((item, index) =>\n formatChatHistoryItem(item, index, {\n includeIds,\n includeTimestamps,\n }),\n );\n\n return [\n `Chat history (${chatCtx.items.length} items)`,\n ...formattedItems.flatMap((item) => ['', item]),\n ].join('\\n');\n}\n\nfunction formatChatHistoryItem(\n item: ChatItem,\n index: number,\n options: Required<FormatChatHistoryOptions>,\n): string {\n const headerParts = [`[${index}]`];\n\n if (item.type === 'message') {\n headerParts.push('message', item.role);\n } else if (item.type === 'function_call') {\n headerParts.push('function_call', item.name, `call_id=${item.callId}`);\n } else if (item.type === 'function_call_output') {\n headerParts.push('function_call_output', item.name || '(unnamed)', `call_id=${item.callId}`);\n if (item.isError) {\n headerParts.push('error=true');\n }\n } else {\n headerParts.push('agent_handoff');\n }\n\n if (options.includeIds) {\n headerParts.push(`id=${item.id}`);\n }\n\n if (options.includeTimestamps) {\n headerParts.push(`created_at=${item.createdAt.toFixed(3)}`);\n }\n\n const body = formatChatHistoryItemBody(item);\n if (!body) {\n return headerParts.join(' ');\n }\n\n return `${headerParts.join(' ')}\\n${indentBlock(body, ' ')}`;\n}\n\nfunction formatChatHistoryItemBody(item: ChatItem): string {\n if (item.type === 'message') {\n const content = item.content.map((part) => formatMessageContentPart(part)).join('\\n');\n return content.trim() ? content : '(empty)';\n }\n\n if (item.type === 'function_call') {\n return prettyJsonText(item.args);\n }\n\n if (item.type === 'function_call_output') {\n return prettyJsonText(item.output);\n }\n\n return `${item.oldAgentId ?? '(none)'} -> ${item.newAgentId}`;\n}\n\nfunction formatMessageContentPart(part: ChatContent): string {\n if (typeof part === 'string') {\n return part;\n }\n\n if (part.type === 'image_content') {\n if (typeof part.image === 'string') {\n return `[image url=${truncateText(part.image, 120)}]`;\n }\n\n return `[image frame=${part.image.width}x${part.image.height}]`;\n }\n\n if (part.transcript) {\n return `[audio transcript=${JSON.stringify(truncateText(part.transcript, 120))}]`;\n }\n\n return `[audio frames=${part.frame.length}]`;\n}\n\nfunction prettyJsonText(text: string): string {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n}\n\nfunction truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n\n return `${text.slice(0, Math.max(0, maxLength - 3))}...`;\n}\n\nfunction indentBlock(text: string, indent: string): string {\n return text\n .split('\\n')\n .map((line) => `${indent}${line}`)\n .join('\\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"]}
|
package/dist/llm/utils.d.cts
CHANGED
|
@@ -24,6 +24,14 @@ export declare const oaiParams: (schema: any, isOpenai?: boolean) => OpenAIFunct
|
|
|
24
24
|
/** @internal */
|
|
25
25
|
export declare const oaiBuildFunctionInfo: (toolCtx: ToolContext, toolCallId: string, toolName: string, rawArgs: string) => FunctionCall;
|
|
26
26
|
export declare function executeToolCall(toolCall: FunctionCall, toolCtx: ToolContext): Promise<FunctionCallOutput>;
|
|
27
|
+
export interface FormatChatHistoryOptions {
|
|
28
|
+
includeIds?: boolean;
|
|
29
|
+
includeTimestamps?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Render a chat context into a readable multiline string for debugging and logging.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatChatHistory(chatCtx: ChatContext, options?: FormatChatHistoryOptions): string;
|
|
27
35
|
interface DiffOps {
|
|
28
36
|
toRemove: string[];
|
|
29
37
|
toCreate: Array<[string | null, string]>;
|
package/dist/llm/utils.d.ts
CHANGED
|
@@ -24,6 +24,14 @@ export declare const oaiParams: (schema: any, isOpenai?: boolean) => OpenAIFunct
|
|
|
24
24
|
/** @internal */
|
|
25
25
|
export declare const oaiBuildFunctionInfo: (toolCtx: ToolContext, toolCallId: string, toolName: string, rawArgs: string) => FunctionCall;
|
|
26
26
|
export declare function executeToolCall(toolCall: FunctionCall, toolCtx: ToolContext): Promise<FunctionCallOutput>;
|
|
27
|
+
export interface FormatChatHistoryOptions {
|
|
28
|
+
includeIds?: boolean;
|
|
29
|
+
includeTimestamps?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Render a chat context into a readable multiline string for debugging and logging.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatChatHistory(chatCtx: ChatContext, options?: FormatChatHistoryOptions): string;
|
|
27
35
|
interface DiffOps {
|
|
28
36
|
toRemove: string[];
|
|
29
37
|
toCreate: Array<[string | null, string]>;
|
package/dist/llm/utils.d.ts.map
CHANGED
|
@@ -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,
|
|
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,EAGL,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;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAkBR;AA+ID,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
|
@@ -162,6 +162,93 @@ async function executeToolCall(toolCall, toolCtx) {
|
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
function formatChatHistory(chatCtx, options = {}) {
|
|
166
|
+
const { includeIds = false, includeTimestamps = false } = options;
|
|
167
|
+
if (chatCtx.items.length === 0) {
|
|
168
|
+
return "Chat history (0 items)";
|
|
169
|
+
}
|
|
170
|
+
const formattedItems = chatCtx.items.map(
|
|
171
|
+
(item, index) => formatChatHistoryItem(item, index, {
|
|
172
|
+
includeIds,
|
|
173
|
+
includeTimestamps
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
return [
|
|
177
|
+
`Chat history (${chatCtx.items.length} items)`,
|
|
178
|
+
...formattedItems.flatMap((item) => ["", item])
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
function formatChatHistoryItem(item, index, options) {
|
|
182
|
+
const headerParts = [`[${index}]`];
|
|
183
|
+
if (item.type === "message") {
|
|
184
|
+
headerParts.push("message", item.role);
|
|
185
|
+
} else if (item.type === "function_call") {
|
|
186
|
+
headerParts.push("function_call", item.name, `call_id=${item.callId}`);
|
|
187
|
+
} else if (item.type === "function_call_output") {
|
|
188
|
+
headerParts.push("function_call_output", item.name || "(unnamed)", `call_id=${item.callId}`);
|
|
189
|
+
if (item.isError) {
|
|
190
|
+
headerParts.push("error=true");
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
headerParts.push("agent_handoff");
|
|
194
|
+
}
|
|
195
|
+
if (options.includeIds) {
|
|
196
|
+
headerParts.push(`id=${item.id}`);
|
|
197
|
+
}
|
|
198
|
+
if (options.includeTimestamps) {
|
|
199
|
+
headerParts.push(`created_at=${item.createdAt.toFixed(3)}`);
|
|
200
|
+
}
|
|
201
|
+
const body = formatChatHistoryItemBody(item);
|
|
202
|
+
if (!body) {
|
|
203
|
+
return headerParts.join(" ");
|
|
204
|
+
}
|
|
205
|
+
return `${headerParts.join(" ")}
|
|
206
|
+
${indentBlock(body, " ")}`;
|
|
207
|
+
}
|
|
208
|
+
function formatChatHistoryItemBody(item) {
|
|
209
|
+
if (item.type === "message") {
|
|
210
|
+
const content = item.content.map((part) => formatMessageContentPart(part)).join("\n");
|
|
211
|
+
return content.trim() ? content : "(empty)";
|
|
212
|
+
}
|
|
213
|
+
if (item.type === "function_call") {
|
|
214
|
+
return prettyJsonText(item.args);
|
|
215
|
+
}
|
|
216
|
+
if (item.type === "function_call_output") {
|
|
217
|
+
return prettyJsonText(item.output);
|
|
218
|
+
}
|
|
219
|
+
return `${item.oldAgentId ?? "(none)"} -> ${item.newAgentId}`;
|
|
220
|
+
}
|
|
221
|
+
function formatMessageContentPart(part) {
|
|
222
|
+
if (typeof part === "string") {
|
|
223
|
+
return part;
|
|
224
|
+
}
|
|
225
|
+
if (part.type === "image_content") {
|
|
226
|
+
if (typeof part.image === "string") {
|
|
227
|
+
return `[image url=${truncateText(part.image, 120)}]`;
|
|
228
|
+
}
|
|
229
|
+
return `[image frame=${part.image.width}x${part.image.height}]`;
|
|
230
|
+
}
|
|
231
|
+
if (part.transcript) {
|
|
232
|
+
return `[audio transcript=${JSON.stringify(truncateText(part.transcript, 120))}]`;
|
|
233
|
+
}
|
|
234
|
+
return `[audio frames=${part.frame.length}]`;
|
|
235
|
+
}
|
|
236
|
+
function prettyJsonText(text) {
|
|
237
|
+
try {
|
|
238
|
+
return JSON.stringify(JSON.parse(text), null, 2);
|
|
239
|
+
} catch {
|
|
240
|
+
return text;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function truncateText(text, maxLength) {
|
|
244
|
+
if (text.length <= maxLength) {
|
|
245
|
+
return text;
|
|
246
|
+
}
|
|
247
|
+
return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
248
|
+
}
|
|
249
|
+
function indentBlock(text, indent) {
|
|
250
|
+
return text.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
251
|
+
}
|
|
165
252
|
function computeLCS(oldIds, newIds) {
|
|
166
253
|
const n = oldIds.length;
|
|
167
254
|
const m = newIds.length;
|
|
@@ -222,6 +309,7 @@ export {
|
|
|
222
309
|
computeChatCtxDiff,
|
|
223
310
|
createToolOptions,
|
|
224
311
|
executeToolCall,
|
|
312
|
+
formatChatHistory,
|
|
225
313
|
oaiBuildFunctionInfo,
|
|
226
314
|
oaiParams,
|
|
227
315
|
serializeImage,
|
package/dist/llm/utils.js.map
CHANGED
|
@@ -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(\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":["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 ChatContent,\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\nexport interface FormatChatHistoryOptions {\n includeIds?: boolean;\n includeTimestamps?: boolean;\n}\n\n/**\n * Render a chat context into a readable multiline string for debugging and logging.\n */\nexport function formatChatHistory(\n chatCtx: ChatContext,\n options: FormatChatHistoryOptions = {},\n): string {\n const { includeIds = false, includeTimestamps = false } = options;\n\n if (chatCtx.items.length === 0) {\n return 'Chat history (0 items)';\n }\n\n const formattedItems = chatCtx.items.map((item, index) =>\n formatChatHistoryItem(item, index, {\n includeIds,\n includeTimestamps,\n }),\n );\n\n return [\n `Chat history (${chatCtx.items.length} items)`,\n ...formattedItems.flatMap((item) => ['', item]),\n ].join('\\n');\n}\n\nfunction formatChatHistoryItem(\n item: ChatItem,\n index: number,\n options: Required<FormatChatHistoryOptions>,\n): string {\n const headerParts = [`[${index}]`];\n\n if (item.type === 'message') {\n headerParts.push('message', item.role);\n } else if (item.type === 'function_call') {\n headerParts.push('function_call', item.name, `call_id=${item.callId}`);\n } else if (item.type === 'function_call_output') {\n headerParts.push('function_call_output', item.name || '(unnamed)', `call_id=${item.callId}`);\n if (item.isError) {\n headerParts.push('error=true');\n }\n } else {\n headerParts.push('agent_handoff');\n }\n\n if (options.includeIds) {\n headerParts.push(`id=${item.id}`);\n }\n\n if (options.includeTimestamps) {\n headerParts.push(`created_at=${item.createdAt.toFixed(3)}`);\n }\n\n const body = formatChatHistoryItemBody(item);\n if (!body) {\n return headerParts.join(' ');\n }\n\n return `${headerParts.join(' ')}\\n${indentBlock(body, ' ')}`;\n}\n\nfunction formatChatHistoryItemBody(item: ChatItem): string {\n if (item.type === 'message') {\n const content = item.content.map((part) => formatMessageContentPart(part)).join('\\n');\n return content.trim() ? content : '(empty)';\n }\n\n if (item.type === 'function_call') {\n return prettyJsonText(item.args);\n }\n\n if (item.type === 'function_call_output') {\n return prettyJsonText(item.output);\n }\n\n return `${item.oldAgentId ?? '(none)'} -> ${item.newAgentId}`;\n}\n\nfunction formatMessageContentPart(part: ChatContent): string {\n if (typeof part === 'string') {\n return part;\n }\n\n if (part.type === 'image_content') {\n if (typeof part.image === 'string') {\n return `[image url=${truncateText(part.image, 120)}]`;\n }\n\n return `[image frame=${part.image.width}x${part.image.height}]`;\n }\n\n if (part.transcript) {\n return `[audio transcript=${JSON.stringify(truncateText(part.transcript, 120))}]`;\n }\n\n return `[audio frames=${part.frame.length}]`;\n}\n\nfunction prettyJsonText(text: string): string {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n}\n\nfunction truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n\n return `${text.slice(0, Math.max(0, maxLength - 3))}...`;\n}\n\nfunction indentBlock(text: string, indent: string): string {\n return text\n .split('\\n')\n .map((line) => `${indent}${line}`)\n .join('\\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":["i","j"]}
|
package/dist/llm/utils.test.cjs
CHANGED
|
@@ -266,6 +266,96 @@ function expectPixel(data, index, expected) {
|
|
|
266
266
|
]);
|
|
267
267
|
});
|
|
268
268
|
});
|
|
269
|
+
(0, import_vitest.describe)("formatChatHistory", () => {
|
|
270
|
+
(0, import_vitest.it)("should format mixed chat history items for logging", () => {
|
|
271
|
+
const ctx = new import_chat_context.ChatContext([
|
|
272
|
+
import_chat_context.ChatMessage.create({
|
|
273
|
+
id: "msg_system",
|
|
274
|
+
role: "system",
|
|
275
|
+
content: "You are helpful.",
|
|
276
|
+
createdAt: 1
|
|
277
|
+
}),
|
|
278
|
+
import_chat_context.ChatMessage.create({
|
|
279
|
+
id: "msg_user",
|
|
280
|
+
role: "user",
|
|
281
|
+
content: [
|
|
282
|
+
"Show me my order",
|
|
283
|
+
createImageContent("https://example.com/receipt.png"),
|
|
284
|
+
{ type: "audio_content", frame: [], transcript: "order 123" }
|
|
285
|
+
],
|
|
286
|
+
createdAt: 2
|
|
287
|
+
}),
|
|
288
|
+
import_chat_context.FunctionCall.create({
|
|
289
|
+
id: "fn_call",
|
|
290
|
+
callId: "call_1",
|
|
291
|
+
name: "lookup_order",
|
|
292
|
+
args: '{"orderId":"123"}',
|
|
293
|
+
createdAt: 3
|
|
294
|
+
}),
|
|
295
|
+
import_chat_context.FunctionCallOutput.create({
|
|
296
|
+
id: "fn_output",
|
|
297
|
+
callId: "call_1",
|
|
298
|
+
name: "lookup_order",
|
|
299
|
+
output: '{"status":"shipped","ok":true}',
|
|
300
|
+
isError: false,
|
|
301
|
+
createdAt: 4
|
|
302
|
+
}),
|
|
303
|
+
import_chat_context.AgentHandoffItem.create({
|
|
304
|
+
id: "handoff",
|
|
305
|
+
oldAgentId: "support",
|
|
306
|
+
newAgentId: "returns",
|
|
307
|
+
createdAt: 5
|
|
308
|
+
})
|
|
309
|
+
]);
|
|
310
|
+
(0, import_vitest.expect)((0, import_utils.formatChatHistory)(ctx)).toBe(
|
|
311
|
+
[
|
|
312
|
+
"Chat history (5 items)",
|
|
313
|
+
"",
|
|
314
|
+
"[0] message system",
|
|
315
|
+
" You are helpful.",
|
|
316
|
+
"",
|
|
317
|
+
"[1] message user",
|
|
318
|
+
" Show me my order",
|
|
319
|
+
" [image url=https://example.com/receipt.png]",
|
|
320
|
+
' [audio transcript="order 123"]',
|
|
321
|
+
"",
|
|
322
|
+
"[2] function_call lookup_order call_id=call_1",
|
|
323
|
+
" {",
|
|
324
|
+
' "orderId": "123"',
|
|
325
|
+
" }",
|
|
326
|
+
"",
|
|
327
|
+
"[3] function_call_output lookup_order call_id=call_1",
|
|
328
|
+
" {",
|
|
329
|
+
' "status": "shipped",',
|
|
330
|
+
' "ok": true',
|
|
331
|
+
" }",
|
|
332
|
+
"",
|
|
333
|
+
"[4] agent_handoff",
|
|
334
|
+
" support -> returns"
|
|
335
|
+
].join("\n")
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
(0, import_vitest.it)("should optionally include ids and timestamps", () => {
|
|
339
|
+
const ctx = new import_chat_context.ChatContext([
|
|
340
|
+
import_chat_context.FunctionCallOutput.create({
|
|
341
|
+
id: "fn_output",
|
|
342
|
+
callId: "call_1",
|
|
343
|
+
name: "lookup_order",
|
|
344
|
+
output: "plain text error",
|
|
345
|
+
isError: true,
|
|
346
|
+
createdAt: 123.456
|
|
347
|
+
})
|
|
348
|
+
]);
|
|
349
|
+
(0, import_vitest.expect)((0, import_utils.formatChatHistory)(ctx, { includeIds: true, includeTimestamps: true })).toBe(
|
|
350
|
+
[
|
|
351
|
+
"Chat history (1 items)",
|
|
352
|
+
"",
|
|
353
|
+
"[0] function_call_output lookup_order call_id=call_1 error=true id=fn_output created_at=123.456",
|
|
354
|
+
" plain text error"
|
|
355
|
+
].join("\n")
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
269
359
|
(0, import_vitest.describe)("serializeImage", () => {
|
|
270
360
|
let consoleWarnSpy;
|
|
271
361
|
(0, import_vitest.beforeEach)(() => {
|