@tanstack/ai 0.59.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/esm/activities/chat/adapter.d.ts +9 -0
- package/dist/esm/activities/chat/adapter.js +1 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
- package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
- package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
- package/dist/esm/activities/chat/agents/spawn.js +8 -5
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
- package/dist/esm/activities/chat/index.js +289 -78
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +35 -19
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
- package/dist/esm/activities/chat/stream/message-updaters.js +11 -3
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +11 -10
- package/dist/esm/activities/chat/stream/processor.js +51 -28
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +57 -16
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
- package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/embed/adapter.d.ts +7 -0
- package/dist/esm/activities/embed/adapter.js +1 -0
- package/dist/esm/activities/embed/adapter.js.map +1 -1
- package/dist/esm/activities/embed/index.js +2 -0
- package/dist/esm/activities/embed/index.js.map +1 -1
- package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
- package/dist/esm/activities/evaluate/adapter.js.map +1 -1
- package/dist/esm/activities/evaluate/index.d.ts +4 -0
- package/dist/esm/activities/evaluate/index.js +3 -1
- package/dist/esm/activities/evaluate/index.js.map +1 -1
- package/dist/esm/activities/files/adapter.d.ts +97 -0
- package/dist/esm/activities/files/adapter.js +45 -0
- package/dist/esm/activities/files/adapter.js.map +1 -0
- package/dist/esm/activities/files/index.d.ts +66 -0
- package/dist/esm/activities/files/index.js +78 -0
- package/dist/esm/activities/files/index.js.map +1 -0
- package/dist/esm/activities/generateImage/adapter.d.ts +8 -0
- package/dist/esm/activities/generateImage/adapter.js +1 -0
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +2 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.d.ts +8 -0
- package/dist/esm/activities/generateVideo/adapter.js +1 -0
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +3 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateWorld/adapter.d.ts +4 -2
- package/dist/esm/activities/generateWorld/adapter.js.map +1 -1
- package/dist/esm/activities/generateWorld/index.d.ts +4 -3
- package/dist/esm/activities/generateWorld/index.js +5 -4
- package/dist/esm/activities/generateWorld/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +6 -3
- package/dist/esm/activities/index.js +13 -11
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js +8 -8
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/client.d.ts +3 -1
- package/dist/esm/client.js +2 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.js +5 -3
- package/dist/esm/interrupt-resume.js +29 -4
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/middlewares/otel.d.ts +5 -2
- package/dist/esm/middlewares/otel.js +114 -0
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/types.d.ts +114 -14
- package/dist/esm/utilities/ag-ui-wire.js +39 -16
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/content-source.d.ts +60 -0
- package/dist/esm/utilities/content-source.js +85 -0
- package/dist/esm/utilities/content-source.js.map +1 -0
- package/dist/esm/utilities/provider-executed.d.ts +7 -0
- package/dist/esm/utilities/provider-executed.js +10 -1
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/tool-result.d.ts +14 -3
- package/dist/esm/utilities/tool-result.js +26 -3
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +4 -4
- package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
- package/skills/ai-core/chat-experience/SKILL.md +134 -0
- package/skills/ai-core/media-generation/SKILL.md +8 -0
- package/skills/ai-core/tool-calling/SKILL.md +103 -0
- package/src/activities/chat/adapter.ts +10 -0
- package/src/activities/chat/agents/define-agent.ts +20 -3
- package/src/activities/chat/agents/spawn.ts +20 -12
- package/src/activities/chat/index.ts +458 -99
- package/src/activities/chat/messages.ts +52 -6
- package/src/activities/chat/middleware/types.ts +1 -0
- package/src/activities/chat/stream/message-updaters.ts +27 -2
- package/src/activities/chat/stream/processor.ts +81 -49
- package/src/activities/chat/tools/tool-calls.ts +104 -9
- package/src/activities/chat/tools/tool-definition.ts +8 -0
- package/src/activities/embed/adapter.ts +7 -0
- package/src/activities/embed/index.ts +5 -0
- package/src/activities/evaluate/adapter.ts +4 -0
- package/src/activities/evaluate/index.ts +6 -0
- package/src/activities/files/adapter.ts +120 -0
- package/src/activities/files/index.ts +113 -0
- package/src/activities/generateImage/adapter.ts +8 -0
- package/src/activities/generateImage/index.ts +4 -0
- package/src/activities/generateVideo/adapter.ts +8 -0
- package/src/activities/generateVideo/index.ts +7 -0
- package/src/activities/generateWorld/adapter.ts +4 -2
- package/src/activities/generateWorld/index.ts +7 -6
- package/src/activities/index.ts +25 -1
- package/src/activities/summarize/chat-stream-summarize.ts +22 -12
- package/src/client.ts +8 -0
- package/src/index.ts +17 -0
- package/src/interrupt-resume.ts +55 -4
- package/src/middlewares/otel.ts +161 -3
- package/src/types.ts +114 -14
- package/src/utilities/ag-ui-wire.ts +72 -17
- package/src/utilities/content-source.ts +138 -0
- package/src/utilities/provider-executed.ts +13 -0
- package/src/utilities/tool-result.ts +45 -3
|
@@ -299,6 +299,8 @@ import type { UIMessage } from '@tanstack/ai-react'
|
|
|
299
299
|
|
|
300
300
|
function ImagePart({ part }: { part: UIMessage['parts'][number] }) {
|
|
301
301
|
if (part.type !== 'image') return null
|
|
302
|
+
// A provider file handle is an opaque id, so the browser cannot load it.
|
|
303
|
+
if (part.source.type === 'file') return null
|
|
302
304
|
const src =
|
|
303
305
|
part.source.type === 'url'
|
|
304
306
|
? part.source.value
|
|
@@ -307,6 +309,18 @@ function ImagePart({ part }: { part: UIMessage['parts'][number] }) {
|
|
|
307
309
|
}
|
|
308
310
|
```
|
|
309
311
|
|
|
312
|
+
For media reused across turns, upload once via a provider Files adapter
|
|
313
|
+
(`openaiFiles()`, `anthropicFiles()`, `geminiFiles()`, `grokFiles()`,
|
|
314
|
+
`falFiles()`) and send a `{ type: 'file' }` source built with
|
|
315
|
+
`fileSourceFromHandle(handle)` instead of re-sending base64 each request. The
|
|
316
|
+
source is `{ type: 'file', value, provider }`: an opaque handle and the adapter
|
|
317
|
+
that issued it. A different provider (or one without Files API support at all)
|
|
318
|
+
rejects it with a clear error before any request is sent.
|
|
319
|
+
The source crosses the chat wire, so the browser can put it straight into the
|
|
320
|
+
`sendMessage` content. Import `fileSourceFromHandle` from the browser-safe
|
|
321
|
+
`@tanstack/ai/client` entry. See `ai-core/adapter-configuration/SKILL.md` §7
|
|
322
|
+
and `docs/advanced/files-api.md`.
|
|
323
|
+
|
|
310
324
|
### 4. Sending Audio Messages (Browser Recording)
|
|
311
325
|
|
|
312
326
|
Use `useAudioRecorder` from `@tanstack/ai-react` (or `createAudioRecorder` in Svelte) to capture audio in the browser. The resolved `AudioRecording` includes a ready-to-use `part` that slots directly into `sendMessage`.
|
|
@@ -385,6 +399,9 @@ Pass `mcp` to let `chat()` own discovery **and** lifecycle for one or more MCP
|
|
|
385
399
|
clients. Useful when you want minimal boilerplate and don't need to reuse the
|
|
386
400
|
clients across calls.
|
|
387
401
|
|
|
402
|
+
`createMCPClient` tries spec `2026-07-28` first.
|
|
403
|
+
If the server does not support that spec, the client uses the 2025 initialize handshake.
|
|
404
|
+
|
|
388
405
|
```typescript
|
|
389
406
|
// Prop shape:
|
|
390
407
|
// chat({
|
|
@@ -443,6 +460,123 @@ export async function POST(request: Request) {
|
|
|
443
460
|
}
|
|
444
461
|
```
|
|
445
462
|
|
|
463
|
+
**Host an MCP server.** Import `createMCPServer` from `@tanstack/ai-mcp/server`.
|
|
464
|
+
Call `server.fetch(request)` in your HTTP route.
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
468
|
+
import { createMCPServer } from '@tanstack/ai-mcp/server'
|
|
469
|
+
import { z } from 'zod'
|
|
470
|
+
|
|
471
|
+
const getWeather = toolDefinition({
|
|
472
|
+
name: 'get_weather',
|
|
473
|
+
description: 'Current weather for a city',
|
|
474
|
+
inputSchema: z.object({ city: z.string() }),
|
|
475
|
+
}).server(async ({ city }) => {
|
|
476
|
+
return { city, temperature: 18, conditions: 'clear' }
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const server = createMCPServer({
|
|
480
|
+
name: 'weather',
|
|
481
|
+
version: '1.0.0',
|
|
482
|
+
tools: [getWeather],
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
export function handleMcp(request: Request) {
|
|
486
|
+
return server.fetch(request)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Mount handleMcp on GET, POST, and DELETE.
|
|
490
|
+
// GET is the spec 2025 stream.
|
|
491
|
+
// DELETE closes a spec 2025 session.
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
`serveMCPStdio` from `@tanstack/ai-mcp/server/stdio` serves that server on stdin and stdout.
|
|
495
|
+
Write logs with `console.error`.
|
|
496
|
+
stdout carries only protocol messages.
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
500
|
+
import { createMCPServer } from '@tanstack/ai-mcp/server'
|
|
501
|
+
import { serveMCPStdio } from '@tanstack/ai-mcp/server/stdio'
|
|
502
|
+
import { z } from 'zod'
|
|
503
|
+
|
|
504
|
+
const getWeather = toolDefinition({
|
|
505
|
+
name: 'get_weather',
|
|
506
|
+
description: 'Current weather for a city',
|
|
507
|
+
inputSchema: z.object({ city: z.string() }),
|
|
508
|
+
}).server(async ({ city }) => {
|
|
509
|
+
return { city, temperature: 18, conditions: 'clear' }
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
const server = createMCPServer({
|
|
513
|
+
name: 'weather',
|
|
514
|
+
version: '1.0.0',
|
|
515
|
+
tools: [getWeather],
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
serveMCPStdio(server)
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**MCP input interrupt.** When `chat()` receives an MCP input request, the run outcome is an interrupt.
|
|
522
|
+
The stream ends with `RUN_FINISHED`.
|
|
523
|
+
The outcome type is `interrupt`.
|
|
524
|
+
Read each interrupt whose `reason` is `mcp_input`.
|
|
525
|
+
The payload key is `tanstack:interruptPayload`.
|
|
526
|
+
|
|
527
|
+
`form` means the server asks the user for input.
|
|
528
|
+
`sampling` means the server asks for a model result.
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
import { chat, INTERRUPT_PAYLOAD_METADATA_KEY } from '@tanstack/ai'
|
|
532
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
533
|
+
import { createMCPClient } from '@tanstack/ai-mcp'
|
|
534
|
+
|
|
535
|
+
const client = await createMCPClient({
|
|
536
|
+
transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const stream = chat({
|
|
541
|
+
adapter: openaiText('gpt-5.6'),
|
|
542
|
+
messages: [{ role: 'user', content: 'Weather in Paris?' }],
|
|
543
|
+
tools: await client.tools(),
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
for await (const chunk of stream) {
|
|
547
|
+
if (chunk.type !== 'RUN_FINISHED') continue
|
|
548
|
+
if (chunk.outcome?.type !== 'interrupt') continue
|
|
549
|
+
|
|
550
|
+
for (const item of chunk.outcome.interrupts) {
|
|
551
|
+
if (item.reason !== 'mcp_input') continue
|
|
552
|
+
const payload = item.metadata?.[INTERRUPT_PAYLOAD_METADATA_KEY]
|
|
553
|
+
if (typeof payload !== 'object' || payload === null) continue
|
|
554
|
+
if (!('kind' in payload)) continue
|
|
555
|
+
// payload.kind is 'form' or 'sampling'
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
} finally {
|
|
559
|
+
await client.close()
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
To continue, answer the interrupt:
|
|
564
|
+
|
|
565
|
+
1. In `useChat`, the item `kind` is `generic`.
|
|
566
|
+
2. For a `form`, call `resolveInterrupt` with an object that matches `request.requestedSchema`. A `createMCPServer` server asks for `{ value: string }`.
|
|
567
|
+
3. For `sampling`, call `resolveInterrupt` with the reply text.
|
|
568
|
+
4. Call `cancel()` to decline.
|
|
569
|
+
5. The route passes `parentRunId` and `resume` to `chat()`. The tool runs again with the answer.
|
|
570
|
+
|
|
571
|
+
```tsx ignore
|
|
572
|
+
// `interrupt` is one item from `useChat().interrupts`.
|
|
573
|
+
if (interrupt.reason === 'mcp_input' && interrupt.kind === 'generic') {
|
|
574
|
+
interrupt.resolveInterrupt({ value: 'Paris' })
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
This works on spec `2026-07-28`. On spec 2025, the server asks the client in the middle of the tool call. `chat()` cannot pause that call, so the tool call fails.
|
|
579
|
+
|
|
446
580
|
### 7. Queueing Messages Sent While Streaming
|
|
447
581
|
|
|
448
582
|
By default, a `sendMessage` call that arrives while a stream is in flight is
|
|
@@ -283,6 +283,14 @@ await generateVideo({
|
|
|
283
283
|
})
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
Reference images / start frames that are reused (or arrive as inline base64 on
|
|
287
|
+
memory-constrained runtimes) can instead be uploaded once via the provider's
|
|
288
|
+
Files adapter and referenced with `source: fileSourceFromHandle(handle)` —
|
|
289
|
+
supported for Gemini image generation (`geminiFiles()`) and fal image/video
|
|
290
|
+
inputs (`falFiles()`). Endpoints that require raw bytes (OpenAI `images/edits`,
|
|
291
|
+
Sora `input_reference`, Gemini Veo) reject file sources with a clear error.
|
|
292
|
+
See `ai-core/adapter-configuration/SKILL.md` §7.
|
|
293
|
+
|
|
286
294
|
**URL inputs that require an upload throw by default.** Most adapters pass a
|
|
287
295
|
`type: 'url'` source straight through to the provider. Three paths can't —
|
|
288
296
|
OpenAI `images.edit()`, OpenAI Sora `input_reference`, and Gemini **Veo** —
|
|
@@ -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.
|
|
@@ -89,6 +89,15 @@ export interface TextAdapter<
|
|
|
89
89
|
*/
|
|
90
90
|
readonly requires?: ReadonlyArray<CapabilityHandle>
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Declares that this adapter can consume `{ type: 'file' }` content sources
|
|
94
|
+
* (provider Files API references). `chat()` rejects file sources in preflight
|
|
95
|
+
* for adapters that don't declare this, so an adapter written before the
|
|
96
|
+
* file arm existed fails closed instead of silently mis-mapping a reference
|
|
97
|
+
* onto its URL/data branch.
|
|
98
|
+
*/
|
|
99
|
+
readonly supportsFileSources?: boolean
|
|
100
|
+
|
|
92
101
|
/**
|
|
93
102
|
* @internal Type-only properties for inference. Not assigned at runtime.
|
|
94
103
|
*/
|
|
@@ -209,6 +218,7 @@ export abstract class BaseTextAdapter<
|
|
|
209
218
|
abstract readonly name: string
|
|
210
219
|
readonly model: TModel
|
|
211
220
|
readonly requires?: ReadonlyArray<CapabilityHandle> = undefined
|
|
221
|
+
readonly supportsFileSources: boolean = false
|
|
212
222
|
|
|
213
223
|
// Type-only property - never assigned at runtime
|
|
214
224
|
declare '~types': {
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -93,6 +93,8 @@ export function createSubagentSink(): SubagentSink {
|
|
|
93
93
|
/** One child to start, or a suspended child to continue. */
|
|
94
94
|
export interface SpawnEntry {
|
|
95
95
|
name: string
|
|
96
|
+
/** The checked tool input for an agent with `inputSchema`. */
|
|
97
|
+
input?: unknown
|
|
96
98
|
resume?: {
|
|
97
99
|
subagentRunId: string
|
|
98
100
|
/** The child's own messages from the interrupted run. */
|
|
@@ -248,6 +250,7 @@ function openAgentStream(
|
|
|
248
250
|
return spawnAgentStream(
|
|
249
251
|
agent,
|
|
250
252
|
{
|
|
253
|
+
input: entry.input,
|
|
251
254
|
messages: resumed?.messages ?? ctx.messages,
|
|
252
255
|
...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}),
|
|
253
256
|
threadId: childThreadId(bag.sandbox, ctx.threadId, entry.name),
|
|
@@ -721,8 +724,10 @@ export function createSyntheticSubagentTools(
|
|
|
721
724
|
return bag.agents.map((agent) => ({
|
|
722
725
|
name: agent.name,
|
|
723
726
|
description: agent.description,
|
|
727
|
+
...(agent.inputSchema !== undefined && { inputSchema: agent.inputSchema }),
|
|
724
728
|
[SUBAGENT_TOOL]: true,
|
|
725
|
-
|
|
729
|
+
// The tool loop checks `input` against `inputSchema` before this runs.
|
|
730
|
+
execute: async (input: unknown, context?: unknown) => {
|
|
726
731
|
const toolContext = context as
|
|
727
732
|
| {
|
|
728
733
|
toolCallId?: string
|
|
@@ -736,17 +741,20 @@ export function createSyntheticSubagentTools(
|
|
|
736
741
|
child.parentToolCallId !== undefined &&
|
|
737
742
|
child.parentToolCallId === toolCallId,
|
|
738
743
|
)
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
744
|
+
// An agent without `inputSchema` still gets `{}` from the model. Its
|
|
745
|
+
// `ctx.input` stays undefined.
|
|
746
|
+
const entry: SpawnEntry = {
|
|
747
|
+
name: agent.name,
|
|
748
|
+
...(agent.inputSchema !== undefined && { input }),
|
|
749
|
+
...(suspended && {
|
|
750
|
+
resume: {
|
|
751
|
+
subagentRunId: suspended.subagentRunId,
|
|
752
|
+
messages: suspended.messages,
|
|
753
|
+
entries: suspended.resume,
|
|
754
|
+
text: suspended.text,
|
|
755
|
+
},
|
|
756
|
+
}),
|
|
757
|
+
}
|
|
750
758
|
const sink = createSubagentSink()
|
|
751
759
|
const link = linkAbort(parent.abortSignal)
|
|
752
760
|
let subagentRunId = suspended?.subagentRunId ?? ''
|