@tanstack/ai 0.23.0 → 0.24.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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/activities/chat/index.d.ts +33 -9
  3. package/dist/esm/activities/chat/index.js +19 -9
  4. package/dist/esm/activities/chat/index.js.map +1 -1
  5. package/dist/esm/activities/chat/messages.js +2 -1
  6. package/dist/esm/activities/chat/messages.js.map +1 -1
  7. package/dist/esm/activities/chat/middleware/compose.d.ts +14 -14
  8. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  9. package/dist/esm/activities/chat/middleware/types.d.ts +16 -16
  10. package/dist/esm/activities/chat/runtime-context-types.d.ts +43 -0
  11. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  12. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  13. package/dist/esm/activities/chat/stream/processor.d.ts +1 -0
  14. package/dist/esm/activities/chat/stream/processor.js +35 -12
  15. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  16. package/dist/esm/activities/chat/tools/schema-converter.js +10 -3
  17. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  18. package/dist/esm/activities/chat/tools/tool-calls.d.ts +15 -5
  19. package/dist/esm/activities/chat/tools/tool-calls.js +59 -19
  20. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  21. package/dist/esm/activities/chat/tools/tool-definition.d.ts +12 -8
  22. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  23. package/dist/esm/activities/error-payload.d.ts +26 -0
  24. package/dist/esm/activities/error-payload.js +12 -1
  25. package/dist/esm/activities/error-payload.js.map +1 -1
  26. package/dist/esm/adapter-internals.d.ts +1 -1
  27. package/dist/esm/adapter-internals.js +3 -2
  28. package/dist/esm/client.d.ts +1 -1
  29. package/dist/esm/client.js +3 -1
  30. package/dist/esm/client.js.map +1 -1
  31. package/dist/esm/index.d.ts +2 -1
  32. package/dist/esm/index.js +7 -1
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/tool-registry.d.ts +7 -7
  35. package/dist/esm/tool-registry.js +1 -1
  36. package/dist/esm/tool-registry.js.map +1 -1
  37. package/dist/esm/types.d.ts +40 -8
  38. package/dist/esm/utilities/ag-ui-wire.js +1 -1
  39. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  40. package/dist/esm/utilities/chat-params.d.ts +8 -3
  41. package/dist/esm/utilities/chat-params.js +6 -2
  42. package/dist/esm/utilities/chat-params.js.map +1 -1
  43. package/dist/esm/utilities/tool-result.d.ts +21 -0
  44. package/dist/esm/utilities/tool-result.js +37 -0
  45. package/dist/esm/utilities/tool-result.js.map +1 -0
  46. package/package.json +3 -2
  47. package/src/activities/chat/index.ts +219 -47
  48. package/src/activities/chat/messages.ts +2 -1
  49. package/src/activities/chat/middleware/compose.ts +23 -17
  50. package/src/activities/chat/middleware/types.ts +16 -16
  51. package/src/activities/chat/runtime-context-types.ts +68 -0
  52. package/src/activities/chat/stream/message-updaters.ts +2 -1
  53. package/src/activities/chat/stream/processor.ts +48 -8
  54. package/src/activities/chat/tools/schema-converter.ts +32 -14
  55. package/src/activities/chat/tools/tool-calls.ts +138 -43
  56. package/src/activities/chat/tools/tool-definition.ts +25 -31
  57. package/src/activities/error-payload.ts +44 -0
  58. package/src/adapter-internals.ts +4 -1
  59. package/src/client.ts +5 -1
  60. package/src/index.ts +7 -0
  61. package/src/tool-registry.ts +16 -14
  62. package/src/types.ts +86 -29
  63. package/src/utilities/ag-ui-wire.ts +4 -1
  64. package/src/utilities/chat-params.ts +22 -7
  65. package/src/utilities/tool-result.ts +60 -0
