@tanstack/ai 0.61.0 → 0.64.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 (104) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
  3. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
  4. package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
  5. package/dist/esm/activities/chat/agents/spawn.js +10 -33
  6. package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
  7. package/dist/esm/activities/chat/index.d.ts +2 -0
  8. package/dist/esm/activities/chat/index.js +166 -60
  9. package/dist/esm/activities/chat/index.js.map +1 -1
  10. package/dist/esm/activities/chat/messages.js +43 -22
  11. package/dist/esm/activities/chat/messages.js.map +1 -1
  12. package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
  13. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  14. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  15. package/dist/esm/activities/chat/stream/message-updaters.js +6 -2
  16. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  17. package/dist/esm/activities/chat/stream/processor.d.ts +11 -9
  18. package/dist/esm/activities/chat/stream/processor.js +38 -18
  19. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  20. package/dist/esm/activities/chat/tools/schema-converter.d.ts +8 -0
  21. package/dist/esm/activities/chat/tools/schema-converter.js +6 -5
  22. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  23. package/dist/esm/activities/chat/tools/tool-calls.d.ts +20 -3
  24. package/dist/esm/activities/chat/tools/tool-calls.js +126 -36
  25. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  26. package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
  27. package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
  28. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  29. package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
  30. package/dist/esm/activities/evaluate/adapter.js.map +1 -1
  31. package/dist/esm/activities/evaluate/index.d.ts +4 -0
  32. package/dist/esm/activities/evaluate/index.js +3 -1
  33. package/dist/esm/activities/evaluate/index.js.map +1 -1
  34. package/dist/esm/activities/generateSpeech/index.d.ts +1 -1
  35. package/dist/esm/activities/generateSpeech/index.js +1 -1
  36. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  37. package/dist/esm/activities/generateVideo/adapter.d.ts +14 -6
  38. package/dist/esm/activities/generateVideo/adapter.js +6 -3
  39. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  40. package/dist/esm/activities/generateVideo/index.d.ts +5 -4
  41. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  42. package/dist/esm/activities/generateVideo/snap.d.ts +12 -3
  43. package/dist/esm/activities/generateVideo/snap.js +47 -8
  44. package/dist/esm/activities/generateVideo/snap.js.map +1 -1
  45. package/dist/esm/activities/generateVoice/index.d.ts +1 -1
  46. package/dist/esm/activities/generateVoice/index.js +1 -1
  47. package/dist/esm/activities/generateVoice/index.js.map +1 -1
  48. package/dist/esm/activities/index.d.ts +2 -2
  49. package/dist/esm/activities/index.js +2 -2
  50. package/dist/esm/adapter-internals.d.ts +1 -0
  51. package/dist/esm/adapter-internals.js +2 -1
  52. package/dist/esm/client.d.ts +1 -1
  53. package/dist/esm/client.js.map +1 -1
  54. package/dist/esm/index.d.ts +1 -1
  55. package/dist/esm/index.js +2 -2
  56. package/dist/esm/interrupt-resume.js +29 -4
  57. package/dist/esm/interrupt-resume.js.map +1 -1
  58. package/dist/esm/middlewares/otel.d.ts +5 -2
  59. package/dist/esm/middlewares/otel.js +114 -0
  60. package/dist/esm/middlewares/otel.js.map +1 -1
  61. package/dist/esm/types.d.ts +70 -4
  62. package/dist/esm/utilities/ag-ui-wire.js +8 -4
  63. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  64. package/dist/esm/utilities/merge-streams.d.ts +6 -0
  65. package/dist/esm/utilities/merge-streams.js +37 -0
  66. package/dist/esm/utilities/merge-streams.js.map +1 -0
  67. package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
  68. package/dist/esm/utilities/reasoning-encrypted-value.js +11 -1
  69. package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -1
  70. package/dist/esm/utilities/tool-result.d.ts +2 -1
  71. package/dist/esm/utilities/tool-result.js +4 -1
  72. package/dist/esm/utilities/tool-result.js.map +1 -1
  73. package/package.json +3 -3
  74. package/skills/ai-core/chat-experience/SKILL.md +120 -0
  75. package/skills/ai-core/media-generation/SKILL.md +3 -3
  76. package/skills/ai-core/tool-calling/SKILL.md +103 -0
  77. package/src/activities/chat/agents/define-agent.ts +20 -3
  78. package/src/activities/chat/agents/spawn.ts +22 -43
  79. package/src/activities/chat/index.ts +248 -65
  80. package/src/activities/chat/messages.ts +54 -7
  81. package/src/activities/chat/middleware/types.ts +1 -0
  82. package/src/activities/chat/stream/message-updaters.ts +8 -0
  83. package/src/activities/chat/stream/processor.ts +57 -27
  84. package/src/activities/chat/tools/schema-converter.ts +17 -5
  85. package/src/activities/chat/tools/tool-calls.ts +215 -68
  86. package/src/activities/chat/tools/tool-definition.ts +8 -0
  87. package/src/activities/evaluate/adapter.ts +4 -0
  88. package/src/activities/evaluate/index.ts +6 -0
  89. package/src/activities/generateSpeech/index.ts +1 -1
  90. package/src/activities/generateVideo/adapter.ts +21 -7
  91. package/src/activities/generateVideo/index.ts +5 -4
  92. package/src/activities/generateVideo/snap.ts +64 -6
  93. package/src/activities/generateVoice/index.ts +1 -1
  94. package/src/activities/index.ts +2 -1
  95. package/src/adapter-internals.ts +1 -0
  96. package/src/client.ts +1 -0
  97. package/src/index.ts +1 -0
  98. package/src/interrupt-resume.ts +55 -4
  99. package/src/middlewares/otel.ts +161 -3
  100. package/src/types.ts +66 -5
  101. package/src/utilities/ag-ui-wire.ts +17 -2
  102. package/src/utilities/merge-streams.ts +34 -0
  103. package/src/utilities/reasoning-encrypted-value.ts +12 -0
  104. package/src/utilities/tool-result.ts +7 -1
