assistant-stream 0.3.30 → 0.3.31
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/core/converters/toGenericMessages.js +3 -3
- package/dist/core/converters/toGenericMessages.js.map +1 -1
- package/dist/core/serialization/data-stream/DataStream.js +27 -1
- package/dist/core/serialization/data-stream/DataStream.js.map +1 -1
- package/package.json +1 -1
- package/src/core/converters/toGenericMessages.test.ts +17 -0
- package/src/core/converters/toGenericMessages.ts +3 -3
- package/src/core/serialization/data-stream/DataStream.test.ts +109 -0
- package/src/core/serialization/data-stream/DataStream.ts +29 -1
|
@@ -15,9 +15,9 @@ const IMAGE_MEDIA_TYPES = {
|
|
|
15
15
|
heif: "image/heif"
|
|
16
16
|
};
|
|
17
17
|
function inferImageMediaType(url) {
|
|
18
|
-
if (
|
|
19
|
-
const match = url.match(/^data:([^;,]+)/);
|
|
20
|
-
if (match?.[1]) return match[1];
|
|
18
|
+
if (/^data:/i.test(url)) {
|
|
19
|
+
const match = url.match(/^data:([^;,]+)/i);
|
|
20
|
+
if (match?.[1]) return match[1].toLowerCase();
|
|
21
21
|
}
|
|
22
22
|
const [pathWithoutParams = ""] = url.split(/[?#]/);
|
|
23
23
|
const ext = pathWithoutParams.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toGenericMessages.js","names":[],"sources":["../../../src/core/converters/toGenericMessages.ts"],"sourcesContent":["/**\n * Generic message types for framework-agnostic LLM message interchange.\n * These types represent a common format that can be converted to/from\n * various LLM provider formats (AI SDK, LangChain, etc.).\n */\n\nexport type GenericTextPart = {\n type: \"text\";\n text: string;\n};\n\nexport type GenericFilePart = {\n type: \"file\";\n data: string | URL;\n mediaType: string;\n};\n\nexport type GenericToolCallPart = {\n type: \"tool-call\";\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n};\n\nexport type GenericToolResultPart = {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n result: unknown;\n isError?: boolean;\n};\n\nexport type GenericSystemMessage = {\n role: \"system\";\n content: string;\n};\n\nexport type GenericUserMessage = {\n role: \"user\";\n content: (GenericTextPart | GenericFilePart)[];\n};\n\nexport type GenericAssistantMessage = {\n role: \"assistant\";\n content: (GenericTextPart | GenericToolCallPart)[];\n};\n\nexport type GenericToolMessage = {\n role: \"tool\";\n content: GenericToolResultPart[];\n};\n\nexport type GenericMessage =\n | GenericSystemMessage\n | GenericUserMessage\n | GenericAssistantMessage\n | GenericToolMessage;\n\ntype MessagePartLike = {\n type: string;\n text?: string;\n image?: string;\n data?: string;\n mimeType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n isError?: boolean;\n};\n\ntype AttachmentLike = {\n content: readonly MessagePartLike[];\n};\n\ntype ThreadMessageLike = {\n role: \"system\" | \"user\" | \"assistant\";\n content: readonly MessagePartLike[];\n attachments?: readonly AttachmentLike[];\n};\n\nconst IMAGE_MEDIA_TYPES: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n avif: \"image/avif\",\n bmp: \"image/bmp\",\n ico: \"image/x-icon\",\n tiff: \"image/tiff\",\n tif: \"image/tiff\",\n heic: \"image/heic\",\n heif: \"image/heif\",\n};\n\nfunction inferImageMediaType(url: string): string {\n // Handle data URLs: data:[<mediatype>][;base64],<data>\n if (
|
|
1
|
+
{"version":3,"file":"toGenericMessages.js","names":[],"sources":["../../../src/core/converters/toGenericMessages.ts"],"sourcesContent":["/**\n * Generic message types for framework-agnostic LLM message interchange.\n * These types represent a common format that can be converted to/from\n * various LLM provider formats (AI SDK, LangChain, etc.).\n */\n\nexport type GenericTextPart = {\n type: \"text\";\n text: string;\n};\n\nexport type GenericFilePart = {\n type: \"file\";\n data: string | URL;\n mediaType: string;\n};\n\nexport type GenericToolCallPart = {\n type: \"tool-call\";\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n};\n\nexport type GenericToolResultPart = {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n result: unknown;\n isError?: boolean;\n};\n\nexport type GenericSystemMessage = {\n role: \"system\";\n content: string;\n};\n\nexport type GenericUserMessage = {\n role: \"user\";\n content: (GenericTextPart | GenericFilePart)[];\n};\n\nexport type GenericAssistantMessage = {\n role: \"assistant\";\n content: (GenericTextPart | GenericToolCallPart)[];\n};\n\nexport type GenericToolMessage = {\n role: \"tool\";\n content: GenericToolResultPart[];\n};\n\nexport type GenericMessage =\n | GenericSystemMessage\n | GenericUserMessage\n | GenericAssistantMessage\n | GenericToolMessage;\n\ntype MessagePartLike = {\n type: string;\n text?: string;\n image?: string;\n data?: string;\n mimeType?: string;\n toolCallId?: string;\n toolName?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n isError?: boolean;\n};\n\ntype AttachmentLike = {\n content: readonly MessagePartLike[];\n};\n\ntype ThreadMessageLike = {\n role: \"system\" | \"user\" | \"assistant\";\n content: readonly MessagePartLike[];\n attachments?: readonly AttachmentLike[];\n};\n\nconst IMAGE_MEDIA_TYPES: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n avif: \"image/avif\",\n bmp: \"image/bmp\",\n ico: \"image/x-icon\",\n tiff: \"image/tiff\",\n tif: \"image/tiff\",\n heic: \"image/heic\",\n heif: \"image/heif\",\n};\n\nfunction inferImageMediaType(url: string): string {\n // Handle data URLs: data:[<mediatype>][;base64],<data>\n if (/^data:/i.test(url)) {\n const match = url.match(/^data:([^;,]+)/i);\n if (match?.[1]) return match[1].toLowerCase();\n }\n\n // Extract extension from URL path, ignoring query string and hash\n const [pathWithoutParams = \"\"] = url.split(/[?#]/);\n const ext = pathWithoutParams.split(\".\").pop()?.toLowerCase() ?? \"\";\n return IMAGE_MEDIA_TYPES[ext] ?? \"image/png\";\n}\n\nfunction toUrlOrString(value: string): string | URL {\n try {\n return new URL(value);\n } catch {\n return value;\n }\n}\n\ntype ToolCallAccumulator = {\n textParts: (GenericTextPart | GenericToolCallPart)[];\n toolResults: GenericToolResultPart[];\n};\n\nfunction processToolCall(\n part: MessagePartLike,\n accumulator: ToolCallAccumulator,\n): boolean {\n if (!part.toolCallId || !part.toolName) return false;\n\n accumulator.textParts.push({\n type: \"tool-call\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n args: part.args ?? {},\n });\n\n if (part.result !== undefined) {\n const toolResult: GenericToolResultPart = {\n type: \"tool-result\",\n toolCallId: part.toolCallId,\n toolName: part.toolName,\n result: part.result,\n };\n if (part.isError) {\n toolResult.isError = true;\n }\n accumulator.toolResults.push(toolResult);\n return true;\n }\n return false;\n}\n\nfunction flushAccumulator(\n accumulator: ToolCallAccumulator,\n result: GenericMessage[],\n): void {\n if (accumulator.textParts.length > 0) {\n result.push({ role: \"assistant\", content: accumulator.textParts });\n accumulator.textParts = [];\n }\n if (accumulator.toolResults.length > 0) {\n result.push({ role: \"tool\", content: accumulator.toolResults });\n accumulator.toolResults = [];\n }\n}\n\nfunction convertSystemMessage(\n message: ThreadMessageLike,\n result: GenericMessage[],\n): void {\n const textPart = message.content.find((p) => p.type === \"text\");\n if (textPart?.text) {\n result.push({ role: \"system\", content: textPart.text });\n }\n}\n\nfunction convertUserMessage(\n message: ThreadMessageLike,\n result: GenericMessage[],\n): void {\n const attachments = message.attachments ?? [];\n const allContent = [\n ...message.content,\n ...attachments.flatMap((a) => a.content),\n ];\n\n const content: (GenericTextPart | GenericFilePart)[] = [];\n\n for (const part of allContent) {\n if (part.type === \"text\" && part.text) {\n content.push({ type: \"text\", text: part.text });\n } else if (part.type === \"image\" && part.image) {\n content.push({\n type: \"file\",\n data: toUrlOrString(part.image),\n mediaType: inferImageMediaType(part.image),\n });\n } else if (part.type === \"file\" && part.data && part.mimeType) {\n content.push({\n type: \"file\",\n data: toUrlOrString(part.data),\n mediaType: part.mimeType,\n });\n }\n }\n\n if (content.length > 0) {\n result.push({ role: \"user\", content });\n }\n}\n\nfunction convertAssistantMessage(\n message: ThreadMessageLike,\n result: GenericMessage[],\n): void {\n const accumulator: ToolCallAccumulator = {\n textParts: [],\n toolResults: [],\n };\n let hasPendingToolResults = false;\n\n for (const part of message.content) {\n if (part.type === \"text\" && part.text) {\n // Flush pending tool results before adding more text\n if (hasPendingToolResults) {\n flushAccumulator(accumulator, result);\n hasPendingToolResults = false;\n }\n accumulator.textParts.push({ type: \"text\", text: part.text });\n } else if (part.type === \"tool-call\") {\n if (processToolCall(part, accumulator)) {\n hasPendingToolResults = true;\n }\n }\n }\n\n flushAccumulator(accumulator, result);\n}\n\n/**\n * Converts thread messages to generic LLM messages.\n * This format can then be easily converted to provider-specific formats.\n */\nexport function toGenericMessages(\n messages: readonly ThreadMessageLike[],\n): GenericMessage[] {\n const result: GenericMessage[] = [];\n\n for (const message of messages) {\n switch (message.role) {\n case \"system\":\n convertSystemMessage(message, result);\n break;\n case \"user\":\n convertUserMessage(message, result);\n break;\n case \"assistant\":\n convertAssistantMessage(message, result);\n break;\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -170,13 +170,20 @@ var DataStreamDecoder = class extends PipeableTransformStream {
|
|
|
170
170
|
constructor() {
|
|
171
171
|
super((readable) => {
|
|
172
172
|
const toolCallControllers = /* @__PURE__ */ new Map();
|
|
173
|
+
const closedToolCallArgs = /* @__PURE__ */ new Set();
|
|
174
|
+
const warnedDroppedArgs = /* @__PURE__ */ new Set();
|
|
173
175
|
let activeToolCallArgsText;
|
|
176
|
+
let activeToolCallArgsId;
|
|
174
177
|
const transform = new AssistantTransformStream({
|
|
175
178
|
transform(chunk, controller) {
|
|
176
179
|
const { type, value } = chunk;
|
|
177
180
|
if (TOOL_CALL_ARGS_CLOSING_CHUNKS.includes(type)) {
|
|
178
|
-
activeToolCallArgsText
|
|
181
|
+
if (activeToolCallArgsText && activeToolCallArgsId !== void 0) {
|
|
182
|
+
activeToolCallArgsText.close();
|
|
183
|
+
closedToolCallArgs.add(activeToolCallArgsId);
|
|
184
|
+
}
|
|
179
185
|
activeToolCallArgsText = void 0;
|
|
186
|
+
activeToolCallArgsId = void 0;
|
|
180
187
|
}
|
|
181
188
|
switch (type) {
|
|
182
189
|
case DataStreamStreamChunkType.ReasoningDelta:
|
|
@@ -201,10 +208,18 @@ var DataStreamDecoder = class extends PipeableTransformStream {
|
|
|
201
208
|
});
|
|
202
209
|
toolCallControllers.set(toolCallId, toolCallController);
|
|
203
210
|
activeToolCallArgsText = toolCallController.argsText;
|
|
211
|
+
activeToolCallArgsId = toolCallId;
|
|
204
212
|
break;
|
|
205
213
|
}
|
|
206
214
|
case DataStreamStreamChunkType.ToolCallArgsTextDelta: {
|
|
207
215
|
const { toolCallId, argsTextDelta } = value;
|
|
216
|
+
if (closedToolCallArgs.has(toolCallId)) {
|
|
217
|
+
if (!warnedDroppedArgs.has(toolCallId)) {
|
|
218
|
+
warnedDroppedArgs.add(toolCallId);
|
|
219
|
+
console.warn(`Dropped tool-call args delta for closed args stream: ${toolCallId}`);
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
208
223
|
const toolCallController = toolCallControllers.get(toolCallId);
|
|
209
224
|
if (!toolCallController) throw new Error(`Encountered tool call with unknown id: ${toolCallId}`);
|
|
210
225
|
toolCallController.argsText.append(argsTextDelta);
|
|
@@ -219,6 +234,11 @@ var DataStreamDecoder = class extends PipeableTransformStream {
|
|
|
219
234
|
result,
|
|
220
235
|
isError
|
|
221
236
|
});
|
|
237
|
+
closedToolCallArgs.add(toolCallId);
|
|
238
|
+
if (activeToolCallArgsId === toolCallId) {
|
|
239
|
+
activeToolCallArgsText = void 0;
|
|
240
|
+
activeToolCallArgsId = void 0;
|
|
241
|
+
}
|
|
222
242
|
break;
|
|
223
243
|
}
|
|
224
244
|
case DataStreamStreamChunkType.ToolCall: {
|
|
@@ -233,6 +253,11 @@ var DataStreamDecoder = class extends PipeableTransformStream {
|
|
|
233
253
|
});
|
|
234
254
|
toolCallControllers.set(toolCallId, toolCallController);
|
|
235
255
|
}
|
|
256
|
+
closedToolCallArgs.add(toolCallId);
|
|
257
|
+
if (activeToolCallArgsId === toolCallId) {
|
|
258
|
+
activeToolCallArgsText = void 0;
|
|
259
|
+
activeToolCallArgsId = void 0;
|
|
260
|
+
}
|
|
236
261
|
break;
|
|
237
262
|
}
|
|
238
263
|
case DataStreamStreamChunkType.FinishMessage:
|
|
@@ -312,6 +337,7 @@ var DataStreamDecoder = class extends PipeableTransformStream {
|
|
|
312
337
|
flush() {
|
|
313
338
|
activeToolCallArgsText?.close();
|
|
314
339
|
activeToolCallArgsText = void 0;
|
|
340
|
+
activeToolCallArgsId = void 0;
|
|
315
341
|
toolCallControllers.forEach((controller) => controller.close());
|
|
316
342
|
toolCallControllers.clear();
|
|
317
343
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataStream.js","names":["exhaustiveCheck"],"sources":["../../../../src/core/serialization/data-stream/DataStream.ts"],"sourcesContent":["import type { AssistantStreamChunk } from \"../../AssistantStreamChunk\";\nimport type { ToolCallStreamController } from \"../../modules/tool-call\";\nimport { AssistantTransformStream } from \"../../utils/stream/AssistantTransformStream\";\nimport { PipeableTransformStream } from \"../../utils/stream/PipeableTransformStream\";\nimport { type DataStreamChunk, DataStreamStreamChunkType } from \"./chunk-types\";\nimport { LineDecoderStream } from \"../../utils/stream/LineDecoderStream\";\nimport {\n DataStreamChunkDecoder,\n DataStreamChunkEncoder,\n} from \"./serialization\";\nimport {\n type AssistantMetaStreamChunk,\n AssistantMetaTransformStream,\n} from \"../../utils/stream/AssistantMetaTransformStream\";\nimport type { TextStreamController } from \"../../modules/text\";\nimport type { AssistantStreamEncoder } from \"../../AssistantStream\";\n\nexport class DataStreamEncoder\n extends PipeableTransformStream<AssistantStreamChunk, Uint8Array<ArrayBuffer>>\n implements AssistantStreamEncoder\n{\n headers = new Headers({\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"x-vercel-ai-data-stream\": \"v1\",\n });\n\n constructor() {\n super((readable) => {\n const transform = new TransformStream<\n AssistantMetaStreamChunk,\n DataStreamChunk\n >({\n transform(chunk, controller) {\n const type = chunk.type;\n switch (type) {\n case \"part-start\": {\n const part = chunk.part;\n if (part.type === \"tool-call\") {\n const { type, ...value } = part;\n controller.enqueue({\n type: DataStreamStreamChunkType.StartToolCall,\n value,\n });\n }\n if (part.type === \"source\") {\n const { type, ...value } = part;\n controller.enqueue({\n type: DataStreamStreamChunkType.Source,\n value,\n });\n }\n if (part.type === \"data\") {\n const { type, ...value } = part;\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiDataPart,\n value,\n });\n }\n break;\n }\n case \"text-delta\": {\n const part = chunk.meta;\n switch (part.type) {\n case \"text\": {\n if (part.parentId) {\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiTextDelta,\n value: {\n textDelta: chunk.textDelta,\n parentId: part.parentId,\n },\n });\n } else {\n controller.enqueue({\n type: DataStreamStreamChunkType.TextDelta,\n value: chunk.textDelta,\n });\n }\n break;\n }\n case \"reasoning\": {\n if (part.parentId) {\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiReasoningDelta,\n value: {\n reasoningDelta: chunk.textDelta,\n parentId: part.parentId,\n },\n });\n } else {\n controller.enqueue({\n type: DataStreamStreamChunkType.ReasoningDelta,\n value: chunk.textDelta,\n });\n }\n break;\n }\n case \"tool-call\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.ToolCallArgsTextDelta,\n value: {\n toolCallId: part.toolCallId,\n argsTextDelta: chunk.textDelta,\n },\n });\n break;\n }\n default:\n throw new Error(\n `Unsupported part type for text-delta: ${part.type}`,\n );\n }\n break;\n }\n case \"result\": {\n // Only tool-call parts can have results.\n const part = chunk.meta;\n if (part.type !== \"tool-call\") {\n throw new Error(\n `Result chunk on non-tool-call part not supported: ${part.type}`,\n );\n }\n controller.enqueue({\n type: DataStreamStreamChunkType.ToolCallResult,\n value: {\n toolCallId: part.toolCallId,\n result: chunk.result,\n artifact: chunk.artifact,\n ...(chunk.isError ? { isError: chunk.isError } : {}),\n },\n });\n break;\n }\n case \"step-start\": {\n const { type, ...value } = chunk;\n controller.enqueue({\n type: DataStreamStreamChunkType.StartStep,\n value,\n });\n break;\n }\n case \"step-finish\": {\n const { type, ...value } = chunk;\n controller.enqueue({\n type: DataStreamStreamChunkType.FinishStep,\n value,\n });\n break;\n }\n case \"message-finish\": {\n const { type, ...value } = chunk;\n controller.enqueue({\n type: DataStreamStreamChunkType.FinishMessage,\n value,\n });\n break;\n }\n case \"error\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.Error,\n value: chunk.error,\n });\n break;\n }\n case \"annotations\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.Annotation,\n value: chunk.annotations,\n });\n break;\n }\n case \"data\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.Data,\n value: chunk.data,\n });\n break;\n }\n\n case \"update-state\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiUpdateStateOperations,\n value: chunk.operations,\n });\n break;\n }\n\n // TODO ignore for now\n // in the future, we should create a handler that waits for text parts to finish before continuing\n case \"tool-call-args-text-finish\":\n case \"part-finish\":\n break;\n\n default: {\n const exhaustiveCheck: never = type;\n throw new Error(`Unsupported chunk type: ${exhaustiveCheck}`);\n }\n }\n },\n });\n\n return readable\n .pipeThrough(new AssistantMetaTransformStream())\n .pipeThrough(transform)\n .pipeThrough(new DataStreamChunkEncoder())\n .pipeThrough(new TextEncoderStream());\n });\n }\n}\n\nconst TOOL_CALL_ARGS_CLOSING_CHUNKS: DataStreamStreamChunkType[] = [\n DataStreamStreamChunkType.StartToolCall,\n DataStreamStreamChunkType.ToolCall,\n DataStreamStreamChunkType.TextDelta,\n DataStreamStreamChunkType.ReasoningDelta,\n DataStreamStreamChunkType.Source,\n DataStreamStreamChunkType.Error,\n DataStreamStreamChunkType.FinishStep,\n DataStreamStreamChunkType.FinishMessage,\n DataStreamStreamChunkType.AuiTextDelta,\n DataStreamStreamChunkType.AuiReasoningDelta,\n DataStreamStreamChunkType.AuiDataPart,\n];\n\nexport class DataStreamDecoder extends PipeableTransformStream<\n Uint8Array<ArrayBuffer>,\n AssistantStreamChunk\n> {\n constructor() {\n super((readable) => {\n const toolCallControllers = new Map<string, ToolCallStreamController>();\n let activeToolCallArgsText: TextStreamController | undefined;\n const transform = new AssistantTransformStream<DataStreamChunk>({\n transform(chunk, controller) {\n const { type, value } = chunk;\n\n if (TOOL_CALL_ARGS_CLOSING_CHUNKS.includes(type)) {\n activeToolCallArgsText?.close();\n activeToolCallArgsText = undefined;\n }\n\n switch (type) {\n case DataStreamStreamChunkType.ReasoningDelta:\n controller.appendReasoning(value);\n break;\n\n case DataStreamStreamChunkType.TextDelta:\n controller.appendText(value);\n break;\n\n case DataStreamStreamChunkType.AuiTextDelta:\n controller\n .withParentId(value.parentId)\n .appendText(value.textDelta);\n break;\n\n case DataStreamStreamChunkType.AuiReasoningDelta:\n controller\n .withParentId(value.parentId)\n .appendReasoning(value.reasoningDelta);\n break;\n\n case DataStreamStreamChunkType.StartToolCall: {\n const { toolCallId, toolName, parentId } = value;\n const ctrl = parentId\n ? controller.withParentId(parentId)\n : controller;\n\n if (toolCallControllers.has(toolCallId))\n throw new Error(\n `Encountered duplicate tool call id: ${toolCallId}`,\n );\n\n const toolCallController = ctrl.addToolCallPart({\n toolCallId,\n toolName,\n });\n toolCallControllers.set(toolCallId, toolCallController);\n\n activeToolCallArgsText = toolCallController.argsText;\n break;\n }\n\n case DataStreamStreamChunkType.ToolCallArgsTextDelta: {\n const { toolCallId, argsTextDelta } = value;\n const toolCallController = toolCallControllers.get(toolCallId);\n if (!toolCallController)\n throw new Error(\n `Encountered tool call with unknown id: ${toolCallId}`,\n );\n toolCallController.argsText.append(argsTextDelta);\n break;\n }\n\n case DataStreamStreamChunkType.ToolCallResult: {\n const { toolCallId, artifact, result, isError } = value;\n const toolCallController = toolCallControllers.get(toolCallId);\n if (!toolCallController)\n throw new Error(\n `Encountered tool call result with unknown id: ${toolCallId}`,\n );\n toolCallController.setResponse({\n artifact,\n result,\n isError,\n });\n break;\n }\n\n case DataStreamStreamChunkType.ToolCall: {\n const { toolCallId, toolName, args } = value;\n\n let toolCallController = toolCallControllers.get(toolCallId);\n if (toolCallController) {\n toolCallController.argsText.close();\n } else {\n toolCallController = controller.addToolCallPart({\n toolCallId,\n toolName,\n args,\n });\n toolCallControllers.set(toolCallId, toolCallController);\n }\n break;\n }\n\n case DataStreamStreamChunkType.FinishMessage:\n controller.enqueue({\n type: \"message-finish\",\n path: [],\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.StartStep:\n controller.enqueue({\n type: \"step-start\",\n path: [],\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.FinishStep:\n controller.enqueue({\n type: \"step-finish\",\n path: [],\n ...value,\n });\n break;\n case DataStreamStreamChunkType.Data:\n controller.enqueue({\n type: \"data\",\n path: [],\n data: value,\n });\n break;\n\n case DataStreamStreamChunkType.Annotation:\n controller.enqueue({\n type: \"annotations\",\n path: [],\n annotations: value,\n });\n break;\n\n case DataStreamStreamChunkType.Source: {\n const { parentId, ...sourceData } = value;\n const ctrl = parentId\n ? controller.withParentId(parentId)\n : controller;\n ctrl.appendSource({\n type: \"source\",\n ...sourceData,\n });\n break;\n }\n\n case DataStreamStreamChunkType.Error:\n controller.enqueue({\n type: \"error\",\n path: [],\n error: value,\n });\n break;\n\n case DataStreamStreamChunkType.File:\n controller.appendFile({\n type: \"file\",\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.AuiDataPart:\n controller.appendData({\n type: \"data\",\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.AuiUpdateStateOperations:\n controller.enqueue({\n type: \"update-state\",\n path: [],\n operations: value,\n });\n break;\n\n case DataStreamStreamChunkType.ReasoningSignature:\n case DataStreamStreamChunkType.RedactedReasoning:\n // ignore these for now\n break;\n\n default: {\n const exhaustiveCheck: never = type;\n throw new Error(`unsupported chunk type: ${exhaustiveCheck}`);\n }\n }\n },\n flush() {\n activeToolCallArgsText?.close();\n activeToolCallArgsText = undefined;\n toolCallControllers.forEach((controller) => controller.close());\n toolCallControllers.clear();\n },\n });\n\n return readable\n .pipeThrough(new TextDecoderStream())\n .pipeThrough(new LineDecoderStream())\n .pipeThrough(new DataStreamChunkDecoder())\n .pipeThrough(transform);\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataStream.js","names":["exhaustiveCheck"],"sources":["../../../../src/core/serialization/data-stream/DataStream.ts"],"sourcesContent":["import type { AssistantStreamChunk } from \"../../AssistantStreamChunk\";\nimport type { ToolCallStreamController } from \"../../modules/tool-call\";\nimport { AssistantTransformStream } from \"../../utils/stream/AssistantTransformStream\";\nimport { PipeableTransformStream } from \"../../utils/stream/PipeableTransformStream\";\nimport { type DataStreamChunk, DataStreamStreamChunkType } from \"./chunk-types\";\nimport { LineDecoderStream } from \"../../utils/stream/LineDecoderStream\";\nimport {\n DataStreamChunkDecoder,\n DataStreamChunkEncoder,\n} from \"./serialization\";\nimport {\n type AssistantMetaStreamChunk,\n AssistantMetaTransformStream,\n} from \"../../utils/stream/AssistantMetaTransformStream\";\nimport type { TextStreamController } from \"../../modules/text\";\nimport type { AssistantStreamEncoder } from \"../../AssistantStream\";\n\nexport class DataStreamEncoder\n extends PipeableTransformStream<AssistantStreamChunk, Uint8Array<ArrayBuffer>>\n implements AssistantStreamEncoder\n{\n headers = new Headers({\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"x-vercel-ai-data-stream\": \"v1\",\n });\n\n constructor() {\n super((readable) => {\n const transform = new TransformStream<\n AssistantMetaStreamChunk,\n DataStreamChunk\n >({\n transform(chunk, controller) {\n const type = chunk.type;\n switch (type) {\n case \"part-start\": {\n const part = chunk.part;\n if (part.type === \"tool-call\") {\n const { type, ...value } = part;\n controller.enqueue({\n type: DataStreamStreamChunkType.StartToolCall,\n value,\n });\n }\n if (part.type === \"source\") {\n const { type, ...value } = part;\n controller.enqueue({\n type: DataStreamStreamChunkType.Source,\n value,\n });\n }\n if (part.type === \"data\") {\n const { type, ...value } = part;\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiDataPart,\n value,\n });\n }\n break;\n }\n case \"text-delta\": {\n const part = chunk.meta;\n switch (part.type) {\n case \"text\": {\n if (part.parentId) {\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiTextDelta,\n value: {\n textDelta: chunk.textDelta,\n parentId: part.parentId,\n },\n });\n } else {\n controller.enqueue({\n type: DataStreamStreamChunkType.TextDelta,\n value: chunk.textDelta,\n });\n }\n break;\n }\n case \"reasoning\": {\n if (part.parentId) {\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiReasoningDelta,\n value: {\n reasoningDelta: chunk.textDelta,\n parentId: part.parentId,\n },\n });\n } else {\n controller.enqueue({\n type: DataStreamStreamChunkType.ReasoningDelta,\n value: chunk.textDelta,\n });\n }\n break;\n }\n case \"tool-call\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.ToolCallArgsTextDelta,\n value: {\n toolCallId: part.toolCallId,\n argsTextDelta: chunk.textDelta,\n },\n });\n break;\n }\n default:\n throw new Error(\n `Unsupported part type for text-delta: ${part.type}`,\n );\n }\n break;\n }\n case \"result\": {\n // Only tool-call parts can have results.\n const part = chunk.meta;\n if (part.type !== \"tool-call\") {\n throw new Error(\n `Result chunk on non-tool-call part not supported: ${part.type}`,\n );\n }\n controller.enqueue({\n type: DataStreamStreamChunkType.ToolCallResult,\n value: {\n toolCallId: part.toolCallId,\n result: chunk.result,\n artifact: chunk.artifact,\n ...(chunk.isError ? { isError: chunk.isError } : {}),\n },\n });\n break;\n }\n case \"step-start\": {\n const { type, ...value } = chunk;\n controller.enqueue({\n type: DataStreamStreamChunkType.StartStep,\n value,\n });\n break;\n }\n case \"step-finish\": {\n const { type, ...value } = chunk;\n controller.enqueue({\n type: DataStreamStreamChunkType.FinishStep,\n value,\n });\n break;\n }\n case \"message-finish\": {\n const { type, ...value } = chunk;\n controller.enqueue({\n type: DataStreamStreamChunkType.FinishMessage,\n value,\n });\n break;\n }\n case \"error\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.Error,\n value: chunk.error,\n });\n break;\n }\n case \"annotations\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.Annotation,\n value: chunk.annotations,\n });\n break;\n }\n case \"data\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.Data,\n value: chunk.data,\n });\n break;\n }\n\n case \"update-state\": {\n controller.enqueue({\n type: DataStreamStreamChunkType.AuiUpdateStateOperations,\n value: chunk.operations,\n });\n break;\n }\n\n // TODO ignore for now\n // in the future, we should create a handler that waits for text parts to finish before continuing\n case \"tool-call-args-text-finish\":\n case \"part-finish\":\n break;\n\n default: {\n const exhaustiveCheck: never = type;\n throw new Error(`Unsupported chunk type: ${exhaustiveCheck}`);\n }\n }\n },\n });\n\n return readable\n .pipeThrough(new AssistantMetaTransformStream())\n .pipeThrough(transform)\n .pipeThrough(new DataStreamChunkEncoder())\n .pipeThrough(new TextEncoderStream());\n });\n }\n}\n\nconst TOOL_CALL_ARGS_CLOSING_CHUNKS: DataStreamStreamChunkType[] = [\n DataStreamStreamChunkType.StartToolCall,\n DataStreamStreamChunkType.ToolCall,\n DataStreamStreamChunkType.TextDelta,\n DataStreamStreamChunkType.ReasoningDelta,\n DataStreamStreamChunkType.Source,\n DataStreamStreamChunkType.Error,\n DataStreamStreamChunkType.FinishStep,\n DataStreamStreamChunkType.FinishMessage,\n DataStreamStreamChunkType.AuiTextDelta,\n DataStreamStreamChunkType.AuiReasoningDelta,\n DataStreamStreamChunkType.AuiDataPart,\n];\n\nexport class DataStreamDecoder extends PipeableTransformStream<\n Uint8Array<ArrayBuffer>,\n AssistantStreamChunk\n> {\n constructor() {\n super((readable) => {\n const toolCallControllers = new Map<string, ToolCallStreamController>();\n const closedToolCallArgs = new Set<string>();\n const warnedDroppedArgs = new Set<string>();\n let activeToolCallArgsText: TextStreamController | undefined;\n let activeToolCallArgsId: string | undefined;\n const transform = new AssistantTransformStream<DataStreamChunk>({\n transform(chunk, controller) {\n const { type, value } = chunk;\n\n if (TOOL_CALL_ARGS_CLOSING_CHUNKS.includes(type)) {\n if (activeToolCallArgsText && activeToolCallArgsId !== undefined) {\n activeToolCallArgsText.close();\n closedToolCallArgs.add(activeToolCallArgsId);\n }\n activeToolCallArgsText = undefined;\n activeToolCallArgsId = undefined;\n }\n\n switch (type) {\n case DataStreamStreamChunkType.ReasoningDelta:\n controller.appendReasoning(value);\n break;\n\n case DataStreamStreamChunkType.TextDelta:\n controller.appendText(value);\n break;\n\n case DataStreamStreamChunkType.AuiTextDelta:\n controller\n .withParentId(value.parentId)\n .appendText(value.textDelta);\n break;\n\n case DataStreamStreamChunkType.AuiReasoningDelta:\n controller\n .withParentId(value.parentId)\n .appendReasoning(value.reasoningDelta);\n break;\n\n case DataStreamStreamChunkType.StartToolCall: {\n const { toolCallId, toolName, parentId } = value;\n const ctrl = parentId\n ? controller.withParentId(parentId)\n : controller;\n\n if (toolCallControllers.has(toolCallId))\n throw new Error(\n `Encountered duplicate tool call id: ${toolCallId}`,\n );\n\n const toolCallController = ctrl.addToolCallPart({\n toolCallId,\n toolName,\n });\n toolCallControllers.set(toolCallId, toolCallController);\n\n activeToolCallArgsText = toolCallController.argsText;\n activeToolCallArgsId = toolCallId;\n break;\n }\n\n case DataStreamStreamChunkType.ToolCallArgsTextDelta: {\n const { toolCallId, argsTextDelta } = value;\n if (closedToolCallArgs.has(toolCallId)) {\n if (!warnedDroppedArgs.has(toolCallId)) {\n warnedDroppedArgs.add(toolCallId);\n console.warn(\n `Dropped tool-call args delta for closed args stream: ${toolCallId}`,\n );\n }\n break;\n }\n const toolCallController = toolCallControllers.get(toolCallId);\n if (!toolCallController)\n throw new Error(\n `Encountered tool call with unknown id: ${toolCallId}`,\n );\n toolCallController.argsText.append(argsTextDelta);\n break;\n }\n\n case DataStreamStreamChunkType.ToolCallResult: {\n const { toolCallId, artifact, result, isError } = value;\n const toolCallController = toolCallControllers.get(toolCallId);\n if (!toolCallController)\n throw new Error(\n `Encountered tool call result with unknown id: ${toolCallId}`,\n );\n toolCallController.setResponse({\n artifact,\n result,\n isError,\n });\n closedToolCallArgs.add(toolCallId);\n if (activeToolCallArgsId === toolCallId) {\n activeToolCallArgsText = undefined;\n activeToolCallArgsId = undefined;\n }\n break;\n }\n\n case DataStreamStreamChunkType.ToolCall: {\n const { toolCallId, toolName, args } = value;\n\n let toolCallController = toolCallControllers.get(toolCallId);\n if (toolCallController) {\n toolCallController.argsText.close();\n } else {\n toolCallController = controller.addToolCallPart({\n toolCallId,\n toolName,\n args,\n });\n toolCallControllers.set(toolCallId, toolCallController);\n }\n closedToolCallArgs.add(toolCallId);\n if (activeToolCallArgsId === toolCallId) {\n activeToolCallArgsText = undefined;\n activeToolCallArgsId = undefined;\n }\n break;\n }\n\n case DataStreamStreamChunkType.FinishMessage:\n controller.enqueue({\n type: \"message-finish\",\n path: [],\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.StartStep:\n controller.enqueue({\n type: \"step-start\",\n path: [],\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.FinishStep:\n controller.enqueue({\n type: \"step-finish\",\n path: [],\n ...value,\n });\n break;\n case DataStreamStreamChunkType.Data:\n controller.enqueue({\n type: \"data\",\n path: [],\n data: value,\n });\n break;\n\n case DataStreamStreamChunkType.Annotation:\n controller.enqueue({\n type: \"annotations\",\n path: [],\n annotations: value,\n });\n break;\n\n case DataStreamStreamChunkType.Source: {\n const { parentId, ...sourceData } = value;\n const ctrl = parentId\n ? controller.withParentId(parentId)\n : controller;\n ctrl.appendSource({\n type: \"source\",\n ...sourceData,\n });\n break;\n }\n\n case DataStreamStreamChunkType.Error:\n controller.enqueue({\n type: \"error\",\n path: [],\n error: value,\n });\n break;\n\n case DataStreamStreamChunkType.File:\n controller.appendFile({\n type: \"file\",\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.AuiDataPart:\n controller.appendData({\n type: \"data\",\n ...value,\n });\n break;\n\n case DataStreamStreamChunkType.AuiUpdateStateOperations:\n controller.enqueue({\n type: \"update-state\",\n path: [],\n operations: value,\n });\n break;\n\n case DataStreamStreamChunkType.ReasoningSignature:\n case DataStreamStreamChunkType.RedactedReasoning:\n // ignore these for now\n break;\n\n default: {\n const exhaustiveCheck: never = type;\n throw new Error(`unsupported chunk type: ${exhaustiveCheck}`);\n }\n }\n },\n flush() {\n activeToolCallArgsText?.close();\n activeToolCallArgsText = undefined;\n activeToolCallArgsId = undefined;\n toolCallControllers.forEach((controller) => controller.close());\n toolCallControllers.clear();\n },\n });\n\n return readable\n .pipeThrough(new TextDecoderStream())\n .pipeThrough(new LineDecoderStream())\n .pipeThrough(new DataStreamChunkDecoder())\n .pipeThrough(transform);\n });\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -189,6 +189,23 @@ describe("toGenericMessages", () => {
|
|
|
189
189
|
expect((content[0] as { data: unknown }).data).toBeInstanceOf(URL);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
+
it("infers a lowercase media type from an uppercase-scheme data URL", () => {
|
|
193
|
+
const dataUrl =
|
|
194
|
+
"DATA:IMAGE/PNG;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
|
195
|
+
const result = toGenericMessages([
|
|
196
|
+
{
|
|
197
|
+
role: "user",
|
|
198
|
+
content: [{ type: "image", image: dataUrl }],
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
const content = (result[0] as { content: unknown[] }).content;
|
|
203
|
+
expect(content[0]).toMatchObject({
|
|
204
|
+
type: "file",
|
|
205
|
+
mediaType: "image/png",
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
192
209
|
it("handles relative/invalid URL as string", () => {
|
|
193
210
|
const result = toGenericMessages([
|
|
194
211
|
{
|
|
@@ -97,9 +97,9 @@ const IMAGE_MEDIA_TYPES: Record<string, string> = {
|
|
|
97
97
|
|
|
98
98
|
function inferImageMediaType(url: string): string {
|
|
99
99
|
// Handle data URLs: data:[<mediatype>][;base64],<data>
|
|
100
|
-
if (
|
|
101
|
-
const match = url.match(/^data:([^;,]+)/);
|
|
102
|
-
if (match?.[1]) return match[1];
|
|
100
|
+
if (/^data:/i.test(url)) {
|
|
101
|
+
const match = url.match(/^data:([^;,]+)/i);
|
|
102
|
+
if (match?.[1]) return match[1].toLowerCase();
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Extract extension from URL path, ignoring query string and hash
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { DataStreamDecoder } from "./DataStream";
|
|
3
|
+
import type { AssistantStreamChunk } from "../../AssistantStreamChunk";
|
|
4
|
+
|
|
5
|
+
const decodeLines = async (lines: string[]) => {
|
|
6
|
+
const bytes = new ReadableStream<Uint8Array>({
|
|
7
|
+
start(controller) {
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
for (const line of lines) controller.enqueue(encoder.encode(line + "\n"));
|
|
10
|
+
controller.close();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const chunks: AssistantStreamChunk[] = [];
|
|
14
|
+
await bytes.pipeThrough(new DataStreamDecoder()).pipeTo(
|
|
15
|
+
new WritableStream({
|
|
16
|
+
write(chunk) {
|
|
17
|
+
chunks.push(chunk);
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
return chunks;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("DataStreamDecoder interleaved tool-call args", () => {
|
|
25
|
+
it("drops args deltas for a closed args stream instead of crashing", async () => {
|
|
26
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
27
|
+
try {
|
|
28
|
+
const chunks = await decodeLines([
|
|
29
|
+
'b:{"toolCallId":"t1","toolName":"search"}',
|
|
30
|
+
'0:"progress text"',
|
|
31
|
+
'c:{"toolCallId":"t1","argsTextDelta":"{\\"q\\":1}"}',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
chunks.some(
|
|
36
|
+
(c) => c.type === "text-delta" && c.textDelta === "progress text",
|
|
37
|
+
),
|
|
38
|
+
).toBe(true);
|
|
39
|
+
expect(
|
|
40
|
+
chunks.some(
|
|
41
|
+
(c) =>
|
|
42
|
+
c.type === "part-start" &&
|
|
43
|
+
c.part.type === "tool-call" &&
|
|
44
|
+
c.part.toolCallId === "t1",
|
|
45
|
+
),
|
|
46
|
+
).toBe(true);
|
|
47
|
+
expect(chunks.some((c) => c.type === "tool-call-args-text-finish")).toBe(
|
|
48
|
+
true,
|
|
49
|
+
);
|
|
50
|
+
expect(
|
|
51
|
+
chunks.some((c) => c.type === "text-delta" && c.textDelta === "{}"),
|
|
52
|
+
).toBe(true);
|
|
53
|
+
expect(
|
|
54
|
+
chunks.some(
|
|
55
|
+
(c) => c.type === "text-delta" && c.textDelta === '{"q":1}',
|
|
56
|
+
),
|
|
57
|
+
).toBe(false);
|
|
58
|
+
expect(warn).toHaveBeenCalledWith(
|
|
59
|
+
"Dropped tool-call args delta for closed args stream: t1",
|
|
60
|
+
);
|
|
61
|
+
} finally {
|
|
62
|
+
warn.mockRestore();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("drops args deltas arriving after the tool call's result", async () => {
|
|
67
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
68
|
+
try {
|
|
69
|
+
const chunks = await decodeLines([
|
|
70
|
+
'b:{"toolCallId":"t1","toolName":"search"}',
|
|
71
|
+
'a:{"toolCallId":"t1","result":"ok"}',
|
|
72
|
+
'c:{"toolCallId":"t1","argsTextDelta":"late"}',
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
expect(chunks.some((c) => c.type === "result" && c.result === "ok")).toBe(
|
|
76
|
+
true,
|
|
77
|
+
);
|
|
78
|
+
expect(
|
|
79
|
+
chunks.some((c) => c.type === "text-delta" && c.textDelta === "late"),
|
|
80
|
+
).toBe(false);
|
|
81
|
+
} finally {
|
|
82
|
+
warn.mockRestore();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("drops args deltas arriving after a complete tool call frame", async () => {
|
|
87
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
88
|
+
try {
|
|
89
|
+
const chunks = await decodeLines([
|
|
90
|
+
'9:{"toolCallId":"t1","toolName":"search","args":{"q":1}}',
|
|
91
|
+
'c:{"toolCallId":"t1","argsTextDelta":"late"}',
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
expect(
|
|
95
|
+
chunks.some(
|
|
96
|
+
(c) =>
|
|
97
|
+
c.type === "part-start" &&
|
|
98
|
+
c.part.type === "tool-call" &&
|
|
99
|
+
c.part.toolCallId === "t1",
|
|
100
|
+
),
|
|
101
|
+
).toBe(true);
|
|
102
|
+
expect(
|
|
103
|
+
chunks.some((c) => c.type === "text-delta" && c.textDelta === "late"),
|
|
104
|
+
).toBe(false);
|
|
105
|
+
} finally {
|
|
106
|
+
warn.mockRestore();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -229,14 +229,21 @@ export class DataStreamDecoder extends PipeableTransformStream<
|
|
|
229
229
|
constructor() {
|
|
230
230
|
super((readable) => {
|
|
231
231
|
const toolCallControllers = new Map<string, ToolCallStreamController>();
|
|
232
|
+
const closedToolCallArgs = new Set<string>();
|
|
233
|
+
const warnedDroppedArgs = new Set<string>();
|
|
232
234
|
let activeToolCallArgsText: TextStreamController | undefined;
|
|
235
|
+
let activeToolCallArgsId: string | undefined;
|
|
233
236
|
const transform = new AssistantTransformStream<DataStreamChunk>({
|
|
234
237
|
transform(chunk, controller) {
|
|
235
238
|
const { type, value } = chunk;
|
|
236
239
|
|
|
237
240
|
if (TOOL_CALL_ARGS_CLOSING_CHUNKS.includes(type)) {
|
|
238
|
-
activeToolCallArgsText
|
|
241
|
+
if (activeToolCallArgsText && activeToolCallArgsId !== undefined) {
|
|
242
|
+
activeToolCallArgsText.close();
|
|
243
|
+
closedToolCallArgs.add(activeToolCallArgsId);
|
|
244
|
+
}
|
|
239
245
|
activeToolCallArgsText = undefined;
|
|
246
|
+
activeToolCallArgsId = undefined;
|
|
240
247
|
}
|
|
241
248
|
|
|
242
249
|
switch (type) {
|
|
@@ -278,11 +285,21 @@ export class DataStreamDecoder extends PipeableTransformStream<
|
|
|
278
285
|
toolCallControllers.set(toolCallId, toolCallController);
|
|
279
286
|
|
|
280
287
|
activeToolCallArgsText = toolCallController.argsText;
|
|
288
|
+
activeToolCallArgsId = toolCallId;
|
|
281
289
|
break;
|
|
282
290
|
}
|
|
283
291
|
|
|
284
292
|
case DataStreamStreamChunkType.ToolCallArgsTextDelta: {
|
|
285
293
|
const { toolCallId, argsTextDelta } = value;
|
|
294
|
+
if (closedToolCallArgs.has(toolCallId)) {
|
|
295
|
+
if (!warnedDroppedArgs.has(toolCallId)) {
|
|
296
|
+
warnedDroppedArgs.add(toolCallId);
|
|
297
|
+
console.warn(
|
|
298
|
+
`Dropped tool-call args delta for closed args stream: ${toolCallId}`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
286
303
|
const toolCallController = toolCallControllers.get(toolCallId);
|
|
287
304
|
if (!toolCallController)
|
|
288
305
|
throw new Error(
|
|
@@ -304,6 +321,11 @@ export class DataStreamDecoder extends PipeableTransformStream<
|
|
|
304
321
|
result,
|
|
305
322
|
isError,
|
|
306
323
|
});
|
|
324
|
+
closedToolCallArgs.add(toolCallId);
|
|
325
|
+
if (activeToolCallArgsId === toolCallId) {
|
|
326
|
+
activeToolCallArgsText = undefined;
|
|
327
|
+
activeToolCallArgsId = undefined;
|
|
328
|
+
}
|
|
307
329
|
break;
|
|
308
330
|
}
|
|
309
331
|
|
|
@@ -321,6 +343,11 @@ export class DataStreamDecoder extends PipeableTransformStream<
|
|
|
321
343
|
});
|
|
322
344
|
toolCallControllers.set(toolCallId, toolCallController);
|
|
323
345
|
}
|
|
346
|
+
closedToolCallArgs.add(toolCallId);
|
|
347
|
+
if (activeToolCallArgsId === toolCallId) {
|
|
348
|
+
activeToolCallArgsText = undefined;
|
|
349
|
+
activeToolCallArgsId = undefined;
|
|
350
|
+
}
|
|
324
351
|
break;
|
|
325
352
|
}
|
|
326
353
|
|
|
@@ -419,6 +446,7 @@ export class DataStreamDecoder extends PipeableTransformStream<
|
|
|
419
446
|
flush() {
|
|
420
447
|
activeToolCallArgsText?.close();
|
|
421
448
|
activeToolCallArgsText = undefined;
|
|
449
|
+
activeToolCallArgsId = undefined;
|
|
422
450
|
toolCallControllers.forEach((controller) => controller.close());
|
|
423
451
|
toolCallControllers.clear();
|
|
424
452
|
},
|