@@ -1 +1 @@
1
- {"version":3,"file":"error-payload.js","sources":["../../../src/activities/error-payload.ts"],"sourcesContent":["/**\n * Shared error-narrowing helper for activities that convert thrown values\n * into structured `RUN_ERROR` events.\n *\n * Accepts Error instances, objects with string-ish `message`/`code`, or bare\n * strings; always returns a shape safe to serialize. Never leaks the full\n * error object (which may carry request/response state from an SDK).\n *\n * Abort-shaped errors (DOM `AbortError`, OpenAI `APIUserAbortError`,\n * OpenRouter `RequestAbortedError`) are normalized to a stable\n * `{ message: 'Request aborted', code: 'aborted' }` shape so callers can\n * discriminate user-initiated cancellation from other failures without\n * matching on provider-specific message strings.\n */\nconst ABORT_ERROR_NAMES = new Set([\n 'AbortError',\n 'APIUserAbortError',\n 'RequestAbortedError',\n])\n\n// HTTP status codes carried as numbers (e.g. `error.status = 429`) are a\n// common variant on SDK error classes; coerce so the resulting `code` field\n// is stable as a string for downstream consumers.\nfunction normalizeCode(codeField: unknown): string | undefined {\n if (typeof codeField === 'string') return codeField\n if (typeof codeField === 'number' && Number.isFinite(codeField)) {\n return String(codeField)\n }\n return undefined\n}\n\nexport function toRunErrorPayload(\n error: unknown,\n fallbackMessage = 'Unknown error occurred',\n): { message: string; code: string | undefined } {\n if (error && typeof error === 'object') {\n const name = (error as { name?: unknown }).name\n if (typeof name === 'string' && ABORT_ERROR_NAMES.has(name)) {\n return { message: 'Request aborted', code: 'aborted' }\n }\n }\n if (error instanceof Error) {\n const codeField = (error as Error & { code?: unknown }).code\n return {\n message: error.message || fallbackMessage,\n code: normalizeCode(codeField),\n }\n }\n if (typeof error === 'object' && error !== null) {\n const messageField = (error as { message?: unknown }).message\n const codeField = (error as { code?: unknown }).code\n return {\n message:\n typeof messageField === 'string' && messageField.length > 0\n ? messageField\n : fallbackMessage,\n code: normalizeCode(codeField),\n }\n }\n if (typeof error === 'string' && error.length > 0) {\n return { message: error, code: undefined }\n }\n return { message: fallbackMessage, code: undefined }\n}\n"],"names":[],"mappings":"AAcA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,SAAS,cAAc,WAAwC;AAC7D,MAAI,OAAO,cAAc,SAAU,QAAO;AAC1C,MAAI,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,GAAG;AAC/D,WAAO,OAAO,SAAS;AAAA,EACzB;AACA,SAAO;AACT;AAEO,SAAS,kBACd,OACA,kBAAkB,0BAC6B;AAC/C,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,OAAQ,MAA6B;AAC3C,QAAI,OAAO,SAAS,YAAY,kBAAkB,IAAI,IAAI,GAAG;AAC3D,aAAO,EAAE,SAAS,mBAAmB,MAAM,UAAA;AAAA,IAC7C;AAAA,EACF;AACA,MAAI,iBAAiB,OAAO;AAC1B,UAAM,YAAa,MAAqC;AACxD,WAAO;AAAA,MACL,SAAS,MAAM,WAAW;AAAA,MAC1B,MAAM,cAAc,SAAS;AAAA,IAAA;AAAA,EAEjC;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,UAAM,eAAgB,MAAgC;AACtD,UAAM,YAAa,MAA6B;AAChD,WAAO;AAAA,MACL,SACE,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACtD,eACA;AAAA,MACN,MAAM,cAAc,SAAS;AAAA,IAAA;AAAA,EAEjC;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO,EAAE,SAAS,OAAO,MAAM,OAAA;AAAA,EACjC;AACA,SAAO,EAAE,SAAS,iBAAiB,MAAM,OAAA;AAC3C;"}
1
+ {"version":3,"file":"error-payload.js","sources":["../../../src/activities/error-payload.ts"],"sourcesContent":["/**\n * Shared error-narrowing helper for activities that convert thrown values\n * into structured `RUN_ERROR` events.\n *\n * Accepts Error instances, objects with string-ish `message`/`code`, or bare\n * strings; always returns a shape safe to serialize. Never leaks the full\n * error object (which may carry request/response state from an SDK).\n *\n * Abort-shaped errors (DOM `AbortError`, OpenAI `APIUserAbortError`,\n * OpenRouter `RequestAbortedError`) are normalized to a stable\n * `{ message: 'Request aborted', code: 'aborted' }` shape so callers can\n * discriminate user-initiated cancellation from other failures without\n * matching on provider-specific message strings.\n */\nconst ABORT_ERROR_NAMES = new Set([\n 'AbortError',\n 'APIUserAbortError',\n 'RequestAbortedError',\n])\n\n// HTTP status codes carried as numbers (e.g. `error.status = 429`) are a\n// common variant on SDK error classes; coerce so the resulting `code` field\n// is stable as a string for downstream consumers.\nfunction normalizeCode(codeField: unknown): string | undefined {\n if (typeof codeField === 'string') return codeField\n if (typeof codeField === 'number' && Number.isFinite(codeField)) {\n return String(codeField)\n }\n return undefined\n}\n\nexport function toRunErrorPayload(\n error: unknown,\n fallbackMessage = 'Unknown error occurred',\n): { message: string; code: string | undefined } {\n if (error && typeof error === 'object') {\n const name = (error as { name?: unknown }).name\n if (typeof name === 'string' && ABORT_ERROR_NAMES.has(name)) {\n return { message: 'Request aborted', code: 'aborted' }\n }\n }\n if (error instanceof Error) {\n const codeField = (error as Error & { code?: unknown }).code\n return {\n message: error.message || fallbackMessage,\n code: normalizeCode(codeField),\n }\n }\n if (typeof error === 'object' && error !== null) {\n const messageField = (error as { message?: unknown }).message\n const codeField = (error as { code?: unknown }).code\n return {\n message:\n typeof messageField === 'string' && messageField.length > 0\n ? messageField\n : fallbackMessage,\n code: normalizeCode(codeField),\n }\n }\n if (typeof error === 'string' && error.length > 0) {\n return { message: error, code: undefined }\n }\n return { message: fallbackMessage, code: undefined }\n}\n\n/**\n * Extract the provider's *structured error body* from a thrown value, to attach\n * as the AG-UI `rawEvent` on a RUN_ERROR event. This is the recoverable upstream\n * detail (provider name, the upstream model's error JSON, rate-limit/overload\n * codes, etc.) that `toRunErrorPayload`'s `{ message, code }` deliberately drops.\n *\n * Security boundary: only known provider-response-body fields are forwarded —\n * never the raw SDK exception object, which can carry request metadata such as\n * auth headers or request ids. The recognized sources, in priority order:\n *\n * - `error.rawEvent` — a provider body an adapter attached explicitly (e.g. the\n * OpenRouter mid-stream `chunk.error`).\n * - `error.error` (object) — the parsed provider response body exposed by SDK\n * `APIError` instances (OpenAI/Anthropic `{ type, message, code, param }`,\n * OpenRouter typed errors whose `.error` carries `.metadata`). This is\n * provider-shaped data, distinct from `.headers` / `.request_id`.\n * - `error.metadata` — OpenRouter's `provider_name` + raw upstream body, when\n * surfaced directly on the thrown error.\n *\n * Returns `undefined` when no structured provider body is present, so callers\n * omit the field entirely rather than setting it to `null`:\n *\n * const rawEvent = toRunErrorRawEvent(error)\n * yield { type: EventType.RUN_ERROR, ..., ...(rawEvent !== undefined && { rawEvent }) }\n */\nexport function toRunErrorRawEvent(error: unknown): unknown {\n if (!error || typeof error !== 'object') return undefined\n const e = error as {\n rawEvent?: unknown\n error?: unknown\n metadata?: unknown\n }\n if (e.rawEvent !== undefined && e.rawEvent !== null) return e.rawEvent\n if (\n e.error !== undefined &&\n e.error !== null &&\n typeof e.error === 'object'\n ) {\n return e.error\n }\n if (e.metadata !== undefined && e.metadata !== null) return e.metadata\n return undefined\n}\n"],"names":[],"mappings":"AAcA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,SAAS,cAAc,WAAwC;AAC7D,MAAI,OAAO,cAAc,SAAU,QAAO;AAC1C,MAAI,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,GAAG;AAC/D,WAAO,OAAO,SAAS;AAAA,EACzB;AACA,SAAO;AACT;AAEO,SAAS,kBACd,OACA,kBAAkB,0BAC6B;AAC/C,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,OAAQ,MAA6B;AAC3C,QAAI,OAAO,SAAS,YAAY,kBAAkB,IAAI,IAAI,GAAG;AAC3D,aAAO,EAAE,SAAS,mBAAmB,MAAM,UAAA;AAAA,IAC7C;AAAA,EACF;AACA,MAAI,iBAAiB,OAAO;AAC1B,UAAM,YAAa,MAAqC;AACxD,WAAO;AAAA,MACL,SAAS,MAAM,WAAW;AAAA,MAC1B,MAAM,cAAc,SAAS;AAAA,IAAA;AAAA,EAEjC;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,UAAM,eAAgB,MAAgC;AACtD,UAAM,YAAa,MAA6B;AAChD,WAAO;AAAA,MACL,SACE,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACtD,eACA;AAAA,MACN,MAAM,cAAc,SAAS;AAAA,IAAA;AAAA,EAEjC;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO,EAAE,SAAS,OAAO,MAAM,OAAA;AAAA,EACjC;AACA,SAAO,EAAE,SAAS,iBAAiB,MAAM,OAAA;AAC3C;AA2BO,SAAS,mBAAmB,OAAyB;AAC1D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AAKV,MAAI,EAAE,aAAa,UAAa,EAAE,aAAa,aAAa,EAAE;AAC9D,MACE,EAAE,UAAU,UACZ,EAAE,UAAU,QACZ,OAAO,EAAE,UAAU,UACnB;AACA,WAAO,EAAE;AAAA,EACX;AACA,MAAI,EAAE,aAAa,UAAa,EAAE,aAAa,aAAa,EAAE;AAC9D,SAAO;AACT;"}
@@ -2,4 +2,4 @@ export type { ResolvedCategories } from './logger/internal-logger.js';
2
2
  export { InternalLogger } from './logger/internal-logger.js';
3
3
  export type { Logger } from './logger/types.js';
4
4
  export { resolveDebugOption } from './logger/resolve.js';
5
- export { toRunErrorPayload } from './activities/error-payload.js';
5
+ export { toRunErrorPayload, toRunErrorRawEvent, } from './activities/error-payload.js';
@@ -1,9 +1,10 @@
1
1
  import { InternalLogger } from "./logger/internal-logger.js";
2
2
  import { resolveDebugOption } from "./logger/resolve.js";
3
- import { toRunErrorPayload } from "./activities/error-payload.js";
3
+ import { toRunErrorPayload, toRunErrorRawEvent } from "./activities/error-payload.js";
4
4
  export {
5
5
  InternalLogger,
6
6
  resolveDebugOption,
7
- toRunErrorPayload
7
+ toRunErrorPayload,
8
+ toRunErrorRawEvent
8
9
  };
9
10
  //# sourceMappingURL=adapter-internals.js.map
@@ -34,7 +34,7 @@ export declare enum EventType {
34
34
  REASONING_ENCRYPTED_VALUE = "REASONING_ENCRYPTED_VALUE"
35
35
  }
36
36
  export { toolDefinition, type AnyClientTool, type ClientTool, type InferToolInput, type InferToolName, type InferToolOutput, type ToolDefinition, type ToolDefinitionConfig, type ToolDefinitionInstance, } from './activities/chat/tools/tool-definition.js';
37
- export { convertSchemaToJsonSchema } from './activities/chat/tools/schema-converter.js';
37
+ export { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema, } from './activities/chat/tools/schema-converter.js';
38
38
  export { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages, } from './activities/chat/messages.js';
39
39
  export { BatchStrategy, CompositeStrategy, defaultJSONParser, ImmediateStrategy, parsePartialJSON, PartialJSONParser, PunctuationStrategy, StreamProcessor, WordBoundaryStrategy, } from './activities/chat/stream/index.js';
40
40
  export type { ChunkRecording, ChunkStrategy, InternalToolCallState, JSONParser, ProcessorResult, ProcessorState, StreamProcessorEvents, StreamProcessorOptions, ToolCallState, ToolResultState, } from './activities/chat/stream/index.js';
@@ -1,5 +1,5 @@
1
1
  import { toolDefinition } from "./activities/chat/tools/tool-definition.js";