@@ -513,6 +513,9 @@ The post-discovery payload always returns the full description and schema regard
513
513
  `@tanstack/ai-mcp` lets a server-side `chat()` call discover and invoke tools
514
514
  hosted on any MCP server (Streamable HTTP, SSE, or stdio).
515
515
 
516
+ `createMCPClient` tries spec `2026-07-28` first.
517
+ If the server does not support that spec, the client uses the 2025 initialize handshake.
518
+
516
519
  **MCP tools and UI resources:** When an MCP tool result carries a `ui://`
517
520
  resource URI (via `_meta.ui.resourceUri`), TanStack AI surfaces it as a
518
521
  `UIResourcePart` on the assistant `UIMessage` in the client message list.
@@ -723,6 +726,106 @@ export async function POST(request: Request) {
723
726
  }
724
727
  ```
725
728
 
729
+ ### Host your own MCP server
730
+
731
+ Import `createMCPServer` from `@tanstack/ai-mcp/server`.
732
+ Pass tools from `toolDefinition().server()`.
733
+ Call `server.fetch(request)` in your HTTP route.
734
+
735
+ ```typescript
736
+ import { toolDefinition } from '@tanstack/ai'
737
+ import { createMCPServer } from '@tanstack/ai-mcp/server'
738
+ import { z } from 'zod'
739
+
740
+ const getWeather = toolDefinition({
741
+ name: 'get_weather',
742
+ description: 'Current weather for a city',
743
+ inputSchema: z.object({ city: z.string() }),
744
+ }).server(async ({ city }) => {
745
+ return { city, temperature: 18, conditions: 'clear' }
746
+ })
747
+
748
+ const server = createMCPServer({
749
+ name: 'weather',
750
+ version: '1.0.0',
751
+ tools: [getWeather],
752
+ })
753
+
754
+ export function POST(request: Request) {
755
+ return server.fetch(request)
756
+ }
757
+ ```
758
+
759
+ `stdioTransport` from `@tanstack/ai-mcp/stdio` connects your client to a command.
760
+ `serveMCPStdio` from `@tanstack/ai-mcp/server/stdio` serves your server on stdin and stdout.
761
+ Write logs with `console.error`.
762
+ stdout carries only protocol messages.
763
+
764
+ ```typescript
765
+ import { toolDefinition } from '@tanstack/ai'
766
+ import { createMCPServer } from '@tanstack/ai-mcp/server'
767
+ import { serveMCPStdio } from '@tanstack/ai-mcp/server/stdio'
768
+ import { z } from 'zod'
769
+
770
+ const getWeather = toolDefinition({
771
+ name: 'get_weather',
772
+ description: 'Current weather for a city',
773
+ inputSchema: z.object({ city: z.string() }),
774
+ }).server(async ({ city }) => {
775
+ return { city, temperature: 18, conditions: 'clear' }
776
+ })
777
+
778
+ const server = createMCPServer({
779
+ name: 'weather',
780
+ version: '1.0.0',
781
+ tools: [getWeather],
782
+ })
783
+
784
+ serveMCPStdio(server)
785
+ ```
786
+
787
+ The `@tanstack/ai-mcp` skill shows `ctx.context.requestInput` and `ctx.context.sample`.
788
+
789
+ ### Read an MCP input interrupt
790
+
791
+ When `chat()` receives an MCP input request, the run outcome is an interrupt.
792
+ The stream ends with `RUN_FINISHED`.
793
+ The outcome type is `interrupt`.
794
+ Read each interrupt whose `reason` is `mcp_input`.
795
+ The payload key is `tanstack:interruptPayload`.
796
+
797
+ `form` means the server asks the user for input.
798
+ `sampling` means the server asks for a model result.
799
+
800
+ ```typescript
801
+ import { chat, INTERRUPT_PAYLOAD_METADATA_KEY } from '@tanstack/ai'
802
+ import { openaiText } from '@tanstack/ai-openai'
803
+ import { createMCPClient } from '@tanstack/ai-mcp'
804
+
805
+ const client = await createMCPClient({
806
+ transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
807
+ })
808
+
809
+ const stream = chat({
810
+ adapter: openaiText('gpt-5.5'),
811
+ messages: [{ role: 'user', content: 'Weather in Paris?' }],
812
+ tools: await client.tools(),
813
+ })
814
+
815
+ for await (const chunk of stream) {
816
+ if (chunk.type !== 'RUN_FINISHED') continue
817
+ if (chunk.outcome?.type !== 'interrupt') continue
818
+
819
+ for (const item of chunk.outcome.interrupts) {
820
+ if (item.reason !== 'mcp_input') continue
821
+ const payload = item.metadata?.[INTERRUPT_PAYLOAD_METADATA_KEY]
822
+ if (typeof payload !== 'object' || payload === null) continue
823
+ if (!('kind' in payload)) continue
824
+ // payload.kind is 'form' or 'sampling'
825
+ }
826
+ }
827
+ ```
828
+
726
829
  ## Provider Skills
727
830
 
728
831
  > **Not to be confused with `@tanstack/ai-code-mode-snippets`**, whose snippets are TypeScript functions your application generates and runs in its own Code Mode sandbox (a local JS isolate). Provider Skills are hosted, provider-managed bundles that the model loads on demand and runs inside the provider's server-side sandbox.
@@ -2,6 +2,7 @@ import type { SubagentInfo as AGUISubagentInfo } from '@ag-ui/core'
2
2
  import type { InterruptDefinition } from '../../../interrupt-definition'
3
3
  import type {
4
4
  AnyTool,
5
+ InferSchemaType,
5
6
  ModelMessage,
6
7
  RunAgentResumeItem,
7
8
  SchemaInput,
@@ -12,8 +13,16 @@ import type { AnyClientTool } from '../tools/tool-definition'
12
13
 
13
14
  /**
14
15
  * Context the library passes into {@link defineAgent} `run`.
16
+ * `TInput` is the agent's `inputSchema`.
15
17
  */
16
- export interface SubagentRunContext {
18
+ export interface SubagentRunContext<
19
+ TInput extends SchemaInput | undefined = any,
20
+ > {
21
+ /**
22
+ * The input the parent model wrote for this child, checked against
23
+ * `inputSchema`. `undefined` when the agent has no `inputSchema`.
24
+ */
25
+ input: TInput extends SchemaInput ? InferSchemaType<TInput> : undefined
17
26
  messages: Array<UIMessage | ModelMessage>
18
27
  abortSignal?: AbortSignal
19
28
  threadId: string
@@ -53,13 +62,20 @@ export interface DefinedAgent<
53
62
  TSchema extends SchemaInput | undefined = SchemaInput | undefined,
54
63
  TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
55
64
  ReadonlyArray<InterruptDefinition<any, any, any, any>>,
65
+ TInput extends SchemaInput | undefined = any,
56
66
  > extends AGUISubagentInfo {
57
67
  name: TName
58
68
  /** Required here: the router and the synthetic tool both read it. */
59
69
  description: string
60
70
  run: (
61
- ctx: SubagentRunContext,
71
+ ctx: SubagentRunContext<TInput>,
62
72
  ) => AsyncIterable<StreamChunk> | Promise<AsyncIterable<StreamChunk>>
73
+ /**
74
+ * The input the parent model writes when it calls this agent's tool, such
75
+ * as a short brief. `run` reads it as `ctx.input`. Tool mode only: a
76
+ * `subagents.router` cannot start an agent that has `inputSchema`.
77
+ */
78
+ inputSchema?: TInput
63
79
  tools?: TTools
64
80
  interrupts?: TInterrupts
65
81
  outputSchema?: TSchema
@@ -104,7 +120,8 @@ export function defineAgent<
104
120
  const TInterrupts extends ReadonlyArray<
105
121
  InterruptDefinition<any, any, any, any>
106
122
  > = readonly [],
107
- >(agent: DefinedAgent<TName, TTools, TSchema, TInterrupts>) {
123
+ TInput extends SchemaInput | undefined = undefined,
124
+ >(agent: DefinedAgent<TName, TTools, TSchema, TInterrupts, TInput>) {
108
125
  if (agent.name.trim() === '') {
109
126
  throw new Error('defineAgent requires a non-empty name')
110
127
  }
@@ -9,6 +9,7 @@ import {
9
9
  tanstackMetadata,
10
10
  withTanstackMetadata,
11
11
  } from '../../../utilities/merge-metadata'
12
+ import { mergeStreams } from '../../../utilities/merge-streams'
12
13
  import { INTERRUPT_BINDING_METADATA_KEY } from '../../../interrupt-resume'
13
14
  import { EMIT_STREAM_CHUNK, SUBAGENT_TOOL } from '../tools/tool-calls'
14
15
  import type { SubagentToolOutcome } from '../tools/tool-calls'
@@ -93,6 +94,8 @@ export function createSubagentSink(): SubagentSink {
93
94
  /** One child to start, or a suspended child to continue. */
94
95
  export interface SpawnEntry {
95
96
  name: string
97
+ /** The checked tool input for an agent with `inputSchema`. */
98
+ input?: unknown
96
99
  resume?: {
97
100
  subagentRunId: string
98
101
  /** The child's own messages from the interrupted run. */
@@ -248,6 +251,7 @@ function openAgentStream(
248
251
  return spawnAgentStream(
249
252
  agent,
250
253
  {
254
+ input: entry.input,
251
255
  messages: resumed?.messages ?? ctx.messages,
252
256
  ...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}),
253
257
  threadId: childThreadId(bag.sandbox, ctx.threadId, entry.name),
@@ -522,36 +526,6 @@ export async function* spawnAgentStream(
522
526
  }
523
527
  }
524
528
 
525
- async function* mergeAgentStreams(streams: Array<AsyncIterable<StreamChunk>>) {
526
- const readers = streams.map((stream) => {
527
- const iterator = stream[Symbol.asyncIterator]()
528
- return {
529
- iterator,
530
- next: iterator.next(),
531
- }
532
- })
533
-
534
- try {
535
- while (readers.length > 0) {
536
- const indexed = readers.map((reader, index) =>
537
- reader.next.then((result) => ({ index, result, reader })),
538
- )
539
- const winner = await Promise.race(indexed)
540
- if (winner.result.done) {
541
- readers.splice(winner.index, 1)
542
- continue
543
- }
544
- yield winner.result.value
545
- winner.reader.next = winner.reader.iterator.next()
546
- }
547
- } finally {
548
- // The reader stopped early. Close every child so its finally runs.
549
- for (const reader of readers) {
550
- void reader.iterator.return?.().catch(() => {})
551
- }
552
- }
553
- }
554
-
555
529
  /** True when a child in these chunks failed or stopped for outside input. */
556
530
  function stopsSequence(chunks: ReadonlyArray<StreamChunk>, id: string) {
557
531
  return chunks.some(
@@ -617,7 +591,7 @@ export async function* spawnNamedAgents(
617
591
  yield* onlyStream
618
592
  return
619
593
  }
620
- yield* mergeAgentStreams(streams)
594
+ yield* mergeStreams(streams)
621
595
  } finally {
622
596
  group.controller.abort()
623
597
  group.dispose()
@@ -721,8 +695,10 @@ export function createSyntheticSubagentTools(
721
695
  return bag.agents.map((agent) => ({
722
696
  name: agent.name,
723
697
  description: agent.description,
698
+ ...(agent.inputSchema !== undefined && { inputSchema: agent.inputSchema }),
724
699
  [SUBAGENT_TOOL]: true,
725
- execute: async (_input: unknown, context?: unknown) => {
700
+ // The tool loop checks `input` against `inputSchema` before this runs.
701
+ execute: async (input: unknown, context?: unknown) => {
726
702
  const toolContext = context as
727
703
  | {
728
704
  toolCallId?: string
@@ -736,17 +712,20 @@ export function createSyntheticSubagentTools(
736
712
  child.parentToolCallId !== undefined &&
737
713
  child.parentToolCallId === toolCallId,
738
714
  )
739
- const entry: SpawnEntry = suspended
740
- ? {
741
- name: agent.name,
742
- resume: {
743
- subagentRunId: suspended.subagentRunId,
744
- messages: suspended.messages,
745
- entries: suspended.resume,
746
- text: suspended.text,
747
- },
748
- }
749
- : { name: agent.name }
715
+ // An agent without `inputSchema` still gets `{}` from the model. Its
716
+ // `ctx.input` stays undefined.
717
+ const entry: SpawnEntry = {
718
+ name: agent.name,
719
+ ...(agent.inputSchema !== undefined && { input }),
720
+ ...(suspended && {
721
+ resume: {
722
+ subagentRunId: suspended.subagentRunId,
723
+ messages: suspended.messages,
724
+ entries: suspended.resume,
725
+ text: suspended.text,
726
+ },
727
+ }),
728
+ }
750
729
  const sink = createSubagentSink()
751
730
  const link = linkAbort(parent.abortSignal)
752
731
  let subagentRunId = suspended?.subagentRunId ?? ''