assistant-stream
Version:
Streaming utilities for AI assistants
224 lines (223 loc) • 6.88 kB
JavaScript
import { generateId } from "../../utils/generateId.js";
// src/core/serialization/ui-message-stream/chunk-normalizer.ts
const createChunkNormalizer = () => {
const pendingTools = /* @__PURE__ */ new Map();
const createPendingTool = (toolCallId, toolName) => ({
toolCallId,
toolName: typeof toolName === "string" ? toolName : "",
deltas: [],
emitted: false,
hasFinalResult: false,
pendingResults: []
});
const emitPendingTool = (out, tool) => {
if (!tool.emitted) {
tool.emitted = true;
out.enqueue({
type: "tool-call-start",
id: generateId(),
toolCallId: tool.toolCallId,
toolName: tool.toolName
});
for (const argsText of tool.deltas) out.enqueue({
type: "tool-call-delta",
argsText
});
out.enqueue({ type: "tool-call-end" });
}
while (tool.pendingResults.length > 0) {
const pendingResult = tool.pendingResults.shift();
out.enqueue({
type: "tool-result",
toolCallId: tool.toolCallId,
result: pendingResult.result,
...pendingResult.isError ? { isError: true } : {},
...pendingResult.isPreliminary ? { isPreliminary: true } : {}
});
}
};
return {
normalize(chunk, controller) {
if (chunk.type === "abort") {
pendingTools.clear();
controller.enqueue({
type: "finish",
finishReason: "other",
usage: {
inputTokens: 0,
outputTokens: 0
}
});
return;
}
if (chunk.type === "text-delta" && chunk.textDelta === void 0) {
const { delta, ...rest } = chunk;
controller.enqueue({
...rest,
type: "text-delta",
textDelta: delta ?? ""
});
return;
}
if (chunk.type === "start") {
controller.enqueue({
...chunk,
type: "start",
messageId: chunk.messageId ?? generateId()
});
return;
}
if (chunk.type === "source-url") {
controller.enqueue({
type: "source",
source: {
sourceType: "url",
id: chunk.sourceId,
url: chunk.url,
...chunk.title && { title: chunk.title }
}
});
return;
}
if (chunk.type === "source" && chunk.source == null) return;
if (chunk.type === "file" && chunk.file == null) {
if (chunk.url === void 0) return;
controller.enqueue({
type: "file",
file: {
mimeType: chunk.mediaType,
data: chunk.url
}
});
return;
}
if (chunk.type === "finish-step") {
controller.enqueue({
...chunk,
type: "finish-step",
finishReason: chunk.finishReason ?? "unknown",
usage: chunk.usage ?? {
inputTokens: 0,
outputTokens: 0
},
isContinued: chunk.isContinued ?? false
});
return;
}
if (chunk.type === "finish") {
controller.enqueue({
...chunk,
type: "finish",
finishReason: chunk.finishReason ?? "unknown",
usage: chunk.usage ?? {
inputTokens: 0,
outputTokens: 0
}
});
return;
}
if (chunk.type === "tool-input-start") {
if (typeof chunk.toolCallId !== "string") return;
if (pendingTools.has(chunk.toolCallId)) return;
pendingTools.set(chunk.toolCallId, createPendingTool(chunk.toolCallId, chunk.toolName));
return;
}
if (chunk.type === "tool-input-delta") {
if (typeof chunk.toolCallId !== "string") return;
if (typeof chunk.inputTextDelta !== "string") return;
const tool = pendingTools.get(chunk.toolCallId);
if (!tool || tool.emitted) return;
tool.deltas.push(chunk.inputTextDelta);
return;
}
if (chunk.type === "tool-input-available" || chunk.type === "tool-input-error") {
if (typeof chunk.toolCallId !== "string") return;
let tool = pendingTools.get(chunk.toolCallId);
if (!tool) {
tool = createPendingTool(chunk.toolCallId, chunk.toolName);
pendingTools.set(chunk.toolCallId, tool);
}
if (tool.emitted) return;
if (chunk.input !== void 0) {
const isUnparsedInputText = chunk.type === "tool-input-error" && typeof chunk.input === "string";
tool.deltas = [isUnparsedInputText ? chunk.input : JSON.stringify(chunk.input)];
}
if (chunk.type === "tool-input-error") {
tool.pendingResults.push({
result: typeof chunk.errorText === "string" ? chunk.errorText : "",
isError: true
});
tool.hasFinalResult = true;
}
emitPendingTool(controller, tool);
return;
}
if (chunk.type === "tool-output-available" || chunk.type === "tool-output-error") {
if (typeof chunk.toolCallId !== "string") return;
if (chunk.type === "tool-output-available" && !Object.hasOwn(chunk, "output")) return;
let tool = pendingTools.get(chunk.toolCallId);
const opensCall = tool === void 0;
if (!tool) {
tool = createPendingTool(chunk.toolCallId, "");
pendingTools.set(chunk.toolCallId, tool);
}
if (tool.hasFinalResult) return;
const pendingResult = chunk.type === "tool-output-error" ? {
result: typeof chunk.errorText === "string" ? chunk.errorText : "",
isError: true
} : {
result: chunk.output,
isError: false,
...chunk.preliminary ? { isPreliminary: true } : {}
};
tool.pendingResults.push(pendingResult);
if (!pendingResult.isPreliminary) tool.hasFinalResult = true;
if (tool.emitted || opensCall) emitPendingTool(controller, tool);
return;
}
if (chunk.type === "tool-approval-request") {
if (typeof chunk.toolCallId !== "string" || typeof chunk.approvalId !== "string") return;
controller.enqueue({
type: "tool-approval-request",
toolCallId: chunk.toolCallId,
approval: {
id: chunk.approvalId,
...chunk.approvalDescriptor != null && { descriptor: chunk.approvalDescriptor },
...Object.hasOwn(chunk, "inputSchemaInput") && { inputSchemaInput: chunk.inputSchemaInput },
...typeof chunk.reason === "string" && {
prompt: chunk.reason,
requestReason: chunk.reason
},
...typeof chunk.isAutomatic === "boolean" && { isAutomatic: chunk.isAutomatic },
...typeof chunk.signature === "string" && { signature: chunk.signature }
}
});
return;
}
if (chunk.type === "tool-approval-response") {
if (typeof chunk.approvalId !== "string" || typeof chunk.approved !== "boolean") return;
controller.enqueue({
type: "tool-approval-response",
approvalId: chunk.approvalId,
approved: chunk.approved,
...typeof chunk.reason === "string" && { reason: chunk.reason }
});
return;
}
if (chunk.type === "tool-output-denied") {
if (typeof chunk.toolCallId !== "string") return;
controller.enqueue({
type: "tool-output-denied",
toolCallId: chunk.toolCallId
});
return;
}
controller.enqueue(chunk);
},
flush(controller) {
for (const tool of pendingTools.values()) emitPendingTool(controller, tool);
}
};
};
export { createChunkNormalizer };
//# sourceMappingURL=chunk-normalizer.js.map