@tanstack/ai 0.49.1 → 0.51.0

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/esm/index.js CHANGED
@@ -7,6 +7,7 @@ import { fromSpecTokenUsage, toSpecTokenUsage } from "./utilities/ag-ui-usage.js
7
7
  import { mergeMetadata, withTanstackMetadata } from "./utilities/merge-metadata.js";
8
8
  import { normalizeStreamChunk } from "./utilities/normalize-stream-chunk.js";
9
9
  import { ConsoleLogger } from "./logger/console-logger.js";
10
+ import { SkillLimitError } from "./utilities/errors.js";
10
11
  import { RUN_ACCEPTED_EVENT, resumeHttpResponse, resumeServerSentEventsResponse, streamToText, toHttpResponse, toHttpStream, toServerSentEventsResponse, toServerSentEventsStream } from "./stream-to-response.js";
11
12
  import { canonicalInterruptJson, cloneAndDeepFreezeJson, digestInterruptJson } from "./interrupt-serialization.js";
12
13
  import { INTERRUPT_BINDING_VERSION, canonicalizeInterruptResolutions } from "./interrupts.js";
@@ -15,15 +16,15 @@ import { hashSchemaInput, normalizeApprovalSchema } from "./activities/chat/tool
15
16
  import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, interruptItemError, readInterruptBinding, readUnopenedInterruptBinding, validateInterruptResumeBatch, withInterruptBinding, withoutInterruptBinding } from "./interrupt-resume.js";
16
17
  import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, defineInterrupt } from "./interrupt-definition.js";
17
18
  import { INTERRUPT_CONTINUATION_METADATA_KEY, INTERRUPT_CONTINUATION_VERSION, genericInterruptContinuationFromDescriptor, readGenericInterruptContinuation, wrapGenericInterruptContinuation } from "./generic-interrupt-continuation.js";
18
- import { uiMessagesToWire } from "./utilities/ag-ui-wire.js";
19
19
  import { isContentPart, isContentPartArray, normalizeToolResult } from "./utilities/tool-result.js";
20
20
  import { getProviderExecutedMetadata, isProviderExecutedToolCall } from "./utilities/provider-executed.js";
21
+ import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
22
+ import { uiMessagesToWire } from "./utilities/ag-ui-wire.js";
21
23
  import { firstSentence, renderLazyCatalogEntry } from "./activities/chat/tools/lazy-tools.js";
22
24
  import { DISCOVERY_TOOL_NAME } from "./activities/chat/tools/lazy-tool-manager.js";
23
25
  import { DuplicateToolNameError } from "./activities/chat/tools/unique-tool-names.js";
24
26
  import { ToolCallManager } from "./activities/chat/tools/tool-calls.js";
25
27
  import { combineStrategies, maxIterations, untilFinishReason } from "./activities/chat/agent-loop-strategies.js";
26
- import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
27
28
  import { MCPDuplicateToolNameError } from "./activities/chat/mcp/manager.js";
28
29
  import { chat, createChatOptions } from "./activities/chat/index.js";
29
30
  import { createSummarizeOptions, summarize } from "./activities/summarize/index.js";
@@ -57,4 +58,4 @@ import { BatchStrategy, CompositeStrategy, ImmediateStrategy, PunctuationStrateg
57
58
  import { StreamProcessor, createReplayStream } from "./activities/chat/stream/processor.js";
58
59
  import { generationParamsFromBody, generationParamsFromRequest } from "./client.js";
59
60
  import { createModel, extendAdapter } from "./extend-adapter.js";
60
- export { BaseRerankAdapter, BatchStrategy, CUSTOM_EVENT, CompositeStrategy, ConsoleLogger, DISCOVERY_TOOL_NAME, DetachableRunCapability, DuplicateToolNameError, EventType, INTERRUPT_BINDING_METADATA_KEY, INTERRUPT_BINDING_VERSION, INTERRUPT_BOUNDARY_PHASES, INTERRUPT_CONTINUATION_METADATA_KEY, INTERRUPT_CONTINUATION_VERSION, INTERRUPT_PAYLOAD_METADATA_KEY, INTERRUPT_TOOL_RESUMES, ImmediateStrategy, InMemoryRunStore, InterruptResumeValidationError, MCPDuplicateToolNameError, PartialJSONParser, PunctuationStrategy, RUN_ACCEPTED_EVENT, RUN_CANCEL_REASON, RunDetachedCapability, StandardSchemaValidationError, StreamProcessor, ToolCallManager, WordBoundaryStrategy, brandProviderTool, buildBaseUsage, canonicalInterruptJson, canonicalizeInterruptResolutions, chat, chatParamsFromRequest, chatParamsFromRequestBody, cloneAndDeepFreezeJson, combineStrategies, convertMessagesToModelMessages, convertSchemaToJsonSchema, countEmbeddingInputModalities, createAudioOptions, createCapability, createChatMiddleware, createChatOptions, createEmbedOptions, createFrozenRegistry, createImageOptions, createInterruptBinding, createModel, createRealtimeEventEmitter, createReplayStream, createRerankOptions, createSpeechOptions, createSummarizeOptions, createToolRegistry, createTranscriptionOptions, createVideoOptions, decodeWsFrame, defaultJSONParser, defineChatMiddleware, defineInterrupt, defineRunStore, detectImageMimeType, digestInterruptJson, embed, encodeWsFrame, extendAdapter, firstSentence, fromSpecTokenUsage, generateAudio, generateImage, generateMessageId, generateSpeech, generateTranscription, generateVideo, generationParamsFromBody, generationParamsFromRequest, genericInterruptContinuationFromDescriptor, getChunkRunId, getChunkThreadId, getDetachableRun, getProviderExecutedMetadata, getRunDetached, getVideoJobStatus, hashSchemaInput, interruptItemError, isCancelRequestedReason, isContentPart, isContentPartArray, isCustomEvent, isProviderExecutedToolCall, isRunStatus, isStandardSchema, isTerminalRunStatus, maxIterations, memoryStream, mergeAgentTools, mergeMetadata, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeApprovalSchema, normalizeStreamChunk, normalizeSystemPrompts, normalizeToUIMessage, normalizeToolResult, parsePartialJSON, parseWithStandardSchema, provideDetachableRun, provideRunDetached, readGenericInterruptContinuation, readInterruptBinding, readUnopenedInterruptBinding, realtimeToken, renderLazyCatalogEntry, replayRunStream, requestRunCancel, requireTextOnlyEmbeddingInput, rerank, resolveEmbeddingInput, resolveMediaPrompt, resolveResumeRunId, resumeHttpResponse, resumeServerSentEventsResponse, resumeWebSocketResponse, resumeWebSocketStream, streamToText, summarize, toHttpResponse, toHttpStream, toServerSentEventsResponse, toServerSentEventsStream, toSpecTokenUsage, toWebSocketResponse, toWebSocketStream, toolDefinition, uiMessageToModelMessages, uiMessagesToWire, untilFinishReason, validateInterruptResumeBatch, validateWithStandardSchema, wasCancelRequested, withInterruptBinding, withTanstackMetadata, withoutInterruptBinding, wrapGenericInterruptContinuation };
61
+ export { BaseRerankAdapter, BatchStrategy, CUSTOM_EVENT, CompositeStrategy, ConsoleLogger, DISCOVERY_TOOL_NAME, DetachableRunCapability, DuplicateToolNameError, EventType, INTERRUPT_BINDING_METADATA_KEY, INTERRUPT_BINDING_VERSION, INTERRUPT_BOUNDARY_PHASES, INTERRUPT_CONTINUATION_METADATA_KEY, INTERRUPT_CONTINUATION_VERSION, INTERRUPT_PAYLOAD_METADATA_KEY, INTERRUPT_TOOL_RESUMES, ImmediateStrategy, InMemoryRunStore, InterruptResumeValidationError, MCPDuplicateToolNameError, PartialJSONParser, PunctuationStrategy, RUN_ACCEPTED_EVENT, RUN_CANCEL_REASON, RunDetachedCapability, SkillLimitError, StandardSchemaValidationError, StreamProcessor, ToolCallManager, WordBoundaryStrategy, brandProviderTool, buildBaseUsage, canonicalInterruptJson, canonicalizeInterruptResolutions, chat, chatParamsFromRequest, chatParamsFromRequestBody, cloneAndDeepFreezeJson, combineStrategies, convertMessagesToModelMessages, convertSchemaToJsonSchema, countEmbeddingInputModalities, createAudioOptions, createCapability, createChatMiddleware, createChatOptions, createEmbedOptions, createFrozenRegistry, createImageOptions, createInterruptBinding, createModel, createRealtimeEventEmitter, createReplayStream, createRerankOptions, createSpeechOptions, createSummarizeOptions, createToolRegistry, createTranscriptionOptions, createVideoOptions, decodeWsFrame, defaultJSONParser, defineChatMiddleware, defineInterrupt, defineRunStore, detectImageMimeType, digestInterruptJson, embed, encodeWsFrame, extendAdapter, firstSentence, fromSpecTokenUsage, generateAudio, generateImage, generateMessageId, generateSpeech, generateTranscription, generateVideo, generationParamsFromBody, generationParamsFromRequest, genericInterruptContinuationFromDescriptor, getChunkRunId, getChunkThreadId, getDetachableRun, getProviderExecutedMetadata, getRunDetached, getVideoJobStatus, hashSchemaInput, interruptItemError, isCancelRequestedReason, isContentPart, isContentPartArray, isCustomEvent, isProviderExecutedToolCall, isRunStatus, isStandardSchema, isTerminalRunStatus, maxIterations, memoryStream, mergeAgentTools, mergeMetadata, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeApprovalSchema, normalizeStreamChunk, normalizeSystemPrompts, normalizeToUIMessage, normalizeToolResult, parsePartialJSON, parseWithStandardSchema, provideDetachableRun, provideRunDetached, readGenericInterruptContinuation, readInterruptBinding, readUnopenedInterruptBinding, realtimeToken, renderLazyCatalogEntry, replayRunStream, requestRunCancel, requireTextOnlyEmbeddingInput, rerank, resolveEmbeddingInput, resolveMediaPrompt, resolveResumeRunId, resumeHttpResponse, resumeServerSentEventsResponse, resumeWebSocketResponse, resumeWebSocketStream, streamToText, summarize, toHttpResponse, toHttpStream, toServerSentEventsResponse, toServerSentEventsStream, toSpecTokenUsage, toWebSocketResponse, toWebSocketStream, toolDefinition, uiMessageToModelMessages, uiMessagesToWire, untilFinishReason, validateInterruptResumeBatch, validateWithStandardSchema, wasCancelRequested, withInterruptBinding, withTanstackMetadata, withoutInterruptBinding, wrapGenericInterruptContinuation };
@@ -250,6 +250,10 @@ export interface ModelMessage<TContent extends string | null | Array<ContentPart
250
250
  content: string;
251
251
  signature?: string;
252
252
  }>;
