@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
93 lines (92 loc) • 2.95 kB
JavaScript
//#region src/utilities/ag-ui-wire.ts
/**
* Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
* wire shape. Each anchor (system/user/assistant) carries the canonical
* `parts` array verbatim plus AG-UI mirror fields (`content`, `toolCalls`)
* so AG-UI Zod parsing succeeds. 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({
...msg,
content: parts.length > 0 ? collectText(parts) : msg.content ?? ""
});
continue;
}
if (msg.role === "user") {
wire.push({
...msg,
content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? ""
});
continue;
}
for (const part of parts) if (part.type === "thinking") wire.push({
role: "reasoning",
id: deriveReasoningId(msg.id, part),
content: part.content
});
const text = collectText(parts);
const toolCalls = collectToolCalls(parts);
wire.push({
...msg,
...text !== "" && { content: text },
...toolCalls && { toolCalls }
});
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 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 collectToolCalls(parts) {
const calls = [];
for (const p of parts) if (p.type === "tool-call") calls.push({
id: p.id,
type: "function",
function: {
name: p.name,
arguments: p.arguments
}
});
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