UNPKG

@tanstack/ai

Version:

Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.

134 lines (133 loc) 4.9 kB
import { tanstackMetadata } from "./merge-metadata.js"; //#region src/utilities/ag-ui-wire.ts /** * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages` * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`, * `toolCalls`, `metadata`). Tool results and thinking parts on assistant * messages are additionally emitted as fan-out `{role:'tool',...}` and * `{role:'reasoning',...}` entries for strict AG-UI server consumers. */ function uiMessagesToWire(messages) { const wire = []; for (const msg of messages) { const parts = msg.parts ?? []; if (msg.role === "system") { wire.push(toAnchor(msg, { content: parts.length > 0 ? collectText(parts) : msg.content ?? "" }, parts)); continue; } if (msg.role === "user") { wire.push(toAnchor(msg, { content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? "" }, parts)); continue; } for (const part of parts) if (part.type === "thinking") { const reasoning = { role: "reasoning", id: deriveReasoningId(msg.id, part), content: part.content }; if (part.signature) reasoning.encryptedValue = part.signature; wire.push(reasoning); } const text = collectText(parts); const toolCalls = collectToolCalls(parts); wire.push(toAnchor(msg, { ...text !== "" && { content: text }, ...toolCalls && { toolCalls } }, parts)); for (const part of parts) if (part.type === "tool-result") wire.push({ role: "tool", id: deriveToolMessageId(part.toolCallId), toolCallId: part.toolCallId, content: typeof part.content === "string" ? part.content : JSON.stringify(part.content), ...part.error !== void 0 && { error: part.error } }); } return wire; } function toAnchor(msg, extras, parts) { const metadata = messageMetadata(msg, parts); const name = msg.name; return { id: msg.id, role: msg.role, ...name !== void 0 && { name }, ...extras, ...metadata !== void 0 && { metadata } }; } function messageMetadata(msg, parts) { const base = { ...msg.metadata ?? {} }; const tanstack = { ...tanstackMetadata(msg) ?? {} }; if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString(); const leftover = unfinishedStructuredOutput(parts); if (leftover) tanstack.structuredOutput = leftover; const toolCallMetadata = {}; for (const part of parts) if (part.type === "tool-call" && part.metadata !== void 0) toolCallMetadata[part.id] = part.metadata; if (Object.keys(toolCallMetadata).length > 0) tanstack.toolCallMetadata = toolCallMetadata; const uiResources = parts.filter((p) => p.type === "ui-resource"); if (uiResources.length > 0) tanstack.uiResources = uiResources; if (Object.keys(tanstack).length > 0) base.tanstack = tanstack; return Object.keys(base).length > 0 ? base : void 0; } function unfinishedStructuredOutput(parts) { for (const p of parts) if (p.type === "structured-output" && p.status !== "complete") return structuredOutputLeftover(p); } function structuredOutputLeftover(part) { return { status: part.status, raw: part.raw, ...part.errorMessage !== void 0 && { errorMessage: part.errorMessage } }; } function collectText(parts) { const out = []; for (const p of parts) if (p.type === "text") out.push(p.content); else if (p.type === "structured-output" && p.status === "complete" && p.raw !== "") out.push(p.raw); return out.join(""); } function collectUserContent(parts) { if (!parts.some((p) => p.type === "image" || p.type === "audio" || p.type === "video" || p.type === "document")) return collectText(parts); const out = []; for (const p of parts) if (p.type === "text") out.push({ type: "text", text: p.content }); else if (p.type === "image" || p.type === "audio" || p.type === "video" || p.type === "document") out.push(p); return out; } function thoughtSignatureFromMetadata(metadata) { if (metadata == null || typeof metadata !== "object" || Array.isArray(metadata)) return; if (!("thoughtSignature" in metadata)) return void 0; const value = metadata.thoughtSignature; return typeof value === "string" && value !== "" ? value : void 0; } function collectToolCalls(parts) { const calls = []; for (const p of parts) if (p.type === "tool-call") { const encryptedValue = thoughtSignatureFromMetadata(p.metadata); calls.push({ id: p.id, type: "function", function: { name: p.name, arguments: p.arguments }, ...encryptedValue !== void 0 ? { encryptedValue } : {} }); } return calls.length > 0 ? calls : void 0; } function deriveReasoningId(messageId, part) { return `${messageId}-reasoning-${part.id ?? hashContent(part.content)}`; } function deriveToolMessageId(toolCallId) { return `tool-${toolCallId}`; } function hashContent(s) { let h = 0; for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0; return Math.abs(h).toString(36); } //#endregion export { uiMessagesToWire }; //# sourceMappingURL=ag-ui-wire.js.map