253
+ /** Error reported by an AG-UI tool message. */
254
+ error?: string;
255
+ /** Optional AG-UI message metadata. TanStack-owned fields live under `tanstack`. */
256
+ metadata?: Record<string, any>;
253
257
  /**
254
258
  * Completed structured output represented by this assistant message.
255
259
  * `content` remains the provider-facing JSON text; this field preserves the
@@ -310,10 +314,14 @@ export interface ToolCallPart<TMetadata = unknown> {
310
314
  }
311
315
  export interface ToolResultPart {
312
316
  type: 'tool-result';
317
+ id?: string;
318
+ name?: string;
313
319
  toolCallId: string;
314
320
  content: string | Array<ContentPart>;
315
321
  state: ToolResultState;
316
322
  error?: string;
323
+ metadata?: Record<string, unknown>;
324
+ createdAt?: Date;
317
325
  }
318
326
  export interface ThinkingPart {
319
327
  type: 'thinking';
@@ -385,9 +393,17 @@ export interface TanStackMessageMetadata {
385
393
  signature?: string;
386
394
  /** Per-tool-call provider metadata keyed by tool call id (e.g. Gemini thoughtSignature). */
387
395
  toolCallMetadata?: Record<string, unknown>;
396
+ toolResult?: {
397
+ id?: string;
398
+ createdAt?: string;
399
+ content?: Array<ContentPart>;
400
+ };
388
401
  structuredOutput?: {
389
402
  status?: 'streaming' | 'complete' | 'error';
403
+ partial?: unknown;
404
+ data?: unknown;
390
405
  raw?: string;
406
+ reasoning?: string;
391
407
  errorMessage?: string;
392
408
  };
393
409
  uiResources?: Array<UIResourcePart>;