2
- import { convertSchemaToJsonSchema } from "./activities/chat/tools/schema-converter.js";
2
+ import { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./activities/chat/tools/schema-converter.js";
3
3
  import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
4
4
  import { uiMessagesToWire } from "./utilities/ag-ui-wire.js";
5
5
  import { BatchStrategy, CompositeStrategy, ImmediateStrategy, PunctuationStrategy, WordBoundaryStrategy } from "./activities/chat/stream/strategies.js";
@@ -54,10 +54,12 @@ export {
54
54
  convertSchemaToJsonSchema,
55
55
  defaultJSONParser,
56
56
  generateMessageId,
57
+ isStandardSchema,
57
58
  modelMessageToUIMessage,
58
59
  modelMessagesToUIMessages,
59
60
  normalizeToUIMessage,
60
61
  parsePartialJSON,
62
+ parseWithStandardSchema,
61
63
  toolDefinition,
62
64
  uiMessageToModelMessages,
63
65
  uiMessagesToWire
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../src/client.ts"],"sourcesContent":["export enum EventType {\n TEXT_MESSAGE_START = 'TEXT_MESSAGE_START',\n TEXT_MESSAGE_CONTENT = 'TEXT_MESSAGE_CONTENT',\n TEXT_MESSAGE_END = 'TEXT_MESSAGE_END',\n TEXT_MESSAGE_CHUNK = 'TEXT_MESSAGE_CHUNK',\n TOOL_CALL_START = 'TOOL_CALL_START',\n TOOL_CALL_ARGS = 'TOOL_CALL_ARGS',\n TOOL_CALL_END = 'TOOL_CALL_END',\n TOOL_CALL_CHUNK = 'TOOL_CALL_CHUNK',\n TOOL_CALL_RESULT = 'TOOL_CALL_RESULT',\n THINKING_START = 'THINKING_START',\n THINKING_END = 'THINKING_END',\n THINKING_TEXT_MESSAGE_START = 'THINKING_TEXT_MESSAGE_START',\n THINKING_TEXT_MESSAGE_CONTENT = 'THINKING_TEXT_MESSAGE_CONTENT',\n THINKING_TEXT_MESSAGE_END = 'THINKING_TEXT_MESSAGE_END',\n STATE_SNAPSHOT = 'STATE_SNAPSHOT',\n STATE_DELTA = 'STATE_DELTA',\n MESSAGES_SNAPSHOT = 'MESSAGES_SNAPSHOT',\n ACTIVITY_SNAPSHOT = 'ACTIVITY_SNAPSHOT',\n ACTIVITY_DELTA = 'ACTIVITY_DELTA',\n RAW = 'RAW',\n CUSTOM = 'CUSTOM',\n RUN_STARTED = 'RUN_STARTED',\n RUN_FINISHED = 'RUN_FINISHED',\n RUN_ERROR = 'RUN_ERROR',\n STEP_STARTED = 'STEP_STARTED',\n STEP_FINISHED = 'STEP_FINISHED',\n REASONING_START = 'REASONING_START',\n REASONING_MESSAGE_START = 'REASONING_MESSAGE_START',\n REASONING_MESSAGE_CONTENT = 'REASONING_MESSAGE_CONTENT',\n REASONING_MESSAGE_END = 'REASONING_MESSAGE_END',\n REASONING_MESSAGE_CHUNK = 'REASONING_MESSAGE_CHUNK',\n REASONING_END = 'REASONING_END',\n REASONING_ENCRYPTED_VALUE = 'REASONING_ENCRYPTED_VALUE',\n}\n\nexport {\n toolDefinition,\n type AnyClientTool,\n type ClientTool,\n type InferToolInput,\n type InferToolName,\n type InferToolOutput,\n type ToolDefinition,\n type ToolDefinitionConfig,\n type ToolDefinitionInstance,\n} from './activities/chat/tools/tool-definition'\n\nexport { convertSchemaToJsonSchema } from './activities/chat/tools/schema-converter'\n\nexport {\n convertMessagesToModelMessages,\n generateMessageId,\n modelMessageToUIMessage,\n modelMessagesToUIMessages,\n normalizeToUIMessage,\n uiMessageToModelMessages,\n} from './activities/chat/messages'\n\nexport {\n BatchStrategy,\n CompositeStrategy,\n defaultJSONParser,\n ImmediateStrategy,\n parsePartialJSON,\n PartialJSONParser,\n PunctuationStrategy,\n StreamProcessor,\n WordBoundaryStrategy,\n} from './activities/chat/stream/index'\nexport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n JSONParser,\n ProcessorResult,\n ProcessorState,\n StreamProcessorEvents,\n StreamProcessorOptions,\n ToolCallState,\n ToolResultState,\n} from './activities/chat/stream/index'\n\nexport { uiMessagesToWire } from './utilities/ag-ui-wire'\nexport type { WireMessage } from './utilities/ag-ui-wire'\n\nexport type {\n AudioPart,\n ContentPart,\n ContentPartDataSource,\n ContentPartSource,\n ContentPartUrlSource,\n CustomEvent,\n DocumentPart,\n ImagePart,\n MessagePart,\n ModelMessage,\n RunErrorEvent,\n RunFinishedEvent,\n SchemaInput,\n StreamChunk,\n StructuredOutputPart,\n TextPart,\n ThinkingPart,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n VideoPart,\n InferSchemaType,\n} from './types'\n\nexport type {\n AudioVisualization,\n RealtimeError,\n RealtimeErrorCode,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeEventPayloads,\n RealtimeMessage,\n RealtimeMessagePart,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n RealtimeAudioPart,\n RealtimeImagePart,\n RealtimeTextPart,\n RealtimeToolCallPart,\n RealtimeToolResultPart,\n VADConfig,\n} from './realtime/types'\n"],"names":["EventType"],"mappings":";;;;;;;AAAO,IAAK,8BAAAA,eAAL;AACLA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,sBAAA,IAAuB;AACvBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,cAAA,IAAe;AACfA,aAAA,6BAAA,IAA8B;AAC9BA,aAAA,+BAAA,IAAgC;AAChCA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,aAAA,IAAc;AACdA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,KAAA,IAAM;AACNA,aAAA,QAAA,IAAS;AACTA,aAAA,aAAA,IAAc;AACdA,aAAA,cAAA,IAAe;AACfA,aAAA,WAAA,IAAY;AACZA,aAAA,cAAA,IAAe;AACfA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,uBAAA,IAAwB;AACxBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,eAAA,IAAgB;AAChBA,aAAA,2BAAA,IAA4B;AAjClB,SAAAA;AAAA,GAAA,aAAA,CAAA,CAAA;"}
1
+ {"version":3,"file":"client.js","sources":["../../src/client.ts"],"sourcesContent":["export enum EventType {\n TEXT_MESSAGE_START = 'TEXT_MESSAGE_START',\n TEXT_MESSAGE_CONTENT = 'TEXT_MESSAGE_CONTENT',\n TEXT_MESSAGE_END = 'TEXT_MESSAGE_END',\n TEXT_MESSAGE_CHUNK = 'TEXT_MESSAGE_CHUNK',\n TOOL_CALL_START = 'TOOL_CALL_START',\n TOOL_CALL_ARGS = 'TOOL_CALL_ARGS',\n TOOL_CALL_END = 'TOOL_CALL_END',\n TOOL_CALL_CHUNK = 'TOOL_CALL_CHUNK',\n TOOL_CALL_RESULT = 'TOOL_CALL_RESULT',\n THINKING_START = 'THINKING_START',\n THINKING_END = 'THINKING_END',\n THINKING_TEXT_MESSAGE_START = 'THINKING_TEXT_MESSAGE_START',\n THINKING_TEXT_MESSAGE_CONTENT = 'THINKING_TEXT_MESSAGE_CONTENT',\n THINKING_TEXT_MESSAGE_END = 'THINKING_TEXT_MESSAGE_END',\n STATE_SNAPSHOT = 'STATE_SNAPSHOT',\n STATE_DELTA = 'STATE_DELTA',\n MESSAGES_SNAPSHOT = 'MESSAGES_SNAPSHOT',\n ACTIVITY_SNAPSHOT = 'ACTIVITY_SNAPSHOT',\n ACTIVITY_DELTA = 'ACTIVITY_DELTA',\n RAW = 'RAW',\n CUSTOM = 'CUSTOM',\n RUN_STARTED = 'RUN_STARTED',\n RUN_FINISHED = 'RUN_FINISHED',\n RUN_ERROR = 'RUN_ERROR',\n STEP_STARTED = 'STEP_STARTED',\n STEP_FINISHED = 'STEP_FINISHED',\n REASONING_START = 'REASONING_START',\n REASONING_MESSAGE_START = 'REASONING_MESSAGE_START',\n REASONING_MESSAGE_CONTENT = 'REASONING_MESSAGE_CONTENT',\n REASONING_MESSAGE_END = 'REASONING_MESSAGE_END',\n REASONING_MESSAGE_CHUNK = 'REASONING_MESSAGE_CHUNK',\n REASONING_END = 'REASONING_END',\n REASONING_ENCRYPTED_VALUE = 'REASONING_ENCRYPTED_VALUE',\n}\n\nexport {\n toolDefinition,\n type AnyClientTool,\n type ClientTool,\n type InferToolInput,\n type InferToolName,\n type InferToolOutput,\n type ToolDefinition,\n type ToolDefinitionConfig,\n type ToolDefinitionInstance,\n} from './activities/chat/tools/tool-definition'\n\nexport {\n convertSchemaToJsonSchema,\n isStandardSchema,\n parseWithStandardSchema,\n} from './activities/chat/tools/schema-converter'\n\nexport {\n convertMessagesToModelMessages,\n generateMessageId,\n modelMessageToUIMessage,\n modelMessagesToUIMessages,\n normalizeToUIMessage,\n uiMessageToModelMessages,\n} from './activities/chat/messages'\n\nexport {\n BatchStrategy,\n CompositeStrategy,\n defaultJSONParser,\n ImmediateStrategy,\n parsePartialJSON,\n PartialJSONParser,\n PunctuationStrategy,\n StreamProcessor,\n WordBoundaryStrategy,\n} from './activities/chat/stream/index'\nexport type {\n ChunkRecording,\n ChunkStrategy,\n InternalToolCallState,\n JSONParser,\n ProcessorResult,\n ProcessorState,\n StreamProcessorEvents,\n StreamProcessorOptions,\n ToolCallState,\n ToolResultState,\n} from './activities/chat/stream/index'\n\nexport { uiMessagesToWire } from './utilities/ag-ui-wire'\nexport type { WireMessage } from './utilities/ag-ui-wire'\n\nexport type {\n AudioPart,\n ContentPart,\n ContentPartDataSource,\n ContentPartSource,\n ContentPartUrlSource,\n CustomEvent,\n DocumentPart,\n ImagePart,\n MessagePart,\n ModelMessage,\n RunErrorEvent,\n RunFinishedEvent,\n SchemaInput,\n StreamChunk,\n StructuredOutputPart,\n TextPart,\n ThinkingPart,\n ToolCall,\n ToolCallPart,\n ToolResultPart,\n UIMessage,\n VideoPart,\n InferSchemaType,\n} from './types'\n\nexport type {\n AudioVisualization,\n RealtimeError,\n RealtimeErrorCode,\n RealtimeEvent,\n RealtimeEventHandler,\n RealtimeEventPayloads,\n RealtimeMessage,\n RealtimeMessagePart,\n RealtimeMode,\n RealtimeSessionConfig,\n RealtimeStatus,\n RealtimeToken,\n RealtimeAudioPart,\n RealtimeImagePart,\n RealtimeTextPart,\n RealtimeToolCallPart,\n RealtimeToolResultPart,\n VADConfig,\n} from './realtime/types'\n"],"names":["EventType"],"mappings":";;;;;;;AAAO,IAAK,8BAAAA,eAAL;AACLA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,sBAAA,IAAuB;AACvBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,oBAAA,IAAqB;AACrBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,kBAAA,IAAmB;AACnBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,cAAA,IAAe;AACfA,aAAA,6BAAA,IAA8B;AAC9BA,aAAA,+BAAA,IAAgC;AAChCA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,aAAA,IAAc;AACdA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,mBAAA,IAAoB;AACpBA,aAAA,gBAAA,IAAiB;AACjBA,aAAA,KAAA,IAAM;AACNA,aAAA,QAAA,IAAS;AACTA,aAAA,aAAA,IAAc;AACdA,aAAA,cAAA,IAAe;AACfA,aAAA,WAAA,IAAY;AACZA,aAAA,cAAA,IAAe;AACfA,aAAA,eAAA,IAAgB;AAChBA,aAAA,iBAAA,IAAkB;AAClBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,2BAAA,IAA4B;AAC5BA,aAAA,uBAAA,IAAwB;AACxBA,aAAA,yBAAA,IAA0B;AAC1BA,aAAA,eAAA,IAAgB;AAChBA,aAAA,2BAAA,IAA4B;AAjClB,SAAAA;AAAA,GAAA,aAAA,CAAA,CAAA;"}
@@ -8,7 +8,7 @@ export { createSpeechOptions } from './activities/generateSpeech/index.js';
8
8
  export { createTranscriptionOptions } from './activities/generateTranscription/index.js';
9
9
  export type { AIAdapter, ImageAdapter, AnyImageAdapter, TextAdapter, AnyTextAdapter, AnySummarizeAdapter, SummarizeAdapter, AnyAudioAdapter, AudioAdapter, AnyTTSAdapter, TTSAdapter, AnyTranscriptionAdapter, TranscriptionAdapter, AnyVideoAdapter, VideoAdapter, } from './activities/index.js';
10
10
  export { toolDefinition, type ToolDefinition, type ToolDefinitionInstance, type ToolDefinitionConfig, type ServerTool, type ClientTool, type AnyClientTool, type InferToolName, type InferToolInput, type InferToolOutput, } from './activities/chat/tools/tool-definition.js';
11
- export { convertSchemaToJsonSchema, StandardSchemaValidationError, } from './activities/chat/tools/schema-converter.js';
11
+ export { convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema, StandardSchemaValidationError, } from './activities/chat/tools/schema-converter.js';
12
12
  export { streamToText, toServerSentEventsStream, toServerSentEventsResponse, toHttpStream, toHttpResponse, } from './stream-to-response.js';
13
13
  export { ToolCallManager } from './activities/chat/tools/tool-calls.js';
14
14
  export type { ProviderTool } from './tools/provider-tool.js';
@@ -28,6 +28,7 @@ export type { ChunkStrategy, ChunkRecording, InternalToolCallState, ProcessorRes
28
28
  export { chatParamsFromRequest, chatParamsFromRequestBody, mergeAgentTools, } from './utilities/chat-params.js';
29
29
  export { uiMessagesToWire } from './utilities/ag-ui-wire.js';
30
30
  export type { WireMessage } from './utilities/ag-ui-wire.js';
31
+ export { isContentPart, isContentPartArray, normalizeToolResult, } from './utilities/tool-result.js';
31
32
  export { createModel, extendAdapter } from './extend-adapter.js';
32
33
  export type { ExtendedModelDef } from './extend-adapter.js';
33
34
  export type { Logger, DebugCategories, DebugConfig, DebugOption, } from './logger/types.js';
package/dist/esm/index.js CHANGED
@@ -6,7 +6,7 @@ import { createVideoOptions, generateVideo, getVideoJobStatus } from "./activiti
6
6
  import { createSpeechOptions, generateSpeech } from "./activities/generateSpeech/index.js";
7
7
  import { createTranscriptionOptions, generateTranscription } from "./activities/generateTranscription/index.js";
8
8
  import { toolDefinition } from "./activities/chat/tools/tool-definition.js";
9
- import { StandardSchemaValidationError, convertSchemaToJsonSchema } from "./activities/chat/tools/schema-converter.js";
9
+ import { StandardSchemaValidationError, convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./activities/chat/tools/schema-converter.js";
10
10
  import { streamToText, toHttpResponse, toHttpStream, toServerSentEventsResponse, toServerSentEventsStream } from "./stream-to-response.js";
11
11
  import { ToolCallManager } from "./activities/chat/tools/tool-calls.js";
12
12
  import { brandProviderTool } from "./tools/provider-tool.js";
@@ -18,6 +18,7 @@ import { realtimeToken } from "./realtime/index.js";
18
18
  import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
19
19
  import { chatParamsFromRequest, chatParamsFromRequestBody, mergeAgentTools } from "./utilities/chat-params.js";
20
20
  import { uiMessagesToWire } from "./utilities/ag-ui-wire.js";
21
+ import { isContentPart, isContentPartArray, normalizeToolResult } from "./utilities/tool-result.js";
21
22
  import { createModel, extendAdapter } from "./extend-adapter.js";
22
23
  import { ConsoleLogger } from "./logger/console-logger.js";
23
24
  import { BatchStrategy, CompositeStrategy, ImmediateStrategy, PunctuationStrategy, WordBoundaryStrategy } from "./activities/chat/stream/strategies.js";
@@ -64,13 +65,18 @@ export {
64
65
  generateTranscription,
65
66
  generateVideo,
66
67
  getVideoJobStatus,
68
+ isContentPart,
69
+ isContentPartArray,
70
+ isStandardSchema,
67
71
  maxIterations,
68
72
  mergeAgentTools,
69
73
  modelMessageToUIMessage,
70
74
  modelMessagesToUIMessages,
71
75
  normalizeSystemPrompts,
72
76
  normalizeToUIMessage,
77
+ normalizeToolResult,
73
78
  parsePartialJSON,
79
+ parseWithStandardSchema,
74
80
  realtimeToken,
75
81
  streamToText,
76
82
  summarize,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,23 +1,23 @@
1
- import { Tool } from './types.js';
1
+ import { AnyTool } from './types.js';
2
2
  /**
3
3
  * A registry that holds tools and allows dynamic tool management.
4
4
  *
5
5
  * The registry can be either mutable (allowing additions/removals during execution)
6
6
  * or frozen (static tool list, for backward compatibility with tools arrays).
7
7
  */
8
- export interface ToolRegistry {
8
+ export interface ToolRegistry<TTool extends AnyTool = AnyTool> {
9
9
  /**
10
10
  * Get all current tools in the registry.
11
11
  * Called each agent loop iteration to get the latest tool list.
12
12
  */
13
- getTools: () => ReadonlyArray<Tool>;
13
+ getTools: () => Array<TTool>;
14
14
  /**
15
15
  * Add a tool to the registry dynamically.
16
16
  * For frozen registries, this is a no-op.
17
17
  *
18
18
  * @param tool - The tool to add
19
19
  */
20
- add: (tool: Tool) => void;
20
+ add: (tool: TTool) => void;
21
21
  /**
22
22
  * Remove a tool from the registry by name.
23
23
  * For frozen registries, this always returns false.
@@ -38,7 +38,7 @@ export interface ToolRegistry {
38
38
  * @param name - The name of the tool to get
39
39
  * @returns The tool if found, undefined otherwise
40
40
  */
41
- get: (name: string) => Tool | undefined;
41
+ get: (name: string) => TTool | undefined;
42
42
  /**
43
43
  * Whether this registry is frozen (immutable).
44
44
  * Frozen registries don't allow add/remove operations.
@@ -68,7 +68,7 @@ export interface ToolRegistry {
68
68
  * registry.add(newTool) // Immediately available to LLM
69
69
  * ```
70
70
  */
71
- export declare function createToolRegistry(initialTools?: Array<Tool>): ToolRegistry;
71
+ export declare function createToolRegistry<TTool extends AnyTool = AnyTool>(initialTools?: Array<TTool>): ToolRegistry<TTool>;
72
72
  /**
73
73
  * Create a frozen (immutable) tool registry from a tools array.
74
74
  *
@@ -78,4 +78,4 @@ export declare function createToolRegistry(initialTools?: Array<Tool>): ToolRegi
78
78
  * @param tools - The static array of tools
79
79
  * @returns A frozen ToolRegistry
80
80
  */
81
- export declare function createFrozenRegistry(tools?: Array<Tool>): ToolRegistry;
81
+ export declare function createFrozenRegistry<TTool extends AnyTool = AnyTool>(tools?: Array<TTool>): ToolRegistry<TTool>;
@@ -27,7 +27,7 @@ function createFrozenRegistry(tools = []) {
27
27
  }
28
28
  const frozenTools = Object.freeze([...tools]);
29
29
  return {
30
- getTools: () => frozenTools,
30
+ getTools: () => [...frozenTools],
31
31
  add: (_tool) => {
32
32
  },
33
33
  remove: (_name) => {
@@ -1 +1 @@
1
- {"version":3,"file":"tool-registry.js","sources":["../../src/tool-registry.ts"],"sourcesContent":["import type { Tool } from './types'\n\n/**\n * A registry that holds tools and allows dynamic tool management.\n *\n * The registry can be either mutable (allowing additions/removals during execution)\n * or frozen (static tool list, for backward compatibility with tools arrays).\n */\nexport interface ToolRegistry {\n /**\n * Get all current tools in the registry.\n * Called each agent loop iteration to get the latest tool list.\n */\n getTools: () => ReadonlyArray<Tool>\n\n /**\n * Add a tool to the registry dynamically.\n * For frozen registries, this is a no-op.\n *\n * @param tool - The tool to add\n */\n add: (tool: Tool) => void\n\n /**\n * Remove a tool from the registry by name.\n * For frozen registries, this always returns false.\n *\n * @param name - The name of the tool to remove\n * @returns true if the tool was removed, false if not found or frozen\n */\n remove: (name: string) => boolean\n\n /**\n * Check if a tool exists in the registry.\n *\n * @param name - The name of the tool to check\n */\n has: (name: string) => boolean\n\n /**\n * Get a tool by name.\n *\n * @param name - The name of the tool to get\n * @returns The tool if found, undefined otherwise\n */\n get: (name: string) => Tool | undefined\n\n /**\n * Whether this registry is frozen (immutable).\n * Frozen registries don't allow add/remove operations.\n */\n readonly isFrozen: boolean\n}\n\n/**\n * Create a mutable tool registry for dynamic tool scenarios.\n *\n * Tools can be added and removed during chat execution, and the\n * changes will be reflected in subsequent agent loop iterations.\n *\n * @param initialTools - Optional initial set of tools\n * @returns A mutable ToolRegistry\n *\n * @example\n * ```typescript\n * const registry = createToolRegistry([toolA, toolB])\n *\n * const stream = chat({\n * adapter,\n * messages,\n * toolRegistry: registry,\n * })\n *\n * // Later, during tool execution:\n * registry.add(newTool) // Immediately available to LLM\n * ```\n */\nexport function createToolRegistry(\n initialTools: Array<Tool> = [],\n): ToolRegistry {\n const tools = new Map<string, Tool>()\n\n for (const tool of initialTools) {\n tools.set(tool.name, tool)\n }\n\n return {\n getTools: () => Array.from(tools.values()),\n\n add: (tool: Tool) => {\n tools.set(tool.name, tool)\n },\n\n remove: (name: string) => {\n return tools.delete(name)\n },\n\n has: (name: string) => {\n return tools.has(name)\n },\n\n get: (name: string) => {\n return tools.get(name)\n },\n\n isFrozen: false,\n }\n}\n\n/**\n * Create a frozen (immutable) tool registry from a tools array.\n *\n * This is used internally to wrap static `tools` arrays for backward compatibility.\n * Add and remove operations are no-ops on frozen registries.\n *\n * @param tools - The static array of tools\n * @returns A frozen ToolRegistry\n */\nexport function createFrozenRegistry(tools: Array<Tool> = []): ToolRegistry {\n const toolMap = new Map<string, Tool>()\n\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n const frozenTools = Object.freeze([...tools])\n\n return {\n getTools: () => frozenTools,\n\n add: (_tool: Tool) => {\n // No-op for frozen registry\n },\n\n remove: (_name: string) => {\n // No-op for frozen registry\n return false\n },\n\n has: (name: string) => {\n return toolMap.has(name)\n },\n\n get: (name: string) => {\n return toolMap.get(name)\n },\n\n isFrozen: true,\n }\n}\n"],"names":[],"mappings":"AA6EO,SAAS,mBACd,eAA4B,IACd;AACd,QAAM,4BAAY,IAAA;AAElB,aAAW,QAAQ,cAAc;AAC/B,UAAM,IAAI,KAAK,MAAM,IAAI;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM,KAAK,MAAM,QAAQ;AAAA,IAEzC,KAAK,CAAC,SAAe;AACnB,YAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAC3B;AAAA,IAEA,QAAQ,CAAC,SAAiB;AACxB,aAAO,MAAM,OAAO,IAAI;AAAA,IAC1B;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB;AAAA,IAEA,UAAU;AAAA,EAAA;AAEd;AAWO,SAAS,qBAAqB,QAAqB,IAAkB;AAC1E,QAAM,8BAAc,IAAA;AAEpB,aAAW,QAAQ,OAAO;AACxB,YAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,EAC7B;AAEA,QAAM,cAAc,OAAO,OAAO,CAAC,GAAG,KAAK,CAAC;AAE5C,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAEhB,KAAK,CAAC,UAAgB;AAAA,IAEtB;AAAA,IAEA,QAAQ,CAAC,UAAkB;AAEzB,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,QAAQ,IAAI,IAAI;AAAA,IACzB;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,QAAQ,IAAI,IAAI;AAAA,IACzB;AAAA,IAEA,UAAU;AAAA,EAAA;AAEd;"}
1
+ {"version":3,"file":"tool-registry.js","sources":["../../src/tool-registry.ts"],"sourcesContent":["import type { AnyTool } from './types'\n\n/**\n * A registry that holds tools and allows dynamic tool management.\n *\n * The registry can be either mutable (allowing additions/removals during execution)\n * or frozen (static tool list, for backward compatibility with tools arrays).\n */\nexport interface ToolRegistry<TTool extends AnyTool = AnyTool> {\n /**\n * Get all current tools in the registry.\n * Called each agent loop iteration to get the latest tool list.\n */\n getTools: () => Array<TTool>\n\n /**\n * Add a tool to the registry dynamically.\n * For frozen registries, this is a no-op.\n *\n * @param tool - The tool to add\n */\n add: (tool: TTool) => void\n\n /**\n * Remove a tool from the registry by name.\n * For frozen registries, this always returns false.\n *\n * @param name - The name of the tool to remove\n * @returns true if the tool was removed, false if not found or frozen\n */\n remove: (name: string) => boolean\n\n /**\n * Check if a tool exists in the registry.\n *\n * @param name - The name of the tool to check\n */\n has: (name: string) => boolean\n\n /**\n * Get a tool by name.\n *\n * @param name - The name of the tool to get\n * @returns The tool if found, undefined otherwise\n */\n get: (name: string) => TTool | undefined\n\n /**\n * Whether this registry is frozen (immutable).\n * Frozen registries don't allow add/remove operations.\n */\n readonly isFrozen: boolean\n}\n\n/**\n * Create a mutable tool registry for dynamic tool scenarios.\n *\n * Tools can be added and removed during chat execution, and the\n * changes will be reflected in subsequent agent loop iterations.\n *\n * @param initialTools - Optional initial set of tools\n * @returns A mutable ToolRegistry\n *\n * @example\n * ```typescript\n * const registry = createToolRegistry([toolA, toolB])\n *\n * const stream = chat({\n * adapter,\n * messages,\n * toolRegistry: registry,\n * })\n *\n * // Later, during tool execution:\n * registry.add(newTool) // Immediately available to LLM\n * ```\n */\nexport function createToolRegistry<TTool extends AnyTool = AnyTool>(\n initialTools: Array<TTool> = [],\n): ToolRegistry<TTool> {\n const tools = new Map<string, TTool>()\n\n for (const tool of initialTools) {\n tools.set(tool.name, tool)\n }\n\n return {\n getTools: () => Array.from(tools.values()),\n\n add: (tool: TTool) => {\n tools.set(tool.name, tool)\n },\n\n remove: (name: string) => {\n return tools.delete(name)\n },\n\n has: (name: string) => {\n return tools.has(name)\n },\n\n get: (name: string) => {\n return tools.get(name)\n },\n\n isFrozen: false,\n }\n}\n\n/**\n * Create a frozen (immutable) tool registry from a tools array.\n *\n * This is used internally to wrap static `tools` arrays for backward compatibility.\n * Add and remove operations are no-ops on frozen registries.\n *\n * @param tools - The static array of tools\n * @returns A frozen ToolRegistry\n */\nexport function createFrozenRegistry<TTool extends AnyTool = AnyTool>(\n tools: Array<TTool> = [],\n): ToolRegistry<TTool> {\n const toolMap = new Map<string, TTool>()\n\n for (const tool of tools) {\n toolMap.set(tool.name, tool)\n }\n\n const frozenTools = Object.freeze([...tools])\n\n return {\n getTools: () => [...frozenTools],\n\n add: (_tool: TTool) => {\n // No-op for frozen registry\n },\n\n remove: (_name: string) => {\n // No-op for frozen registry\n return false\n },\n\n has: (name: string) => {\n return toolMap.has(name)\n },\n\n get: (name: string) => {\n return toolMap.get(name)\n },\n\n isFrozen: true,\n }\n}\n"],"names":[],"mappings":"AA6EO,SAAS,mBACd,eAA6B,IACR;AACrB,QAAM,4BAAY,IAAA;AAElB,aAAW,QAAQ,cAAc;AAC/B,UAAM,IAAI,KAAK,MAAM,IAAI;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM,KAAK,MAAM,QAAQ;AAAA,IAEzC,KAAK,CAAC,SAAgB;AACpB,YAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAC3B;AAAA,IAEA,QAAQ,CAAC,SAAiB;AACxB,aAAO,MAAM,OAAO,IAAI;AAAA,IAC1B;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB;AAAA,IAEA,UAAU;AAAA,EAAA;AAEd;AAWO,SAAS,qBACd,QAAsB,IACD;AACrB,QAAM,8BAAc,IAAA;AAEpB,aAAW,QAAQ,OAAO;AACxB,YAAQ,IAAI,KAAK,MAAM,IAAI;AAAA,EAC7B;AAEA,QAAM,cAAc,OAAO,OAAO,CAAC,GAAG,KAAK,CAAC;AAE5C,SAAO;AAAA,IACL,UAAU,MAAM,CAAC,GAAG,WAAW;AAAA,IAE/B,KAAK,CAAC,UAAiB;AAAA,IAEvB;AAAA,IAEA,QAAQ,CAAC,UAAkB;AAEzB,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,QAAQ,IAAI,IAAI;AAAA,IACzB;AAAA,IAEA,KAAK,CAAC,SAAiB;AACrB,aAAO,QAAQ,IAAI,IAAI;AAAA,IACzB;AAAA,IAEA,UAAU;AAAA,EAAA;AAEd;"}
@@ -10,6 +10,7 @@ export type ToolCallState = 'awaiting-input' | 'input-streaming' | 'input-comple
10
10
  * Tool result states - track the lifecycle of a tool result
11
11
  */
12
12
  export type ToolResultState = 'streaming' | 'complete' | 'error';
13
+ export type ToolOutputState = 'output-available' | 'output-error';
13
14
  /**
14
15
  * JSON Schema type for defining tool input/output schemas as raw JSON Schema objects.
15
16
  * This allows tools to be defined without schema libraries when you have JSON Schema definitions available.
@@ -255,7 +256,7 @@ export interface ToolCallPart<TMetadata = unknown> {
255
256
  export interface ToolResultPart {
256
257
  type: 'tool-result';
257
258
  toolCallId: string;
258
- content: string;
259
+ content: string | Array<ContentPart>;
259
260
  state: ToolResultState;
260
261
  error?: string;
261
262
  }
@@ -321,11 +322,29 @@ export type InputModalitiesTypes = {
321
322
  export type ConstrainedModelMessage<TInputModalitiesTypes extends InputModalitiesTypes> = Omit<ModelMessage, 'content'> & {
322
323
  content: ConstrainedContent<TInputModalitiesTypes>;
323
324
  };
325
+ type IsUnknown<T> = unknown extends T ? [T] extends [unknown] ? true : false : false;
326
+ type RuntimeContextField<TContext> = IsUnknown<TContext> extends true ? {
327
+ /**
328
+ * Runtime context provided by the caller.
329
+ *
330
+ * This is request-local application state for tool and middleware
331
+ * implementations, not the AG-UI `Context[]` protocol field.
332
+ */
333
+ context?: TContext;
334
+ } : {
335
+ /**
336
+ * Runtime context provided by the caller.
337
+ *
338
+ * This is request-local application state for tool and middleware
339
+ * implementations, not the AG-UI `Context[]` protocol field.
340
+ */
341
+ context: TContext;
342
+ };
324
343
  /**
325
344
  * Context passed to tool execute functions, providing capabilities like
326
345
  * emitting custom events during execution.
327
346
  */
328
- export interface ToolExecutionContext {
347
+ export type ToolExecutionContext<TContext = unknown> = RuntimeContextField<TContext> & {
329
348
  /** The ID of the tool call being executed */
330
349
  toolCallId?: string;
331
350
  /**
@@ -347,7 +366,8 @@ export interface ToolExecutionContext {
347
366
  * ```
348
367
  */
349
368
  emitCustomEvent: (eventName: string, value: Record<string, any>) => void;
350
- }
369
+ };
370
+ export type ToolExecuteFunction<TInput extends SchemaInput = SchemaInput, TOutput extends SchemaInput = SchemaInput, TContext = unknown> = undefined extends TContext ? (args: InferSchemaType<TInput>, context?: ToolExecutionContext<TContext>) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput> : (args: InferSchemaType<TInput>, context: ToolExecutionContext<TContext>) => Promise<InferSchemaType<TOutput>> | InferSchemaType<TOutput>;
351
371
  /**
352
372
  * Tool/Function definition for function calling.
353
373
  *
@@ -361,7 +381,7 @@ export interface ToolExecutionContext {
361
381
  * @see https://docs.anthropic.com/claude/docs/tool-use
362
382
  * @see https://standardschema.dev/json-schema
363
383
  */
364
- export interface Tool<TInput extends SchemaInput = SchemaInput, TOutput extends SchemaInput = SchemaInput, TName extends string = string> {
384
+ export interface Tool<TInput extends SchemaInput = SchemaInput, TOutput extends SchemaInput = SchemaInput, TName extends string = string, TContext = unknown> {
365
385
  /**
366
386
  * Unique name of the tool (used by the model to call it).
367
387
  *
@@ -456,7 +476,7 @@ export interface Tool<TInput extends SchemaInput = SchemaInput, TOutput extends
456
476
  * return weather; // Can return object or string
457
477
  * }
458
478
  */
459
- execute?: ((args: any, context?: ToolExecutionContext) => Promise<any> | any) | undefined;
479
+ execute?: ToolExecuteFunction<TInput, TOutput, TContext> | undefined;
460
480
  /** If true, tool execution requires user approval before running. Works with both server and client tools. */
461
481
  needsApproval?: boolean;
462
482
  /** If true, this tool is lazy and will only be sent to the LLM after being discovered via the lazy tool discovery mechanism. Only meaningful when used with chat(). */
@@ -464,6 +484,9 @@ export interface Tool<TInput extends SchemaInput = SchemaInput, TOutput extends
464
484
  /** Additional metadata for adapters or custom extensions */
465
485
  metadata?: Record<string, any> | undefined;
466
486
  }
487
+ export type AnyTool = Omit<Tool<any, any, any, any>, 'execute'> & {
488
+ execute?: ((args: any, context?: any) => any) | undefined;
489
+ };
467
490
  export interface ToolConfig {
468
491
  [key: string]: Tool;
469
492
  }
@@ -580,10 +603,15 @@ export type AgentLoopStrategy = (state: AgentLoopState) => boolean;
580
603
  /**
581
604
  * Options passed into the SDK and further piped to the AI provider.
582
605
  */
583
- export interface TextOptions<TProviderOptionsSuperset extends Record<string, any> = Record<string, any>, TProviderOptionsForModel = TProviderOptionsSuperset> {
606
+ export interface TextOptions<TProviderOptionsSuperset extends Record<string, any> = Record<string, any>, TProviderOptionsForModel = TProviderOptionsSuperset, TContext = unknown> {
584
607
  model: string;
585
608
  messages: Array<ModelMessage>;
586
- tools?: Array<Tool<any, any, any>> | undefined;
609
+ tools?: Array<AnyTool> | undefined;
610
+ /**
611
+ * Runtime context provided by the caller and passed to middleware and
612
+ * server-side tool implementations.
613
+ */
614
+ context?: TContext;
587
615
  /**
588
616
  * System prompts to include with the request.
589
617
  *
@@ -907,7 +935,9 @@ export interface ToolCallEndEvent extends AGUIToolCallEndEvent {
907
935
  /** Final parsed input arguments (TanStack AI internal) */
908
936
  input?: unknown;
909
937
  /** Tool execution result (TanStack AI internal) */
910
- result?: string;
938
+ result?: string | Array<ContentPart>;
939
+ /** Tool execution output state (TanStack AI internal) */
940
+ state?: ToolOutputState;
911
941
  }
912
942
  /**
913
943
  * Emitted when a tool call result is available.
@@ -918,6 +948,8 @@ export interface ToolCallEndEvent extends AGUIToolCallEndEvent {
918
948
  export interface ToolCallResultEvent extends AGUIToolCallResultEvent {
919
949
  /** Model identifier for multi-model support */
920
950
  model?: string;
951
+ /** Tool execution output state (TanStack AI internal) */
952
+ state?: ToolOutputState;
921
953
  }
922
954
  /**
923
955
  * Emitted when a thinking/reasoning step starts.
@@ -41,7 +41,7 @@ function uiMessagesToWire(messages) {
41
41
  role: "tool",
42
42
  id: deriveToolMessageId(part.toolCallId),
43
43
  toolCallId: part.toolCallId,
44
- content: part.content,
44
+ content: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
45
45
  ...part.error !== void 0 && { error: part.error }
46
46
  });
47
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ag-ui-wire.js","sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type { ContentPart, MessagePart, UIMessage } from '../types'\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}\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}\n\ntype WireAnchorMessage = UIMessage & {\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\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. Each anchor (system/user/assistant) carries the canonical\n * `parts` array verbatim plus AG-UI mirror fields (`content`, `toolCalls`)\n * so AG-UI Zod parsing succeeds. Tool results and thinking parts on\n * assistant messages are additionally emitted as fan-out\n * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict\n * 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: if parts is missing (ModelMessage-shaped input), pass through as-is.\n // UIMessage always has parts; ModelMessage uses content directly.\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 ...msg,\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n })\n continue\n }\n\n if (msg.role === 'user') {\n wire.push({\n ...msg,\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\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 wire.push({\n role: 'reasoning',\n id: deriveReasoningId(msg.id, part),\n content: part.content,\n })\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push({\n ...msg,\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\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: part.content,\n ...(part.error !== undefined && { error: part.error }),\n })\n }\n }\n }\n\n return wire\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 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 calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\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"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ag-ui-wire.js","sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type { ContentPart, MessagePart, UIMessage } from '../types'\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}\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}\n\ntype WireAnchorMessage = UIMessage & {\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\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. Each anchor (system/user/assistant) carries the canonical\n * `parts` array verbatim plus AG-UI mirror fields (`content`, `toolCalls`)\n * so AG-UI Zod parsing succeeds. Tool results and thinking parts on\n * assistant messages are additionally emitted as fan-out\n * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict\n * 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: if parts is missing (ModelMessage-shaped input), pass through as-is.\n // UIMessage always has parts; ModelMessage uses content directly.\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 ...msg,\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n })\n continue\n }\n\n if (msg.role === 'user') {\n wire.push({\n ...msg,\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\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 wire.push({\n role: 'reasoning',\n id: deriveReasoningId(msg.id, part),\n content: part.content,\n })\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push({\n ...msg,\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\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 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 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 calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\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"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { Context as AGUIContext } from '@ag-ui/core';
2
- import { JSONSchema, ModelMessage, Tool, UIMessage } from '../types.js';
2
+ import { JSONSchema, ModelMessage, SchemaInput, Tool, UIMessage } from '../types.js';
3
3
  /**
4
4
  * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.
5
5
  *
@@ -24,7 +24,12 @@ export declare function chatParamsFromRequestBody(body: unknown): Promise<{
24
24
  }>;
25
25
  forwardedProps: Record<string, unknown>;
26
26
  state: unknown;
27
+ /**
28
+ * @deprecated Use `aguiContext` instead. This alias will be removed in a
29
+ * future release.
30
+ */
27
31
  context: Array<AGUIContext>;
32
+ aguiContext: Array<AGUIContext>;
28
33
  }>;
29
34
  /**
30
35
  * Read an HTTP `Request`, parse its JSON body, and validate it as an
@@ -73,8 +78,8 @@ export declare function chatParamsFromRequest(req: Request): Promise<Awaited<Ret
73
78
  * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.
74
79
  * @returns A merged array suitable for `chat({ tools })`.
75
80
  */
76
- export declare function mergeAgentTools(serverTools: ReadonlyArray<Tool>, clientTools: ReadonlyArray<{
81
+ export declare function mergeAgentTools<TContext = unknown>(serverTools: ReadonlyArray<Tool<SchemaInput, SchemaInput, string, TContext>>, clientTools: ReadonlyArray<{
77
82
  name: string;
78
83
  description: string;
79
84
  parameters: JSONSchema;
80
- }>): Array<Tool>;
85
+ }>): Array<Tool<SchemaInput, SchemaInput, string, TContext>>;
@@ -28,6 +28,7 @@ function chatParamsFromRequestBody(body) {
28
28
  );
29
29
  }
30
30
  const parsed = parseResult.data;
31
+ const aguiContext = parsed.context;
31
32
  const rawMessages = body.messages ?? [];
32
33
  const messages = parsed.messages.map((m, i) => {
33
34
  const raw = rawMessages[i];
@@ -44,7 +45,8 @@ function chatParamsFromRequestBody(body) {
44
45
  tools: parsed.tools,
45
46
  forwardedProps: parsed.forwardedProps ?? {},
46
47
  state: parsed.state,
47
- context: parsed.context
48
+ context: aguiContext,
49
+ aguiContext
48
50
  });
49
51
  }
50
52
  async function chatParamsFromRequest(req) {
@@ -72,7 +74,9 @@ async function chatParamsFromRequest(req) {
72
74
  }
73
75
  function mergeAgentTools(serverTools, clientTools) {
74
76
  const seen = new Set(serverTools.map((t) => t.name));
75
- const merged = [...serverTools];
77
+ const merged = [
78
+ ...serverTools
79
+ ];
76
80
  for (const ct of clientTools) {
77
81
  if (seen.has(ct.name)) {
78
82
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type { JSONSchema, ModelMessage, Tool, UIMessage } from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n context: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: parsed.context,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools(\n serverTools: ReadonlyArray<Tool>,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): Array<Tool> {\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<Tool> = [...serverTools]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n } as Tool)\n }\n return merged\n}\n"],"names":[],"mappings":";AAIA,MAAM,uCAAuB,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,OAAkD;AACtE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,OAAQ,EAAyB;AACvC,QAAI,OAAO,SAAS,YAAY,CAAC,iBAAiB,IAAI,IAAI,EAAG,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MASvC;AACD,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,QAAQ;AAAA,MACb,IAAI;AAAA,QACF,gLAGwB,YAAY,MAAM,OAAO;AAAA,MAAA;AAAA,IACnD;AAAA,EAEJ;AAEA,QAAM,SAAS,YAAY;AAK3B,QAAM,cACH,KAAuD,YAAY,CAAA;AACtE,QAAM,WAAW,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM;AAC7C,UAAM,MAAM,YAAY,CAAC;AACzB,QACE,OACA,OAAO,QAAQ,YACf,WAAW,OACX,aAAa,IAAI,KAAK,GACtB;AACA,aAAO,EAAE,GAAG,GAAG,OAAO,IAAI,MAAA;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IAKd,gBAAiB,OAAO,kBAAkB,CAAA;AAAA,IAC1C,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,EAAA,CACjB;AACH;AA2BA,eAAsB,sBACpB,KACgE;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAA;AAAA,EACnB,SAAS,OAAO;AAGd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,MAAM,0BAA0B,IAAI;AAAA,EAC7C,SAAS,OAAO;AAKd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACF;AAuBO,SAAS,gBACd,aACA,aAKa;AACb,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,SAAsB,CAAC,GAAG,WAAW;AAC3C,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,IAAI,GAAG,IAAI,GAAG;AAErB;AAAA,IACF;AACA,SAAK,IAAI,GAAG,IAAI;AAChB,WAAO,KAAK;AAAA,MACV,MAAM,GAAG;AAAA,MACT,aAAa,GAAG;AAAA,MAChB,aAAa,GAAG;AAAA;AAAA;AAAA,IAAA,CAGT;AAAA,EACX;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type {\n JSONSchema,\n ModelMessage,\n SchemaInput,\n Tool,\n UIMessage,\n} from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n /**\n * @deprecated Use `aguiContext` instead. This alias will be removed in a\n * future release.\n */\n context: Array<AGUIContext>\n aguiContext: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n const aguiContext = parsed.context\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: aguiContext,\n aguiContext,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools<TContext = unknown>(\n serverTools: ReadonlyArray<Tool<SchemaInput, SchemaInput, string, TContext>>,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): Array<Tool<SchemaInput, SchemaInput, string, TContext>> {\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<Tool<SchemaInput, SchemaInput, string, TContext>> = [\n ...serverTools,\n ]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n } as Tool<SchemaInput, SchemaInput, string, TContext>)\n }\n return merged\n}\n"],"names":[],"mappings":";AAUA,MAAM,uCAAuB,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,OAAkD;AACtE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,OAAQ,EAAyB;AACvC,QAAI,OAAO,SAAS,YAAY,CAAC,iBAAiB,IAAI,IAAI,EAAG,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MAcvC;AACD,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,QAAQ;AAAA,MACb,IAAI;AAAA,QACF,gLAGwB,YAAY,MAAM,OAAO;AAAA,MAAA;AAAA,IACnD;AAAA,EAEJ;AAEA,QAAM,SAAS,YAAY;AAC3B,QAAM,cAAc,OAAO;AAK3B,QAAM,cACH,KAAuD,YAAY,CAAA;AACtE,QAAM,WAAW,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM;AAC7C,UAAM,MAAM,YAAY,CAAC;AACzB,QACE,OACA,OAAO,QAAQ,YACf,WAAW,OACX,aAAa,IAAI,KAAK,GACtB;AACA,aAAO,EAAE,GAAG,GAAG,OAAO,IAAI,MAAA;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IAKd,gBAAiB,OAAO,kBAAkB,CAAA;AAAA,IAC1C,OAAO,OAAO;AAAA,IACd,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACH;AA2BA,eAAsB,sBACpB,KACgE;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAA;AAAA,EACnB,SAAS,OAAO;AAGd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,MAAM,0BAA0B,IAAI;AAAA,EAC7C,SAAS,OAAO;AAKd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACF;AAuBO,SAAS,gBACd,aACA,aAKyD;AACzD,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,SAAkE;AAAA,IACtE,GAAG;AAAA,EAAA;AAEL,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,IAAI,GAAG,IAAI,GAAG;AAErB;AAAA,IACF;AACA,SAAK,IAAI,GAAG,IAAI;AAChB,WAAO,KAAK;AAAA,MACV,MAAM,GAAG;AAAA,MACT,aAAa,GAAG;AAAA,MAChB,aAAa,GAAG;AAAA;AAAA;AAAA,IAAA,CAGmC;AAAA,EACvD;AACA,SAAO;AACT;"}
@@ -0,0 +1,21 @@
1
+ import { ContentPart } from '../types.js';
2
+ /**
3
+ * Structural check for a single `ContentPart`. A text part must carry a string
4
+ * `content`; every other modality must carry a `source` with `type` of
5
+ * `'url' | 'data'` and a string `value`.
6
+ */
7
+ export declare function isContentPart(value: unknown): value is ContentPart;
8
+ /**
9
+ * True iff `value` is a NON-EMPTY array whose every element is a valid
10
+ * `ContentPart`. Empty arrays and mixed arrays return false so they continue
11
+ * to be treated as ordinary (stringified) data — this keeps the auto-detection
12
+ * footgun narrow.
13
+ */
14
+ export declare function isContentPartArray(value: unknown): value is Array<ContentPart>;
15
+ /**
16
+ * Normalize a tool's return value for transport:
17
+ * - string → unchanged
18
+ * - ContentPart array → unchanged (multimodal, passed through to the adapter)
19
+ * - anything else → `JSON.stringify`
20
+ */
21
+ export declare function normalizeToolResult(result: unknown): string | Array<ContentPart>;
@@ -0,0 +1,37 @@
1
+ const CONTENT_PART_TYPES = /* @__PURE__ */ new Set([
2
+ "text",
3
+ "image",
4
+ "audio",
5
+ "video",
6
+ "document"
7
+ ]);
8
+ function isContentPart(value) {
9
+ if (typeof value !== "object" || value === null) return false;
10
+ const part = value;
11
+ if (typeof part.type !== "string" || !CONTENT_PART_TYPES.has(part.type)) {
12
+ return false;
13
+ }
14
+ if (part.type === "text") {
15
+ return typeof part.content === "string";
16
+ }
17
+ const source = part.source;
18
+ if (typeof source !== "object" || source === null) return false;
19
+ const src = source;
20
+ if (typeof src.value !== "string") return false;
21
+ if (src.type === "data") return typeof src.mimeType === "string";
22
+ return src.type === "url";
23
+ }
24
+ function isContentPartArray(value) {
25
+ return Array.isArray(value) && value.length > 0 && value.every(isContentPart);
26
+ }
27
+ function normalizeToolResult(result) {
28
+ if (typeof result === "string") return result;
29
+ if (isContentPartArray(result)) return result;
30
+ return JSON.stringify(result);
31
+ }
32
+ export {
33
+ isContentPart,
34
+ isContentPartArray,
35
+ normalizeToolResult
36
+ };
37
+ //# sourceMappingURL=tool-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result.js","sources":["../../../src/utilities/tool-result.ts"],"sourcesContent":["import type { ContentPart } from '../types'\n\nconst CONTENT_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n])\n\n/**\n * Structural check for a single `ContentPart`. A text part must carry a string\n * `content`; every other modality must carry a `source` with `type` of\n * `'url' | 'data'` and a string `value`.\n */\nexport function isContentPart(value: unknown): value is ContentPart {\n if (typeof value !== 'object' || value === null) return false\n const part = value as Record<string, unknown>\n if (typeof part.type !== 'string' || !CONTENT_PART_TYPES.has(part.type)) {\n return false\n }\n if (part.type === 'text') {\n return typeof part.content === 'string'\n }\n const source = part.source\n if (typeof source !== 'object' || source === null) return false\n const src = source as Record<string, unknown>\n if (typeof src.value !== 'string') return false\n // `data` sources require a mimeType (matches ContentPartDataSource); `url`\n // sources don't. Requiring it here keeps the runtime guard consistent with\n // the type and avoids emitting `data:undefined;base64,...` downstream.\n if (src.type === 'data') return typeof src.mimeType === 'string'\n return src.type === 'url'\n}\n\n/**\n * True iff `value` is a NON-EMPTY array whose every element is a valid\n * `ContentPart`. Empty arrays and mixed arrays return false so they continue\n * to be treated as ordinary (stringified) data — this keeps the auto-detection\n * footgun narrow.\n */\nexport function isContentPartArray(\n value: unknown,\n): value is Array<ContentPart> {\n return Array.isArray(value) && value.length > 0 && value.every(isContentPart)\n}\n\n/**\n * Normalize a tool's return value for transport:\n * - string → unchanged\n * - ContentPart array → unchanged (multimodal, passed through to the adapter)\n * - anything else → `JSON.stringify`\n */\nexport function normalizeToolResult(\n result: unknown,\n): string | Array<ContentPart> {\n if (typeof result === 'string') return result\n if (isContentPartArray(result)) return result\n return JSON.stringify(result)\n}\n"],"names":[],"mappings":"AAEA,MAAM,yCAAyB,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,cAAc,OAAsC;AAClE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,OAAO;AACb,MAAI,OAAO,KAAK,SAAS,YAAY,CAAC,mBAAmB,IAAI,KAAK,IAAI,GAAG;AACvE,WAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,QAAQ;AACxB,WAAO,OAAO,KAAK,YAAY;AAAA,EACjC;AACA,QAAM,SAAS,KAAK;AACpB,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;AAC1D,QAAM,MAAM;AACZ,MAAI,OAAO,IAAI,UAAU,SAAU,QAAO;AAI1C,MAAI,IAAI,SAAS,OAAQ,QAAO,OAAO,IAAI,aAAa;AACxD,SAAO,IAAI,SAAS;AACtB;AAQO,SAAS,mBACd,OAC6B;AAC7B,SAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,aAAa;AAC9E;AAQO,SAAS,oBACd,QAC6B;AAC7B,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,mBAAmB,MAAM,EAAG,QAAO;AACvC,SAAO,KAAK,UAAU,MAAM;AAC9B;"}