@@ -421,6 +437,8 @@ export interface UIMessage<TData = unknown> {
421
437
  role: 'system' | 'user' | 'assistant';
422
438
  parts: Array<MessagePart<TData>>;
423
439
  createdAt?: Date;
440
+ /** Optional AG-UI sender name. Converters preserve it across wire and persist. */
441
+ name?: string;
424
442
  /**
425
443
  * Optional AG-UI metadata bag. TanStack writes the `tanstack` key.
426
444
  * User keys stay at the top.
@@ -1,51 +1,27 @@
1
- import { ContentPart, UIMessage } from '../types.js';
1
+ import { AssistantMessage, ReasoningMessage, SystemMessage, ToolMessage, UserMessage } from '@ag-ui/core';
2
+ import { ModelMessage, UIMessage } from '../types.js';
2
3
  import { MetadataRecord } from './merge-metadata.js';
3
- type AGUITextInputContent = {
4
- type: 'text';
5
- text: string;
6
- };
7
- type AGUIInputContent = AGUITextInputContent | (ContentPart & {
8
- type: 'image' | 'audio' | 'video' | 'document';
9
- });
10
- type AGUIToolCallMirror = {
11
- id: string;
12
- type: 'function';
13
- function: {
14
- name: string;
15
- arguments: string;
16
- };
17
- encryptedValue?: string;
18
- };
19
- type AGUIToolMessage = {
20
- role: 'tool';
21
- id: string;
22
- toolCallId: string;
23
- content: string;
24
- error?: string;
25
- };
26
- type AGUIReasoningMessage = {
27
- role: 'reasoning';
28
- id: string;
29
- content: string;
30
- encryptedValue?: string;
4
+ type WithMetadata<T> = T & {
31
5
  metadata?: MetadataRecord;
32
6
  };
33
- /** Spec AG-UI message. No `parts`, no `createdAt` Date. */
34
- type WireAnchorMessage = {
35
- id: string;
36
- role: UIMessage['role'];
7
+ type WireSystemMessage = WithMetadata<SystemMessage>;
8
+ type WireUserMessage = WithMetadata<UserMessage>;
9
+ type WireAssistantMessage = WithMetadata<AssistantMessage>;
10
+ type WireToolMessage = WithMetadata<ToolMessage & {
37
11
  name?: string;
38
- content?: string | Array<AGUIInputContent>;
39
- toolCalls?: Array<AGUIToolCallMirror>;
40
- metadata?: MetadataRecord;
41
- };
42
- export type WireMessage = WireAnchorMessage | AGUIToolMessage | AGUIReasoningMessage;
12
+ }>;
13
+ type WireReasoningMessage = WithMetadata<ReasoningMessage>;
14
+ export type WireMessage = WireSystemMessage | WireUserMessage | WireAssistantMessage | WireToolMessage | WireReasoningMessage;
43
15
  /**
44
- * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
45
- * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,
46
- * `toolCalls`, `metadata`). Tool results and thinking parts on assistant
47
- * messages are additionally emitted as fan-out `{role:'tool',...}` and
48
- * `{role:'reasoning',...}` entries for strict AG-UI server consumers.
16
+ * Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI
17
+ * `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,
18
+ * `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts
19
+ * on assistant messages are additionally emitted as fan-out
20
+ * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI
21
+ * server consumers. Set `includeSnapshotStructuredOutput` to retain complete
22
+ * structured-output metadata for UI snapshots.
49
23
  */
50
- export declare function uiMessagesToWire(messages: Array<UIMessage>): Array<WireMessage>;
24
+ export declare function uiMessagesToWire(messages: Array<UIMessage | ModelMessage>, options?: {
25
+ includeSnapshotStructuredOutput: boolean;
26
+ }): Array<WireMessage>;
51
27
  export {};
@@ -1,28 +1,74 @@
1
1
  import { tanstackMetadata } from "./merge-metadata.js";
2
+ import { normalizeToolResult } from "./tool-result.js";
3
+ import { coerceCreatedAt, modelMessageToUIMessage } from "../activities/chat/messages.js";
2
4
  //#region src/utilities/ag-ui-wire.ts
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function rebuiltToolMetadata(metadata, createdAt, id, content, anchorOwnsUiResources = false) {
9
+ const source = isRecord(metadata) ? metadata : {};
10
+ const tanstack = isRecord(source.tanstack) ? { ...source.tanstack } : {};
11
+ if (anchorOwnsUiResources) delete tanstack.uiResources;
12
+ const date = coerceCreatedAt(createdAt);
13
+ const toolResult = {
14
+ ...id !== void 0 ? { id } : {},
15
+ ...date && { createdAt: date.toISOString() },
16
+ ...Array.isArray(content) && { content }
17
+ };
18
+ const result = {
19
+ ...source,
20
+ tanstack: {
21
+ ...tanstack,
22
+ toolResult
23
+ }
24
+ };
25
+ return Object.keys(result).length ? result : void 0;
26
+ }
3
27
  /**
4
- * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
5
- * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,
6
- * `toolCalls`, `metadata`). Tool results and thinking parts on assistant
7
- * messages are additionally emitted as fan-out `{role:'tool',...}` and
8
- * `{role:'reasoning',...}` entries for strict AG-UI server consumers.
28
+ * Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI
29
+ * `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,
30
+ * `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts
31
+ * on assistant messages are additionally emitted as fan-out
32
+ * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI
33
+ * server consumers. Set `includeSnapshotStructuredOutput` to retain complete
34
+ * structured-output metadata for UI snapshots.
9
35
  */
10
- function uiMessagesToWire(messages) {
36
+ function uiMessagesToWire(messages, options) {
11
37
  const wire = [];
38
+ const usedWireIds = new Set(messages.flatMap((message) => "id" in message && message.id && message.role !== "tool" ? [message.id] : []));
39
+ const includeSnapshotStructuredOutput = options?.includeSnapshotStructuredOutput ?? false;
40
+ const assistantIds = /* @__PURE__ */ new Set();
41
+ for (const msg of messages) if (msg.role === "assistant" && msg.id !== void 0) assistantIds.add(msg.id);
12
42
  for (const msg of messages) {
13
- const parts = msg.parts ?? [];
43
+ if (!("parts" in msg) && msg.role === "tool" && msg.toolCallId) {
44
+ const id = uniqueToolWireId(toolWireId(msg.id, msg.toolCallId, assistantIds), usedWireIds);
45
+ const metadata = rebuiltToolMetadata(msg.metadata, msg.createdAt, msg.id, msg.content);
46
+ wire.push({
47
+ role: "tool",
48
+ id,
49
+ ...msg.name !== void 0 && { name: msg.name },
50
+ toolCallId: msg.toolCallId,
51
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
52
+ ...msg.error !== void 0 && { error: msg.error },
53
+ ...metadata !== void 0 && { metadata }
54
+ });
55
+ continue;
56
+ }
57
+ const uiMessage = "parts" in msg ? msg : modelMessageToUIMessage(msg, msg.id);
58
+ const parts = uiMessage.parts;
59
+ usedWireIds.add(uiMessage.id);
14
60
  if (msg.role === "system") {
15
- wire.push(toAnchor(msg, { content: parts.length > 0 ? collectText(parts) : msg.content ?? "" }, parts));
61
+ wire.push(toAnchor(uiMessage, "system", { content: parts.length > 0 ? collectText(parts) : msg.content ?? "" }, parts, includeSnapshotStructuredOutput));
16
62
  continue;
17
63
  }
18
64
  if (msg.role === "user") {
19
- wire.push(toAnchor(msg, { content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? "" }, parts));
65
+ wire.push(toAnchor(uiMessage, "user", { content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? "" }, parts, includeSnapshotStructuredOutput));
20
66
  continue;
21
67
  }
22
68
  for (const part of parts) if (part.type === "thinking") {
23
69
  const reasoning = {
24
70
  role: "reasoning",
25
- id: deriveReasoningId(msg.id, part),
71
+ id: uniqueWireId(deriveReasoningId(uiMessage.id, part), usedWireIds),
26
72
  content: part.content
27
73
  };
28
74
  if (part.signature) reasoning.encryptedValue = part.signature;
@@ -30,52 +76,96 @@ function uiMessagesToWire(messages) {
30
76
  }
31
77
  const text = collectText(parts);
32
78
  const toolCalls = collectToolCalls(parts);
33
- wire.push(toAnchor(msg, {
79
+ wire.push(toAnchor(uiMessage, "assistant", {
34
80
  ...text !== "" && { content: text },
35
81
  ...toolCalls && { toolCalls }
36
- }, parts));
37
- for (const part of parts) if (part.type === "tool-result") wire.push({
38
- role: "tool",
39
- id: deriveToolMessageId(part.toolCallId),
40
- toolCallId: part.toolCallId,
41
- content: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
42
- ...part.error !== void 0 && { error: part.error }
43
- });
82
+ }, parts, includeSnapshotStructuredOutput));
83
+ const explicitToolResults = new Set(parts.flatMap((part) => part.type === "tool-result" ? [part.toolCallId] : []));
84
+ for (const part of parts) if (part.type === "tool-result") {
85
+ const id = uniqueToolWireId(part.id ?? deriveToolMessageId(part.toolCallId), usedWireIds);
86
+ const metadata = rebuiltToolMetadata(part.metadata, part.createdAt, part.id, part.content, true);
87
+ wire.push({
88
+ role: "tool",
89
+ id,
90
+ toolCallId: part.toolCallId,
91
+ ...part.name !== void 0 && { name: part.name },
92
+ content: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
93
+ ...part.error !== void 0 && { error: part.error },
94
+ ...metadata !== void 0 && { metadata }
95
+ });
96
+ } else if (part.type === "tool-call") {
97
+ const approved = part.approval?.approved;
98
+ if (explicitToolResults.has(part.id) || part.output === void 0 && (part.state !== "approval-responded" || approved === void 0)) continue;
99
+ const result = part.output !== void 0 ? normalizeToolResult(part.output) : JSON.stringify({
100
+ approved,
101
+ ...approved && { pendingExecution: true },
102
+ message: approved ? "User approved this action" : "User denied this action"
103
+ });
104
+ const content = typeof result === "string" ? result : JSON.stringify(result);
105
+ wire.push({
106
+ role: "tool",
107
+ id: uniqueToolWireId(deriveToolMessageId(part.id), usedWireIds),
108
+ toolCallId: part.id,
109
+ content,
110
+ metadata: rebuiltToolMetadata(void 0, void 0, void 0, result)
111
+ });
112
+ }
44
113
  }
45
114
  return wire;
46
115
  }
47
- function toAnchor(msg, extras, parts) {
48
- const metadata = messageMetadata(msg, parts);
49
- const name = msg.name;
50
- return {
116
+ function toAnchor(msg, role, extras, parts, includeSnapshotStructuredOutput) {
117
+ const metadata = messageMetadata(msg, parts, includeSnapshotStructuredOutput);
118
+ const base = {
51
119
  id: msg.id,
52
- role: msg.role,
53
- ...name !== void 0 && { name },
54
- ...extras,
120
+ ...msg.name !== void 0 && { name: msg.name },
55
121
  ...metadata !== void 0 && { metadata }
56
122
  };
123
+ if (role === "system") return {
124
+ ...base,
125
+ role,
126
+ content: String(extras.content ?? "")
127
+ };
128
+ if (role === "user") return {
129
+ ...base,
130
+ role,
131
+ content: extras.content ?? ""
132
+ };
133
+ return {
134
+ ...base,
135
+ role,
136
+ ...typeof extras.content === "string" && { content: extras.content },
137
+ ...extras.toolCalls !== void 0 && { toolCalls: extras.toolCalls }
138
+ };
57
139
  }
58
- function messageMetadata(msg, parts) {
140
+ function messageMetadata(msg, parts, includeSnapshotStructuredOutput) {
59
141
  const base = { ...msg.metadata ?? {} };
60
- const tanstack = { ...tanstackMetadata(msg) ?? {} };
61
- if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString();
62
- const leftover = unfinishedStructuredOutput(parts);
63
- if (leftover) tanstack.structuredOutput = leftover;
142
+ const previousTanstack = tanstackMetadata(msg);
143
+ const tanstack = {};
144
+ if (previousTanstack?.model !== void 0) tanstack.model = previousTanstack.model;
145
+ if (previousTanstack?.signature !== void 0) tanstack.signature = previousTanstack.signature;
146
+ const createdAt = coerceCreatedAt(msg.createdAt);
147
+ if (createdAt !== void 0) tanstack.createdAt = createdAt.toISOString();
148
+ const structuredOutput = serializedStructuredOutput(parts, includeSnapshotStructuredOutput);
149
+ if (structuredOutput) tanstack.structuredOutput = structuredOutput;
64
150
  const toolCallMetadata = {};
65
151
  for (const part of parts) if (part.type === "tool-call" && part.metadata !== void 0) toolCallMetadata[part.id] = part.metadata;
66
152
  if (Object.keys(toolCallMetadata).length > 0) tanstack.toolCallMetadata = toolCallMetadata;
67
153
  const uiResources = parts.filter((p) => p.type === "ui-resource");
68
154
  if (uiResources.length > 0) tanstack.uiResources = uiResources;
69
155
  if (Object.keys(tanstack).length > 0) base.tanstack = tanstack;
156
+ else delete base.tanstack;
70
157
  return Object.keys(base).length > 0 ? base : void 0;
71
158
  }
72
- function unfinishedStructuredOutput(parts) {
73
- for (const p of parts) if (p.type === "structured-output" && p.status !== "complete") return structuredOutputLeftover(p);
159
+ function serializedStructuredOutput(parts, includeSnapshotStructuredOutput) {
160
+ for (const p of parts) if (p.type === "structured-output" && (includeSnapshotStructuredOutput || p.status !== "complete")) return structuredOutputMetadata(p, includeSnapshotStructuredOutput);
74
161
  }
75
- function structuredOutputLeftover(part) {
162
+ function structuredOutputMetadata(part, includeSnapshotStructuredOutput) {
76
163
  return {
77
164
  status: part.status,
78
165
  raw: part.raw,
166
+ ...includeSnapshotStructuredOutput && part.partial !== void 0 ? { partial: part.partial } : {},
167
+ ...includeSnapshotStructuredOutput && part.data !== void 0 ? { data: part.data } : {},
168
+ ...includeSnapshotStructuredOutput && part.reasoning ? { reasoning: part.reasoning } : {},
79
169
  ...part.errorMessage !== void 0 && { errorMessage: part.errorMessage }
80
170
  };
81
171
  }
@@ -123,6 +213,25 @@ function deriveReasoningId(messageId, part) {
123
213
  function deriveToolMessageId(toolCallId) {
124
214
  return `tool-${toolCallId}`;
125
215
  }
216
+ function uniqueToolWireId(id, used) {
217
+ return uniqueWireId(id, used);
218
+ }
219
+ function uniqueWireId(id, used) {
220
+ if (!used.has(id)) {
221
+ used.add(id);
222
+ return id;
223
+ }
224
+ let suffix = 2;
225
+ while (used.has(`${id}-${suffix}`)) suffix++;
226
+ const unique = `${id}-${suffix}`;
227
+ used.add(unique);
228
+ return unique;
229
+ }
230
+ function toolWireId(id, toolCallId, assistantIds) {
231
+ const derived = deriveToolMessageId(toolCallId);
232
+ if (id === void 0 || assistantIds.has(id)) return derived;
233
+ return id;
234
+ }
126
235
  function hashContent(s) {
127
236
  let h = 0;
128
237
  for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
@@ -1 +1 @@
1
- {"version":3,"file":"ag-ui-wire.js","names":[],"sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n StructuredOutputPart,\n TanStackMessageMetadata,\n UIMessage,\n UIResourcePart,\n} from '../types'\nimport type { MetadataRecord } from './merge-metadata'\nimport { tanstackMetadata } from './merge-metadata'\n\ntype AGUITextInputContent = { type: 'text'; text: string }\ntype AGUIInputContent =\n | AGUITextInputContent\n | (ContentPart & { type: 'image' | 'audio' | 'video' | 'document' })\n\ntype AGUIToolCallMirror = {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n encryptedValue?: string\n}\n\ntype AGUIToolMessage = {\n role: 'tool'\n id: string\n toolCallId: string\n content: string\n error?: string\n}\n\ntype AGUIReasoningMessage = {\n role: 'reasoning'\n id: string\n content: string\n encryptedValue?: string\n metadata?: MetadataRecord\n}\n\n/** Spec AG-UI message. No `parts`, no `createdAt` Date. */\ntype WireAnchorMessage = {\n id: string\n role: UIMessage['role']\n name?: string\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n metadata?: MetadataRecord\n}\n\nexport type WireMessage =\n | WireAnchorMessage\n | AGUIToolMessage\n | AGUIReasoningMessage\n\n/**\n * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`\n * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,\n * `toolCalls`, `metadata`). Tool results and thinking parts on assistant\n * messages are additionally emitted as fan-out `{role:'tool',...}` and\n * `{role:'reasoning',...}` entries for strict AG-UI server consumers.\n */\nexport function uiMessagesToWire(\n messages: Array<UIMessage>,\n): Array<WireMessage> {\n const wire: Array<WireMessage> = []\n\n for (const msg of messages) {\n // Defensive: ModelMessage-shaped input has no `parts`; fall back to `content`.\n const parts: ReadonlyArray<MessagePart> =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- runtime input may be ModelMessage-shaped (no `parts`); cast forces the optional-chain fallback below to remain in scope\n (msg.parts as ReadonlyArray<MessagePart> | undefined) ?? []\n\n if (msg.role === 'system') {\n wire.push(\n toAnchor(\n msg,\n {\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n ),\n )\n continue\n }\n\n if (msg.role === 'user') {\n wire.push(\n toAnchor(\n msg,\n {\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n ),\n )\n continue\n }\n\n // assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs\n for (const part of parts) {\n if (part.type === 'thinking') {\n const reasoning: AGUIReasoningMessage = {\n role: 'reasoning',\n id: deriveReasoningId(msg.id, part),\n content: part.content,\n }\n if (part.signature) {\n reasoning.encryptedValue = part.signature\n }\n wire.push(reasoning)\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push(\n toAnchor(\n msg,\n {\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\n },\n parts,\n ),\n )\n\n for (const part of parts) {\n if (part.type === 'tool-result') {\n wire.push({\n role: 'tool',\n id: deriveToolMessageId(part.toolCallId),\n toolCallId: part.toolCallId,\n content:\n typeof part.content === 'string'\n ? part.content\n : JSON.stringify(part.content),\n ...(part.error !== undefined && { error: part.error }),\n })\n }\n }\n }\n\n return wire\n}\n\nfunction toAnchor(\n msg: UIMessage,\n extras: {\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n },\n parts: ReadonlyArray<MessagePart>,\n): WireAnchorMessage {\n const metadata = messageMetadata(msg, parts)\n const name = (msg as { name?: string }).name\n return {\n id: msg.id,\n role: msg.role,\n ...(name !== undefined && { name }),\n ...extras,\n ...(metadata !== undefined && { metadata }),\n }\n}\n\nfunction messageMetadata(\n msg: UIMessage,\n parts: ReadonlyArray<MessagePart>,\n): MetadataRecord | undefined {\n const base: MetadataRecord = { ...(msg.metadata ?? {}) }\n const tanstack: MetadataRecord = { ...(tanstackMetadata(msg) ?? {}) }\n if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString()\n\n const leftover = unfinishedStructuredOutput(parts)\n if (leftover) tanstack.structuredOutput = leftover\n\n const toolCallMetadata: Record<string, unknown> = {}\n for (const part of parts) {\n if (part.type === 'tool-call' && part.metadata !== undefined) {\n toolCallMetadata[part.id] = part.metadata\n }\n }\n if (Object.keys(toolCallMetadata).length > 0) {\n tanstack.toolCallMetadata = toolCallMetadata\n }\n\n const uiResources = parts.filter(\n (p): p is UIResourcePart => p.type === 'ui-resource',\n )\n if (uiResources.length > 0) tanstack.uiResources = uiResources\n\n if (Object.keys(tanstack).length > 0) base.tanstack = tanstack\n return Object.keys(base).length > 0 ? base : undefined\n}\n\nfunction unfinishedStructuredOutput(\n parts: ReadonlyArray<MessagePart>,\n): TanStackMessageMetadata['structuredOutput'] | undefined {\n for (const p of parts) {\n if (p.type === 'structured-output' && p.status !== 'complete') {\n return structuredOutputLeftover(p)\n }\n }\n return undefined\n}\n\nfunction structuredOutputLeftover(\n part: StructuredOutputPart,\n): NonNullable<TanStackMessageMetadata['structuredOutput']> {\n return {\n status: part.status,\n raw: part.raw,\n ...(part.errorMessage !== undefined && { errorMessage: part.errorMessage }),\n }\n}\n\nfunction collectText(parts: ReadonlyArray<MessagePart>): string {\n // The streamed JSON of a completed structured-output part is the source of\n // truth for multi-turn coherence — emitting it back as assistant content\n // lets the LLM see its own prior structured response. Streaming/errored\n // parts are skipped: they'd ship malformed JSON fragments and confuse the\n // model. `completeStructuredOutputPart` tries hard to populate `raw`\n // (caller → existing buffer → `JSON.stringify(data)`), but the stringify\n // fallback can leave it empty when `data` is unserializable (BigInt,\n // circular). The `p.raw !== ''` guard below is what enforces \"no malformed\n // round-trip\" in that case — without it we'd ship `''` and the model would\n // see an empty assistant turn.\n const out: Array<string> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push(p.content)\n } else if (\n p.type === 'structured-output' &&\n p.status === 'complete' &&\n p.raw !== ''\n ) {\n out.push(p.raw)\n }\n }\n return out.join('')\n}\n\nfunction collectUserContent(\n parts: ReadonlyArray<MessagePart>,\n): string | Array<AGUIInputContent> {\n const hasMultimodal = parts.some(\n (p) =>\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document',\n )\n if (!hasMultimodal) {\n return collectText(parts)\n }\n const out: Array<AGUIInputContent> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push({ type: 'text', text: p.content })\n } else if (\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document'\n ) {\n out.push(p)\n }\n }\n return out\n}\n\nfunction thoughtSignatureFromMetadata(metadata: unknown): string | undefined {\n if (\n metadata == null ||\n typeof metadata !== 'object' ||\n Array.isArray(metadata)\n ) {\n return undefined\n }\n if (!('thoughtSignature' in metadata)) return undefined\n const value = metadata.thoughtSignature\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nfunction collectToolCalls(\n parts: ReadonlyArray<MessagePart>,\n): Array<AGUIToolCallMirror> | undefined {\n const calls: Array<AGUIToolCallMirror> = []\n for (const p of parts) {\n if (p.type === 'tool-call') {\n const encryptedValue = thoughtSignatureFromMetadata(p.metadata)\n calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\n ...(encryptedValue !== undefined ? { encryptedValue } : {}),\n })\n }\n }\n return calls.length > 0 ? calls : undefined\n}\n\nfunction deriveReasoningId(messageId: string, part: MessagePart): string {\n return `${messageId}-reasoning-${(part as { id?: string }).id ?? hashContent((part as { content: string }).content)}`\n}\n\nfunction deriveToolMessageId(toolCallId: string): string {\n return `tool-${toolCallId}`\n}\n\nfunction hashContent(s: string): string {\n // Cheap deterministic id suffix; collisions are tolerable since\n // reasoning ids only matter for AG-UI server consumers, not for our\n // own server's dedup logic (which keys on toolCallId, not reasoning id).\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0\n return Math.abs(h).toString(36)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ag-ui-wire.js","names":[],"sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type {\n AssistantMessage,\n InputContent,\n ReasoningMessage,\n SystemMessage,\n ToolCall,\n ToolMessage,\n UserMessage,\n} from '@ag-ui/core'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StructuredOutputPart,\n TanStackMessageMetadata,\n UIMessage,\n UIResourcePart,\n} from '../types'\nimport type { MetadataRecord } from './merge-metadata'\nimport { tanstackMetadata } from './merge-metadata'\nimport { normalizeToolResult } from './tool-result'\nimport {\n coerceCreatedAt,\n modelMessageToUIMessage,\n} from '../activities/chat/messages'\n\ntype WithMetadata<T> = T & { metadata?: MetadataRecord }\ntype WireSystemMessage = WithMetadata<SystemMessage>\ntype WireUserMessage = WithMetadata<UserMessage>\ntype WireAssistantMessage = WithMetadata<AssistantMessage>\ntype WireToolMessage = WithMetadata<\n ToolMessage & {\n name?: string\n }\n>\ntype WireReasoningMessage = WithMetadata<ReasoningMessage>\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction rebuiltToolMetadata(\n metadata: unknown,\n createdAt: unknown,\n id: string | undefined,\n content: string | null | Array<ContentPart>,\n anchorOwnsUiResources = false,\n): MetadataRecord | undefined {\n const source: MetadataRecord = isRecord(metadata) ? metadata : {}\n const tanstack = isRecord(source.tanstack) ? { ...source.tanstack } : {}\n if (anchorOwnsUiResources) delete tanstack.uiResources\n const date = coerceCreatedAt(createdAt)\n const toolResult: NonNullable<TanStackMessageMetadata['toolResult']> = {\n ...(id !== undefined ? { id } : {}),\n ...(date && { createdAt: date.toISOString() }),\n ...(Array.isArray(content) && { content }),\n }\n const result = {\n ...source,\n tanstack: { ...tanstack, toolResult },\n }\n return Object.keys(result).length ? result : undefined\n}\n\nexport type WireMessage =\n | WireSystemMessage\n | WireUserMessage\n | WireAssistantMessage\n | WireToolMessage\n | WireReasoningMessage\n\n/**\n * Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI\n * `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,\n * `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts\n * on assistant messages are additionally emitted as fan-out\n * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI\n * server consumers. Set `includeSnapshotStructuredOutput` to retain complete\n * structured-output metadata for UI snapshots.\n */\nexport function uiMessagesToWire(\n messages: Array<UIMessage | ModelMessage>,\n options?: { includeSnapshotStructuredOutput: boolean },\n): Array<WireMessage> {\n const wire: Array<WireMessage> = []\n const usedWireIds = new Set<string>(\n messages.flatMap((message) =>\n 'id' in message && message.id && message.role !== 'tool'\n ? [message.id]\n : [],\n ),\n )\n const includeSnapshotStructuredOutput =\n options?.includeSnapshotStructuredOutput ?? false\n\n const assistantIds = new Set<string>()\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.id !== undefined) {\n assistantIds.add(msg.id)\n }\n }\n\n for (const msg of messages) {\n if (!('parts' in msg) && msg.role === 'tool' && msg.toolCallId) {\n const id = uniqueToolWireId(\n toolWireId(msg.id, msg.toolCallId, assistantIds),\n usedWireIds,\n )\n const metadata = rebuiltToolMetadata(\n msg.metadata,\n msg.createdAt,\n msg.id,\n msg.content,\n )\n wire.push({\n role: 'tool',\n id,\n ...(msg.name !== undefined && { name: msg.name }),\n toolCallId: msg.toolCallId,\n content:\n typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content),\n ...(msg.error !== undefined && { error: msg.error }),\n ...(metadata !== undefined && { metadata }),\n })\n continue\n }\n\n const uiMessage: UIMessage =\n 'parts' in msg ? msg : modelMessageToUIMessage(msg, msg.id)\n const parts: ReadonlyArray<MessagePart> = uiMessage.parts\n usedWireIds.add(uiMessage.id)\n\n if (msg.role === 'system') {\n wire.push(\n toAnchor(\n uiMessage,\n 'system',\n {\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n includeSnapshotStructuredOutput,\n ),\n )\n continue\n }\n\n if (msg.role === 'user') {\n wire.push(\n toAnchor(\n uiMessage,\n 'user',\n {\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n includeSnapshotStructuredOutput,\n ),\n )\n continue\n }\n\n // assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs\n for (const part of parts) {\n if (part.type === 'thinking') {\n const reasoning: WireReasoningMessage = {\n role: 'reasoning',\n id: uniqueWireId(deriveReasoningId(uiMessage.id, part), usedWireIds),\n content: part.content,\n }\n if (part.signature) {\n reasoning.encryptedValue = part.signature\n }\n wire.push(reasoning)\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push(\n toAnchor(\n uiMessage,\n 'assistant',\n {\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\n },\n parts,\n includeSnapshotStructuredOutput,\n ),\n )\n\n const explicitToolResults = new Set(\n parts.flatMap((part) =>\n part.type === 'tool-result' ? [part.toolCallId] : [],\n ),\n )\n for (const part of parts) {\n if (part.type === 'tool-result') {\n const id = uniqueToolWireId(\n part.id ?? deriveToolMessageId(part.toolCallId),\n usedWireIds,\n )\n const metadata = rebuiltToolMetadata(\n part.metadata,\n part.createdAt,\n part.id,\n part.content,\n true,\n )\n wire.push({\n role: 'tool',\n id,\n toolCallId: part.toolCallId,\n ...(part.name !== undefined && { name: part.name }),\n content:\n typeof part.content === 'string'\n ? part.content\n : JSON.stringify(part.content),\n ...(part.error !== undefined && { error: part.error }),\n ...(metadata !== undefined && { metadata }),\n })\n } else if (part.type === 'tool-call') {\n const approved = part.approval?.approved\n if (\n explicitToolResults.has(part.id) ||\n (part.output === undefined &&\n (part.state !== 'approval-responded' || approved === undefined))\n ) {\n continue\n }\n const result =\n part.output !== undefined\n ? normalizeToolResult(part.output)\n : JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n })\n const content =\n typeof result === 'string' ? result : JSON.stringify(result)\n wire.push({\n role: 'tool',\n id: uniqueToolWireId(deriveToolMessageId(part.id), usedWireIds),\n toolCallId: part.id,\n content,\n metadata: rebuiltToolMetadata(\n undefined,\n undefined,\n undefined,\n result,\n ),\n })\n }\n }\n }\n\n return wire\n}\n\nfunction toAnchor(\n msg: UIMessage,\n role: 'system',\n extras: { content: string },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireSystemMessage\nfunction toAnchor(\n msg: UIMessage,\n role: 'user',\n extras: { content: string | Array<InputContent> },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireUserMessage\nfunction toAnchor(\n msg: UIMessage,\n role: 'assistant',\n extras: {\n content?: string\n toolCalls?: Array<ToolCall>\n },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireAssistantMessage\nfunction toAnchor(\n msg: UIMessage,\n role: UIMessage['role'],\n extras: {\n content?: string | Array<InputContent>\n toolCalls?: Array<ToolCall>\n },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireSystemMessage | WireUserMessage | WireAssistantMessage {\n const metadata = messageMetadata(msg, parts, includeSnapshotStructuredOutput)\n const base = {\n id: msg.id,\n ...(msg.name !== undefined && { name: msg.name }),\n ...(metadata !== undefined && { metadata }),\n }\n if (role === 'system') {\n return { ...base, role, content: String(extras.content ?? '') }\n }\n if (role === 'user') {\n return { ...base, role, content: extras.content ?? '' }\n }\n return {\n ...base,\n role,\n ...(typeof extras.content === 'string' && { content: extras.content }),\n ...(extras.toolCalls !== undefined && { toolCalls: extras.toolCalls }),\n }\n}\n\nfunction messageMetadata(\n msg: UIMessage,\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): MetadataRecord | undefined {\n const base: MetadataRecord = { ...(msg.metadata ?? {}) }\n const previousTanstack = tanstackMetadata(msg)\n const tanstack: TanStackMessageMetadata = {}\n if (previousTanstack?.model !== undefined)\n tanstack.model = previousTanstack.model\n if (previousTanstack?.signature !== undefined)\n tanstack.signature = previousTanstack.signature\n const createdAt = coerceCreatedAt(msg.createdAt)\n if (createdAt !== undefined) tanstack.createdAt = createdAt.toISOString()\n\n const structuredOutput = serializedStructuredOutput(\n parts,\n includeSnapshotStructuredOutput,\n )\n if (structuredOutput) tanstack.structuredOutput = structuredOutput\n\n const toolCallMetadata: Record<string, unknown> = {}\n for (const part of parts) {\n if (part.type === 'tool-call' && part.metadata !== undefined) {\n toolCallMetadata[part.id] = part.metadata\n }\n }\n if (Object.keys(toolCallMetadata).length > 0) {\n tanstack.toolCallMetadata = toolCallMetadata\n }\n\n const uiResources = parts.filter(\n (p): p is UIResourcePart => p.type === 'ui-resource',\n )\n if (uiResources.length > 0) tanstack.uiResources = uiResources\n\n if (Object.keys(tanstack).length > 0) base.tanstack = tanstack\n else delete base.tanstack\n return Object.keys(base).length > 0 ? base : undefined\n}\n\nfunction serializedStructuredOutput(\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): TanStackMessageMetadata['structuredOutput'] | undefined {\n for (const p of parts) {\n if (\n p.type === 'structured-output' &&\n (includeSnapshotStructuredOutput || p.status !== 'complete')\n ) {\n return structuredOutputMetadata(p, includeSnapshotStructuredOutput)\n }\n }\n return undefined\n}\n\nfunction structuredOutputMetadata(\n part: StructuredOutputPart,\n includeSnapshotStructuredOutput: boolean,\n): NonNullable<TanStackMessageMetadata['structuredOutput']> {\n return {\n status: part.status,\n raw: part.raw,\n ...(includeSnapshotStructuredOutput && part.partial !== undefined\n ? { partial: part.partial }\n : {}),\n ...(includeSnapshotStructuredOutput && part.data !== undefined\n ? { data: part.data }\n : {}),\n ...(includeSnapshotStructuredOutput && part.reasoning\n ? { reasoning: part.reasoning }\n : {}),\n ...(part.errorMessage !== undefined && { errorMessage: part.errorMessage }),\n }\n}\n\nfunction collectText(parts: ReadonlyArray<MessagePart>): string {\n // The streamed JSON of a completed structured-output part is the source of\n // truth for multi-turn coherence — emitting it back as assistant content\n // lets the LLM see its own prior structured response. Streaming/errored\n // parts are skipped: they'd ship malformed JSON fragments and confuse the\n // model. `completeStructuredOutputPart` tries hard to populate `raw`\n // (caller → existing buffer → `JSON.stringify(data)`), but the stringify\n // fallback can leave it empty when `data` is unserializable (BigInt,\n // circular). The `p.raw !== ''` guard below is what enforces \"no malformed\n // round-trip\" in that case — without it we'd ship `''` and the model would\n // see an empty assistant turn.\n const out: Array<string> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push(p.content)\n } else if (\n p.type === 'structured-output' &&\n p.status === 'complete' &&\n p.raw !== ''\n ) {\n out.push(p.raw)\n }\n }\n return out.join('')\n}\n\nfunction collectUserContent(\n parts: ReadonlyArray<MessagePart>,\n): string | Array<InputContent> {\n const hasMultimodal = parts.some(\n (p) =>\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document',\n )\n if (!hasMultimodal) {\n return collectText(parts)\n }\n const out: Array<InputContent> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push({ type: 'text', text: p.content })\n } else if (\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document'\n ) {\n out.push(p)\n }\n }\n return out\n}\n\nfunction thoughtSignatureFromMetadata(metadata: unknown): string | undefined {\n if (\n metadata == null ||\n typeof metadata !== 'object' ||\n Array.isArray(metadata)\n ) {\n return undefined\n }\n if (!('thoughtSignature' in metadata)) return undefined\n const value = metadata.thoughtSignature\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nfunction collectToolCalls(\n parts: ReadonlyArray<MessagePart>,\n): Array<ToolCall> | undefined {\n const calls: Array<ToolCall> = []\n for (const p of parts) {\n if (p.type === 'tool-call') {\n const encryptedValue = thoughtSignatureFromMetadata(p.metadata)\n calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\n ...(encryptedValue !== undefined ? { encryptedValue } : {}),\n })\n }\n }\n return calls.length > 0 ? calls : undefined\n}\n\nfunction deriveReasoningId(messageId: string, part: MessagePart): string {\n return `${messageId}-reasoning-${(part as { id?: string }).id ?? hashContent((part as { content: string }).content)}`\n}\n\nfunction deriveToolMessageId(toolCallId: string): string {\n return `tool-${toolCallId}`\n}\n\nfunction uniqueToolWireId(id: string, used: Set<string>): string {\n return uniqueWireId(id, used)\n}\n\nfunction uniqueWireId(id: string, used: Set<string>): string {\n if (!used.has(id)) {\n used.add(id)\n return id\n }\n let suffix = 2\n while (used.has(`${id}-${suffix}`)) suffix++\n const unique = `${id}-${suffix}`\n used.add(unique)\n return unique\n}\n\nfunction toolWireId(\n id: string | undefined,\n toolCallId: string,\n assistantIds: ReadonlySet<string>,\n): string {\n const derived = deriveToolMessageId(toolCallId)\n if (id === undefined || assistantIds.has(id)) return derived\n return id\n}\n\nfunction hashContent(s: string): string {\n // Cheap deterministic id suffix; collisions are tolerable since\n // reasoning ids only matter for AG-UI server consumers, not for our\n // own server's dedup logic (which keys on toolCallId, not reasoning id).\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0\n return Math.abs(h).toString(36)\n}\n"],"mappings":"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"}
@@ -1,4 +1,31 @@
1
1
  import { StreamChunk } from '../types.js';
2
+ /**
3
+ * Thrown when a skills request exceeds a provider limit. Lives in core (rather
4
+ * than `@tanstack/ai-skills`) so the native tool factories in `ai-anthropic`
5
+ * and `openai-base` can throw it without depending on the skills package;
6
+ * `@tanstack/ai-skills` re-exports it for the portable path.
7
+ *
8
+ * `path` distinguishes the native provider cap (e.g. Anthropic's 8-skill
9
+ * limit) from a portable-path limit, so a portable user isn't sent chasing a
10
+ * cap that only applies to hosted skills.
11
+ */
12
+ export interface SkillLimitErrorInit {
13
+ provider: 'anthropic' | 'openai' | 'gemini' | 'other';
14
+ path: 'native' | 'portable';
15
+ limit: string;
16
+ allowed: number;
17
+ actual: number;
18
+ offending: Array<string>;
19
+ }
20
+ export declare class SkillLimitError extends Error {
21
+ readonly provider: 'anthropic' | 'openai' | 'gemini' | 'other';
22
+ readonly path: 'native' | 'portable';
23
+ readonly limit: string;
24
+ readonly allowed: number;
25
+ readonly actual: number;
26
+ readonly offending: Array<string>;
27
+ constructor(init: SkillLimitErrorInit);
28
+ }
2
29
  /**
3
30
  * Best-effort extraction of a human-readable message from an unknown thrown
4
31
  * value, returning `undefined` when none can be found.
@@ -1,4 +1,22 @@
1
1
  //#region src/utilities/errors.ts
2
+ var SkillLimitError = class extends Error {
3
+ provider;
4
+ path;
5
+ limit;
6
+ allowed;
7
+ actual;
8
+ offending;
9
+ constructor(init) {
10
+ super(`${init.provider} ${init.path} skills limit exceeded (${init.limit}): ${init.actual} > ${init.allowed}`);
11
+ this.name = "SkillLimitError";
12
+ this.provider = init.provider;
13
+ this.path = init.path;
14
+ this.limit = init.limit;
15
+ this.allowed = init.allowed;
16
+ this.actual = init.actual;
17
+ this.offending = init.offending;
18
+ }
19
+ };
2
20
  /**
3
21
  * Best-effort extraction of a human-readable message from an unknown thrown
4
22
  * value, returning `undefined` when none can be found.
@@ -39,6 +57,6 @@ function runErrorEventToError(chunk) {
39
57
  return error;
40
58
  }
41
59
  //#endregion
42
- export { errorMessage, errorTypeName, runErrorEventToError };
60
+ export { SkillLimitError, errorMessage, errorTypeName, runErrorEventToError };
43
61
 
44
62
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../../src/utilities/errors.ts"],"sourcesContent":["import type { StreamChunk } from '../types'\n\n/**\n * Best-effort extraction of a human-readable message from an unknown thrown\n * value, returning `undefined` when none can be found.\n *\n * Used by `otelMiddleware` so error reporting stays identical across chat and\n * media spans.\n */\nexport function errorMessage(err: unknown): string | undefined {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n if (err && typeof err === 'object' && 'message' in err) {\n const m = (err as { message?: unknown }).message\n if (typeof m === 'string') return m\n }\n return undefined\n}\n\n/**\n * Best-effort extraction of an error's type name (used for the `error.type`\n * metric attribute), falling back to `'Error'` when no name is available.\n */\nexport function errorTypeName(err: unknown): string {\n if (err instanceof Error) return err.name || 'Error'\n if (err && typeof err === 'object' && 'name' in err) {\n const n = (err as { name?: unknown }).name\n if (typeof n === 'string') return n\n }\n return 'Error'\n}\n\n/**\n * Convert an AG-UI RUN_ERROR event to the Error shape exposed to consumers.\n * Preserves the provider code and sanitized raw event when available, while\n * accepting the deprecated nested error payload for backward compatibility.\n */\nexport function runErrorEventToError(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n): Error {\n const error = new Error(\n chunk.message || chunk.error?.message || 'An error occurred',\n )\n const code = chunk.code ?? chunk.error?.code\n if (code !== undefined) {\n Object.assign(error, { code })\n }\n if (chunk.rawEvent !== undefined) {\n Object.assign(error, { rawEvent: chunk.rawEvent })\n }\n return error\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,aAAa,KAAkC;CAC7D,IAAI,eAAe,OAAO,OAAO,IAAI;CACrC,IAAI,OAAO,QAAQ,UAAU,OAAO;CACpC,IAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,KAAK;EACtD,MAAM,IAAK,IAA8B;EACzC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;AAEF;;;;;AAMA,SAAgB,cAAc,KAAsB;CAClD,IAAI,eAAe,OAAO,OAAO,IAAI,QAAQ;CAC7C,IAAI,OAAO,OAAO,QAAQ,YAAY,UAAU,KAAK;EACnD,MAAM,IAAK,IAA2B;EACtC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,qBACd,OACO;CACP,MAAM,QAAQ,IAAI,MAChB,MAAM,WAAW,MAAM,OAAO,WAAW,mBAC3C;CACA,MAAM,OAAO,MAAM,QAAQ,MAAM,OAAO;CACxC,IAAI,SAAS,KAAA,GACX,OAAO,OAAO,OAAO,EAAE,KAAK,CAAC;CAE/B,IAAI,MAAM,aAAa,KAAA,GACrB,OAAO,OAAO,OAAO,EAAE,UAAU,MAAM,SAAS,CAAC;CAEnD,OAAO;AACT"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../../src/utilities/errors.ts"],"sourcesContent":["import type { StreamChunk } from '../types'\n\n/**\n * Thrown when a skills request exceeds a provider limit. Lives in core (rather\n * than `@tanstack/ai-skills`) so the native tool factories in `ai-anthropic`\n * and `openai-base` can throw it without depending on the skills package;\n * `@tanstack/ai-skills` re-exports it for the portable path.\n *\n * `path` distinguishes the native provider cap (e.g. Anthropic's 8-skill\n * limit) from a portable-path limit, so a portable user isn't sent chasing a\n * cap that only applies to hosted skills.\n */\nexport interface SkillLimitErrorInit {\n provider: 'anthropic' | 'openai' | 'gemini' | 'other'\n path: 'native' | 'portable'\n limit: string\n allowed: number\n actual: number\n offending: Array<string>\n}\n\nexport class SkillLimitError extends Error {\n readonly provider: 'anthropic' | 'openai' | 'gemini' | 'other'\n readonly path: 'native' | 'portable'\n readonly limit: string\n readonly allowed: number\n readonly actual: number\n readonly offending: Array<string>\n\n constructor(init: SkillLimitErrorInit) {\n super(\n `${init.provider} ${init.path} skills limit exceeded (${init.limit}): ` +\n `${init.actual} > ${init.allowed}`,\n )\n this.name = 'SkillLimitError'\n this.provider = init.provider\n this.path = init.path\n this.limit = init.limit\n this.allowed = init.allowed\n this.actual = init.actual\n this.offending = init.offending\n }\n}\n\n/**\n * Best-effort extraction of a human-readable message from an unknown thrown\n * value, returning `undefined` when none can be found.\n *\n * Used by `otelMiddleware` so error reporting stays identical across chat and\n * media spans.\n */\nexport function errorMessage(err: unknown): string | undefined {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n if (err && typeof err === 'object' && 'message' in err) {\n const m = (err as { message?: unknown }).message\n if (typeof m === 'string') return m\n }\n return undefined\n}\n\n/**\n * Best-effort extraction of an error's type name (used for the `error.type`\n * metric attribute), falling back to `'Error'` when no name is available.\n */\nexport function errorTypeName(err: unknown): string {\n if (err instanceof Error) return err.name || 'Error'\n if (err && typeof err === 'object' && 'name' in err) {\n const n = (err as { name?: unknown }).name\n if (typeof n === 'string') return n\n }\n return 'Error'\n}\n\n/**\n * Convert an AG-UI RUN_ERROR event to the Error shape exposed to consumers.\n * Preserves the provider code and sanitized raw event when available, while\n * accepting the deprecated nested error payload for backward compatibility.\n */\nexport function runErrorEventToError(\n chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,\n): Error {\n const error = new Error(\n chunk.message || chunk.error?.message || 'An error occurred',\n )\n const code = chunk.code ?? chunk.error?.code\n if (code !== undefined) {\n Object.assign(error, { code })\n }\n if (chunk.rawEvent !== undefined) {\n Object.assign(error, { rawEvent: chunk.rawEvent })\n }\n return error\n}\n"],"mappings":";AAqBA,IAAa,kBAAb,cAAqC,MAAM;CACzC;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,MAA2B;EACrC,MACE,GAAG,KAAK,SAAS,GAAG,KAAK,KAAK,0BAA0B,KAAK,MAAM,KAC9D,KAAK,OAAO,KAAK,KAAK,SAC7B;EACA,KAAK,OAAO;EACZ,KAAK,WAAW,KAAK;EACrB,KAAK,OAAO,KAAK;EACjB,KAAK,QAAQ,KAAK;EAClB,KAAK,UAAU,KAAK;EACpB,KAAK,SAAS,KAAK;EACnB,KAAK,YAAY,KAAK;CACxB;AACF;;;;;;;;AASA,SAAgB,aAAa,KAAkC;CAC7D,IAAI,eAAe,OAAO,OAAO,IAAI;CACrC,IAAI,OAAO,QAAQ,UAAU,OAAO;CACpC,IAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,KAAK;EACtD,MAAM,IAAK,IAA8B;EACzC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;AAEF;;;;;AAMA,SAAgB,cAAc,KAAsB;CAClD,IAAI,eAAe,OAAO,OAAO,IAAI,QAAQ;CAC7C,IAAI,OAAO,OAAO,QAAQ,YAAY,UAAU,KAAK;EACnD,MAAM,IAAK,IAA2B;EACtC,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,qBACd,OACO;CACP,MAAM,QAAQ,IAAI,MAChB,MAAM,WAAW,MAAM,OAAO,WAAW,mBAC3C;CACA,MAAM,OAAO,MAAM,QAAQ,MAAM,OAAO;CACxC,IAAI,SAAS,KAAA,GACX,OAAO,OAAO,OAAO,EAAE,KAAK,CAAC;CAE/B,IAAI,MAAM,aAAa,KAAA,GACrB,OAAO,OAAO,OAAO,EAAE,UAAU,MAAM,SAAS,CAAC;CAEnD,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.49.1",
3
+ "version": "0.51.0",
4
4
  "description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -88,7 +88,7 @@
88
88
  "@ag-ui/core": "0.1.1-canary.beta.0",
89
89
  "@standard-schema/spec": "^1.1.0",
90
90
  "partial-json": "^0.1.7",
91
- "@tanstack/ai-event-client": "^0.10.0",
91
+ "@tanstack/ai-event-client": "^0.11.1",
92
92
  "@tanstack/ai-utils": "^0.4.0"
93
93
  },
94
94
  "peerDependencies": {
@@ -37,6 +37,7 @@ Always import from the framework package on the client — never from
37
37
  | Turn on/off debug logging, pipe into pino/winston | ai-core/debug-logging/SKILL.md |
38
38
  | Persist chats server-side (history, runs) | See `@tanstack/ai-persistence` package skills |
39
39
  | Set up Code Mode (LLM code execution) | See `@tanstack/ai-code-mode` package skills |
40
+ | Give the model a catalog of SKILL.md skills | See `@tanstack/ai-skills` package skills |
40
41
 
41
42
  ## Companion packages
42
43
 
@@ -84,6 +85,22 @@ framework packages, so read **ai-core/client-persistence** instead.
84
85
 
85
86
  See the `ai-code-mode` skill in that package.
86
87
 
88
+ ### `@tanstack/ai-skills` — portable Agent Skills at runtime
89
+
90
+ Gives the model a library of `SKILL.md` skills it can load on demand, on any
91
+ provider, via the `withSkills` middleware and a `load_skill` tool. Skills come
92
+ from `inlineSkill`, `skillDirectory`, or a build-time bundle. This is the
93
+ runtime feature for the model **inside your app**, not the coding-assistant
94
+ skills this file is part of, and not the hosted `codeExecutionTool` /
95
+ `shellTool` skills (those run in a provider sandbox).
96
+
97
+ ```bash
98
+ pnpm add @tanstack/ai-skills
99
+ npx @tanstack/intent@latest install
100
+ ```
101
+
102
+ Entry point: `node_modules/@tanstack/ai-skills/skills/ai-skills/SKILL.md`
103
+
87
104
  ## Quick Decision Tree
88
105
 
89
106
  - Setting up a chatbot? → ai-core/chat-experience