@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
package/src/types.ts
CHANGED
|
@@ -100,6 +100,9 @@ export type ToolResultState =
|
|
|
100
100
|
| 'complete' // Result is complete
|
|
101
101
|
| 'error' // Error occurred
|
|
102
102
|
|
|
103
|
+
/** Why a tool result ended without executing successfully. */
|
|
104
|
+
export type ToolResultOutcome = 'cancelled' | 'denied'
|
|
105
|
+
|
|
103
106
|
export type ToolOutputState = 'output-available' | 'output-error'
|
|
104
107
|
|
|
105
108
|
/**
|
|
@@ -196,6 +199,13 @@ export interface ToolCall<TMetadata = unknown> extends Omit<
|
|
|
196
199
|
metadata?: TMetadata
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
/** One source link from a provider-executed web search. */
|
|
203
|
+
export interface ProviderExecutedToolSource {
|
|
204
|
+
url: string
|
|
205
|
+
title?: string
|
|
206
|
+
pageAge?: string
|
|
207
|
+
}
|
|
208
|
+
|
|
199
209
|
/**
|
|
200
210
|
* Convention for tool-call `metadata` that marks a call as **provider-executed**
|
|
201
211
|
* — run by the provider's own infrastructure (e.g. Anthropic `web_search` /
|
|
@@ -209,10 +219,12 @@ export interface ToolCall<TMetadata = unknown> extends Omit<
|
|
|
209
219
|
*
|
|
210
220
|
* Provider-specific payloads live under a namespaced key (e.g. `anthropic`),
|
|
211
221
|
* keeping this convention opaque to the framework core. The index signature
|
|
212
|
-
* preserves those per-adapter fields.
|
|
222
|
+
* preserves those per-adapter fields. `sources` is the normalized list of
|
|
223
|
+
* links a web search used, shared across providers.
|
|
213
224
|
*/
|
|
214
225
|
export interface ProviderExecutedToolMetadata {
|
|
215
226
|
providerExecuted?: boolean
|
|
227
|
+
sources?: Array<ProviderExecutedToolSource>
|
|
216
228
|
[key: string]: unknown
|
|
217
229
|
}
|
|
218
230
|
|
|
@@ -239,8 +251,24 @@ export interface ContentPartUrlSource extends AGUIUrlSource {}
|
|
|
239
251
|
/**
|
|
240
252
|
* A provider-issued file handle (Files API). AG-UI `FileSource`: the handle
|
|
241
253
|
* is opaque, do not fetch or parse it.
|
|
254
|
+
*
|
|
255
|
+
* The media is uploaded once via a `files` adapter (`openaiFiles()`,
|
|
256
|
+
* `anthropicFiles()`, `geminiFiles()`, `grokFiles()`, `falFiles()`) and
|
|
257
|
+
* referenced here by the returned handle instead of re-sending base64 or a
|
|
258
|
+
* public URL on each request. Only the provider that minted a handle can
|
|
259
|
+
* resolve it. Adapters that cannot consume file handles at all are rejected
|
|
260
|
+
* by the activity-layer preflight before mapping starts.
|
|
242
261
|
*/
|
|
243
|
-
export interface ContentPartFileSource
|
|
262
|
+
export interface ContentPartFileSource<
|
|
263
|
+
TProvider extends string = string,
|
|
264
|
+
> extends AGUIFileSource {
|
|
265
|
+
/**
|
|
266
|
+
* The adapter name of the provider that issued the handle (`'openai'`,
|
|
267
|
+
* `'gemini'`, ...), the same id TanStack reports as the usage provider.
|
|
268
|
+
* When present, an adapter rejects a handle another provider issued.
|
|
269
|
+
*/
|
|
270
|
+
provider?: TProvider
|
|
271
|
+
}
|
|
244
272
|
|
|
245
273
|
/**
|
|
246
274
|
* Where a media part's bytes come from: inline data, a URL, or a provider
|
|
@@ -426,6 +454,8 @@ export interface ToolResultPart {
|
|
|
426
454
|
toolCallId: string
|
|
427
455
|
content: string | Array<ContentPart>
|
|
428
456
|
state: ToolResultState
|
|
457
|
+
/** Set when the user or middleware cancelled or denied the tool call; state remains `error`. */
|
|
458
|
+
outcome?: ToolResultOutcome
|
|
429
459
|
error?: string // Error message if state is "error"
|
|
430
460
|
metadata?: Record<string, unknown>
|
|
431
461
|
createdAt?: Date
|
|
@@ -544,6 +574,12 @@ export interface TanStackMessageMetadata {
|
|
|
544
574
|
model?: string
|
|
545
575
|
/** Parent chat run that produced this assistant message. */
|
|
546
576
|
runId?: string
|
|
577
|
+
/**
|
|
578
|
+
* The chat run that produced this assistant message. `withPersistence` sets
|
|
579
|
+
* `id`. `reconstructChat` with `includeRuns: true` adds the finished run's
|
|
580
|
+
* timings, in epoch ms.
|
|
581
|
+
*/
|
|
582
|
+
run?: { id: string; startedAt?: number; finishedAt?: number }
|
|
547
583
|
/** Card data on a child wire message. See `uiMessagesToWire`. */
|
|
548
584
|
subagent?: SubagentWireInfo
|
|
549
585
|
/** Thinking signature for a `role: 'reasoning'` fan-out message. */
|
|
@@ -555,6 +591,8 @@ export interface TanStackMessageMetadata {
|
|
|
555
591
|
createdAt?: string
|
|
556
592
|
content?: Array<ContentPart>
|
|
557
593
|
}
|
|
594
|
+
/** Outcome of a cancelled or denied tool result; when present, the UI state is `error`. */
|
|
595
|
+
toolResultOutcome?: ToolResultOutcome
|
|
558
596
|
structuredOutput?: {
|
|
559
597
|
status?: 'streaming' | 'complete' | 'error'
|
|
560
598
|
partial?: unknown
|
|
@@ -663,6 +701,15 @@ export interface EmitCustomEventOptions {
|
|
|
663
701
|
batch?: boolean
|
|
664
702
|
}
|
|
665
703
|
|
|
704
|
+
/**
|
|
705
|
+
* The user's answer to an `mcp_input` interrupt.
|
|
706
|
+
* `resolved` carries the `payload` from `resolveInterrupt`.
|
|
707
|
+
* `cancelled` means the user called `cancel()`.
|
|
708
|
+
*/
|
|
709
|
+
export type ToolInputResponse =
|
|
710
|
+
| { status: 'resolved'; payload: unknown }
|
|
711
|
+
| { status: 'cancelled' }
|
|
712
|
+
|
|
666
713
|
/**
|
|
667
714
|
* Context passed to tool execute functions, providing capabilities like
|
|
668
715
|
* emitting custom events during execution.
|
|
@@ -677,6 +724,12 @@ export type ToolExecutionContext<TContext = unknown> =
|
|
|
677
724
|
* e.g. MCP `callTool` — should forward this to cancel in-flight work.
|
|
678
725
|
*/
|
|
679
726
|
abortSignal?: AbortSignal
|
|
727
|
+
/**
|
|
728
|
+
* The answer to the input request that this tool call raised in the
|
|
729
|
+
* previous run. It is set only when the run resumes an `mcp_input`
|
|
730
|
+
* interrupt for this tool call.
|
|
731
|
+
*/
|
|
732
|
+
inputResponse?: ToolInputResponse
|
|
680
733
|
/**
|
|
681
734
|
* Emit a custom event during tool execution.
|
|
682
735
|
* Events are streamed to the client in real-time as AG-UI CUSTOM events.
|
|
@@ -2305,7 +2358,7 @@ export interface VideoUrlResult {
|
|
|
2305
2358
|
// ============================================================================
|
|
2306
2359
|
|
|
2307
2360
|
/**
|
|
2308
|
-
* Options for world generation (live
|
|
2361
|
+
* Options for world generation (live session or finished job).
|
|
2309
2362
|
*
|
|
2310
2363
|
* @experimental World generation is an experimental feature and may change.
|
|
2311
2364
|
*/
|
|
@@ -2317,8 +2370,10 @@ export interface WorldGenerationOptions<
|
|
|
2317
2370
|
/** Natural-language description of the world or scene */
|
|
2318
2371
|
prompt: string
|
|
2319
2372
|
/**
|
|
2320
|
-
* Provider
|
|
2321
|
-
*
|
|
2373
|
+
* Provider-specific options. Live adapters (Reactor) use mint fields here.
|
|
2374
|
+
* Job adapters (World Labs) use image/video inputs, `wait`, and poll.
|
|
2375
|
+
* Reactor resolution/seed/audio are browser `sendCommand` fields, not
|
|
2376
|
+
* token-mint fields.
|
|
2322
2377
|
*/
|
|
2323
2378
|
modelOptions?: TProviderOptions
|
|
2324
2379
|
/**
|
|
@@ -2335,28 +2390,73 @@ export interface WorldGenerationOptions<
|
|
|
2335
2390
|
abortSignal?: AbortSignal
|
|
2336
2391
|
}
|
|
2337
2392
|
|
|
2393
|
+
/**
|
|
2394
|
+
* Assets from a finished world job. Live session adapters omit this.
|
|
2395
|
+
* URLs are often signed CDN links. They can expire and may need a proxy
|
|
2396
|
+
* to fetch from a browser.
|
|
2397
|
+
*
|
|
2398
|
+
* @experimental World generation is an experimental feature and may change.
|
|
2399
|
+
*/
|
|
2400
|
+
export interface WorldGenerationAssets {
|
|
2401
|
+
/** Auto-generated scene description */
|
|
2402
|
+
caption?: string
|
|
2403
|
+
/** Preview image URL */
|
|
2404
|
+
thumbnailUrl?: string
|
|
2405
|
+
splats?: {
|
|
2406
|
+
/** Quality-key map of splat URLs (`100k`, `500k`, `full_res`, …) */
|
|
2407
|
+
spzUrls?: Record<string, string>
|
|
2408
|
+
metricScaleFactor?: number
|
|
2409
|
+
groundPlaneOffset?: number
|
|
2410
|
+
}
|
|
2411
|
+
mesh?: {
|
|
2412
|
+
colliderMeshUrl?: string
|
|
2413
|
+
hqMeshUrl?: string
|
|
2414
|
+
fullResMeshUrl?: string
|
|
2415
|
+
}
|
|
2416
|
+
imagery?: {
|
|
2417
|
+
panoUrl?: string
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2338
2421
|
/**
|
|
2339
2422
|
* Result of world generation. JSON-serializable so a server route can return
|
|
2340
|
-
* it to a browser.
|
|
2341
|
-
*
|
|
2423
|
+
* it to a browser.
|
|
2424
|
+
*
|
|
2425
|
+
* Live adapters (Reactor): `status: 'ready'` with `token` and token
|
|
2426
|
+
* `expiresAt`. The browser uses `token` + `model` to open the session.
|
|
2427
|
+
*
|
|
2428
|
+
* Job adapters (World Labs): `status: 'ready'` with viewer `url` and
|
|
2429
|
+
* `worldId`, or `status: 'waiting'` with `operationId` and no `url`.
|
|
2430
|
+
* `expiresAt` on a job is operation expiry, not a session token.
|
|
2342
2431
|
*
|
|
2343
2432
|
* @experimental World generation is an experimental feature and may change.
|
|
2344
2433
|
*/
|
|
2345
2434
|
export interface WorldGenerationResult {
|
|
2346
2435
|
/** Unique identifier for this generation */
|
|
2347
2436
|
id: string
|
|
2348
|
-
/** Model used for generation (provider connect slug) */
|
|
2437
|
+
/** Model used for generation (provider connect slug or model id) */
|
|
2349
2438
|
model: string
|
|
2350
|
-
/** Short-lived session token for
|
|
2351
|
-
token
|
|
2352
|
-
/**
|
|
2353
|
-
|
|
2354
|
-
|
|
2439
|
+
/** Short-lived session token for a live client connection */
|
|
2440
|
+
token?: string
|
|
2441
|
+
/**
|
|
2442
|
+
* Expiry as milliseconds since epoch. Live adapters: session token.
|
|
2443
|
+
* Job adapters: operation expiry when the provider sends it.
|
|
2444
|
+
*/
|
|
2445
|
+
expiresAt?: number
|
|
2446
|
+
/** Prompt used to generate the world, or the prompt the client should send */
|
|
2355
2447
|
prompt: string
|
|
2356
|
-
/**
|
|
2448
|
+
/** Status after the server half finishes */
|
|
2357
2449
|
status: 'ready' | 'waiting'
|
|
2358
2450
|
/** Provider session id, when the adapter created one */
|
|
2359
2451
|
sessionId?: string
|
|
2452
|
+
/** Viewer URL for a finished world job (not an asset download URL) */
|
|
2453
|
+
url?: string
|
|
2454
|
+
/** Provider world id for a finished or in-progress job */
|
|
2455
|
+
worldId?: string
|
|
2456
|
+
/** Provider operation id for a long-running world job */
|
|
2457
|
+
operationId?: string
|
|
2458
|
+
/** Assets when a world job has finished and the provider returned them */
|
|
2459
|
+
assets?: WorldGenerationAssets
|
|
2360
2460
|
/** Token usage / billing, when the adapter can report it */
|
|
2361
2461
|
usage?: TokenUsage
|
|
2362
2462
|
}
|
|
@@ -14,11 +14,13 @@ import type {
|
|
|
14
14
|
StructuredOutputPart,
|
|
15
15
|
SubagentPart,
|
|
16
16
|
TanStackMessageMetadata,
|
|
17
|
+
ToolResultOutcome,
|
|
17
18
|
UIMessage,
|
|
18
19
|
UIResourcePart,
|
|
19
20
|
} from '../types'
|
|
20
21
|
import type { MetadataRecord } from './merge-metadata'
|
|
21
22
|
import { tanstackMetadata } from './merge-metadata'
|
|
23
|
+
import { isProviderExecutedToolCall } from './provider-executed'
|
|
22
24
|
import { normalizeToolResult } from './tool-result'
|
|
23
25
|
import { wireSubagentInfo, wireSubagentRunId } from './subagent-wire'
|
|
24
26
|
import type { SubagentWireInfo } from './subagent-wire'
|
|
@@ -48,6 +50,7 @@ function rebuiltToolMetadata(
|
|
|
48
50
|
id: string | undefined,
|
|
49
51
|
content: string | null | Array<ContentPart>,
|
|
50
52
|
anchorOwnsUiResources = false,
|
|
53
|
+
outcome?: ToolResultOutcome,
|
|
51
54
|
): MetadataRecord | undefined {
|
|
52
55
|
const source: MetadataRecord = isRecord(metadata) ? metadata : {}
|
|
53
56
|
const tanstack = isRecord(source.tanstack) ? { ...source.tanstack } : {}
|
|
@@ -60,7 +63,11 @@ function rebuiltToolMetadata(
|
|
|
60
63
|
}
|
|
61
64
|
const result = {
|
|
62
65
|
...source,
|
|
63
|
-
tanstack: {
|
|
66
|
+
tanstack: {
|
|
67
|
+
...tanstack,
|
|
68
|
+
...(outcome !== undefined && { toolResultOutcome: outcome }),
|
|
69
|
+
toolResult,
|
|
70
|
+
},
|
|
64
71
|
}
|
|
65
72
|
return Object.keys(result).length ? result : undefined
|
|
66
73
|
}
|
|
@@ -171,9 +178,45 @@ export function uiMessagesToWire(
|
|
|
171
178
|
continue
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
// assistant:
|
|
181
|
+
// assistant: reasoning fan-outs, then anchor, then tool fan-outs.
|
|
182
|
+
//
|
|
183
|
+
// Provider-executed tools (Anthropic web_search / web_fetch) run inside one
|
|
184
|
+
// provider response, and the provider signs every thinking block against
|
|
185
|
+
// the blocks before it. Emitting all reasoning first and a single anchor
|
|
186
|
+
// with the joined text and every tool call turns
|
|
187
|
+
// "thinking, tool, thinking, text, tool" into
|
|
188
|
+
// "thinking, thinking, text, tool, tool", and the provider rejects the next
|
|
189
|
+
// turn ("thinking blocks in the latest assistant message cannot be
|
|
190
|
+
// modified"). Split the message into ordered segments at each thinking
|
|
191
|
+
// part that follows a provider-executed tool call, the same rule
|
|
192
|
+
// buildAssistantMessages applies, so the wire keeps the signed order.
|
|
193
|
+
// Segments after the first get derived anchor ids; strict AG-UI consumers
|
|
194
|
+
// still see plain anchors.
|
|
195
|
+
type Segment = {
|
|
196
|
+
thinking: Array<Extract<MessagePart, { type: 'thinking' }>>
|
|
197
|
+
parts: Array<MessagePart>
|
|
198
|
+
}
|
|
199
|
+
let current: Segment = { thinking: [], parts: [] }
|
|
200
|
+
const segments: Array<Segment> = [current]
|
|
175
201
|
for (const part of parts) {
|
|
176
202
|
if (part.type === 'thinking') {
|
|
203
|
+
if (
|
|
204
|
+
current.parts.some(
|
|
205
|
+
(p) => p.type === 'tool-call' && isProviderExecutedToolCall(p),
|
|
206
|
+
)
|
|
207
|
+
) {
|
|
208
|
+
current = { thinking: [part], parts: [] }
|
|
209
|
+
segments.push(current)
|
|
210
|
+
} else {
|
|
211
|
+
current.thinking.push(part)
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
current.parts.push(part)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
segments.forEach((segment, index) => {
|
|
219
|
+
for (const part of segment.thinking) {
|
|
177
220
|
const reasoning: WireReasoningMessage = {
|
|
178
221
|
role: 'reasoning',
|
|
179
222
|
id: uniqueWireId(deriveReasoningId(uiMessage.id, part), usedWireIds),
|
|
@@ -184,22 +227,29 @@ export function uiMessagesToWire(
|
|
|
184
227
|
}
|
|
185
228
|
wire.push(reasoning)
|
|
186
229
|
}
|
|
187
|
-
}
|
|
188
230
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
231
|
+
const text = collectText(segment.parts)
|
|
232
|
+
const toolCalls = collectToolCalls(segment.parts)
|
|
233
|
+
const anchorMessage: UIMessage =
|
|
234
|
+
index === 0
|
|
235
|
+
? uiMessage
|
|
236
|
+
: {
|
|
237
|
+
...uiMessage,
|
|
238
|
+
id: uniqueWireId(`${uiMessage.id}-segment-${index}`, usedWireIds),
|
|
239
|
+
}
|
|
240
|
+
wire.push(
|
|
241
|
+
toAnchor(
|
|
242
|
+
anchorMessage,
|
|
243
|
+
'assistant',
|
|
244
|
+
{
|
|
245
|
+
...(text !== '' && { content: text }),
|
|
246
|
+
...(toolCalls && { toolCalls }),
|
|
247
|
+
},
|
|
248
|
+
segment.parts,
|
|
249
|
+
includeSnapshotStructuredOutput,
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
})
|
|
203
253
|
|
|
204
254
|
const explicitToolResults = new Set(
|
|
205
255
|
parts.flatMap((part) =>
|
|
@@ -218,6 +268,7 @@ export function uiMessagesToWire(
|
|
|
218
268
|
part.id,
|
|
219
269
|
part.content,
|
|
220
270
|
true,
|
|
271
|
+
part.outcome,
|
|
221
272
|
)
|
|
222
273
|
wire.push({
|
|
223
274
|
role: 'tool',
|
|
@@ -262,6 +313,8 @@ export function uiMessagesToWire(
|
|
|
262
313
|
undefined,
|
|
263
314
|
undefined,
|
|
264
315
|
result,
|
|
316
|
+
false,
|
|
317
|
+
part.approval?.approved === false ? 'denied' : undefined,
|
|
265
318
|
),
|
|
266
319
|
})
|
|
267
320
|
}
|
|
@@ -341,6 +394,8 @@ function messageMetadata(
|
|
|
341
394
|
tanstack.model = previousTanstack.model
|
|
342
395
|
if (previousTanstack?.runId !== undefined)
|
|
343
396
|
tanstack.runId = previousTanstack.runId
|
|
397
|
+
if (previousTanstack?.run?.id !== undefined)
|
|
398
|
+
tanstack.run = { id: previousTanstack.run.id }
|
|
344
399
|
if (previousTanstack?.signature !== undefined)
|
|
345
400
|
tanstack.signature = previousTanstack.signature
|
|
346
401
|
const createdAt = coerceCreatedAt(msg.createdAt)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ContentPartFileSource, ContentPartSource } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Narrow a {@link ContentPartSource} to the provider-file-reference arm.
|
|
5
|
+
*
|
|
6
|
+
* Issuer adapters use this to route a file source to their native wire field;
|
|
7
|
+
* everyone else is protected by the core preflight (see
|
|
8
|
+
* {@link assertMessagesFileSourceSupport}) plus a defensive throw at their own
|
|
9
|
+
* mapping site.
|
|
10
|
+
*/
|
|
11
|
+
export function isFileSource(
|
|
12
|
+
source: ContentPartSource,
|
|
13
|
+
): source is ContentPartFileSource {
|
|
14
|
+
return source.type === 'file'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the handle `providerName` should send for a file source.
|
|
19
|
+
*
|
|
20
|
+
* A file source carries one opaque handle (`value`) and, optionally, the
|
|
21
|
+
* provider that issued it. An adapter always knows which provider it talks
|
|
22
|
+
* to, so a source that names no provider is taken as-is.
|
|
23
|
+
*
|
|
24
|
+
* @throws when the source names a different issuing provider. A handle only
|
|
25
|
+
* resolves at the provider that minted it.
|
|
26
|
+
*/
|
|
27
|
+
export function fileReferenceFor(
|
|
28
|
+
source: ContentPartFileSource,
|
|
29
|
+
providerName: string,
|
|
30
|
+
): string {
|
|
31
|
+
if (source.provider !== undefined && source.provider !== providerName) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`${providerName}: file source was issued by ${source.provider}. ` +
|
|
34
|
+
`A provider file handle only works with the provider that issued ` +
|
|
35
|
+
`it. Upload the file with ${providerName}Files(), or pass a data or ` +
|
|
36
|
+
`url source instead.`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
return source.value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build the standard error a non-issuer adapter throws when it encounters a
|
|
44
|
+
* `{ type: 'file' }` source it can't consume — either because the provider has
|
|
45
|
+
* no file-reference input surface, or because the endpoint requires raw bytes
|
|
46
|
+
* (image edits, Veo) rather than a reference.
|
|
47
|
+
*
|
|
48
|
+
* @param detail Optional context appended to the message (e.g. a modality or
|
|
49
|
+
* endpoint name, or a pointer to the adapter that does support references).
|
|
50
|
+
* When provided it replaces the generic remediation tail, so a site-specific
|
|
51
|
+
* hint ("pass inline bytes") is never contradicted by generic advice.
|
|
52
|
+
*/
|
|
53
|
+
export function unsupportedFileSourceError(
|
|
54
|
+
providerName: string,
|
|
55
|
+
detail?: string,
|
|
56
|
+
): Error {
|
|
57
|
+
return new Error(
|
|
58
|
+
`${providerName} does not support provider file-handle sources ` +
|
|
59
|
+
`({ type: 'file' })` +
|
|
60
|
+
(detail
|
|
61
|
+
? ` ${detail}.`
|
|
62
|
+
: `. Pass a data or url source, or upload via the provider's files ` +
|
|
63
|
+
`adapter where supported.`),
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The slice of an adapter the file-source preflight reads. Adapters that can
|
|
69
|
+
* consume `{ type: 'file' }` sources declare `supportsFileSources: true`;
|
|
70
|
+
* everything else — including adapters written before this arm existed —
|
|
71
|
+
* fails closed at the activity layer instead of falling through to a
|
|
72
|
+
* URL/data branch and silently mis-mapping the reference.
|
|
73
|
+
*/
|
|
74
|
+
export interface FileSourceCapable {
|
|
75
|
+
name: string
|
|
76
|
+
supportsFileSources?: boolean
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True when a content-part-like value carries a `{ type: 'file' }` source. */
|
|
80
|
+
function partHasFileSource(part: unknown): boolean {
|
|
81
|
+
if (typeof part !== 'object' || part === null) return false
|
|
82
|
+
const source = (part as { source?: unknown }).source
|
|
83
|
+
return (
|
|
84
|
+
typeof source === 'object' &&
|
|
85
|
+
source !== null &&
|
|
86
|
+
(source as { type?: unknown }).type === 'file'
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* True when `value` is a content part with a file source, or an array
|
|
92
|
+
* (possibly nested — fused embedding items) that contains one.
|
|
93
|
+
*/
|
|
94
|
+
function inputHasFileSource(value: unknown): boolean {
|
|
95
|
+
if (Array.isArray(value)) return value.some(inputHasFileSource)
|
|
96
|
+
return partHasFileSource(value)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Fail-closed preflight for media prompts and embedding inputs
|
|
101
|
+
* (`generateImage` / `generateVideo` / `embed`): throws when the input
|
|
102
|
+
* carries a `{ type: 'file' }` source and the adapter hasn't declared
|
|
103
|
+
* `supportsFileSources`. Runs in the activity dispatcher — the same layer
|
|
104
|
+
* that validates modality — so an adapter that predates the file arm can
|
|
105
|
+
* never receive one. Walks a single part, an array of parts, and nested
|
|
106
|
+
* arrays (fused embedding items).
|
|
107
|
+
*/
|
|
108
|
+
export function assertPromptFileSourceSupport(
|
|
109
|
+
adapter: FileSourceCapable,
|
|
110
|
+
prompt: unknown,
|
|
111
|
+
): void {
|
|
112
|
+
if (adapter.supportsFileSources === true) return
|
|
113
|
+
if (inputHasFileSource(prompt)) {
|
|
114
|
+
throw unsupportedFileSourceError(adapter.name)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Fail-closed preflight for chat messages: throws when any message content
|
|
120
|
+
* part carries a `{ type: 'file' }` source and the adapter hasn't declared
|
|
121
|
+
* `supportsFileSources`. See {@link assertPromptFileSourceSupport}.
|
|
122
|
+
*/
|
|
123
|
+
export function assertMessagesFileSourceSupport(
|
|
124
|
+
adapter: FileSourceCapable,
|
|
125
|
+
messages: ReadonlyArray<unknown>,
|
|
126
|
+
): void {
|
|
127
|
+
if (adapter.supportsFileSources === true) return
|
|
128
|
+
for (const message of messages) {
|
|
129
|
+
if (typeof message !== 'object' || message === null) continue
|
|
130
|
+
const content = (message as { content?: unknown }).content
|
|
131
|
+
if (!Array.isArray(content)) continue
|
|
132
|
+
for (const part of content) {
|
|
133
|
+
if (partHasFileSource(part)) {
|
|
134
|
+
throw unsupportedFileSourceError(adapter.name)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -30,3 +30,16 @@ export function isProviderExecutedToolCall(
|
|
|
30
30
|
): boolean {
|
|
31
31
|
return getProviderExecutedMetadata(toolCall) !== null
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* True when `id` is a `${parentId}-segment-${n}` message. The wire and the
|
|
36
|
+
* run loop split one provider turn into segments at thinking that follows a
|
|
37
|
+
* provider-executed tool call, to keep signed thinking order. Readers fold a
|
|
38
|
+
* segment back into its parent so the UI still shows one message.
|
|
39
|
+
*/
|
|
40
|
+
export function isAssistantSegmentOf(
|
|
41
|
+
id: string | undefined,
|
|
42
|
+
parentId: string,
|
|
43
|
+
): boolean {
|
|
44
|
+
return id?.startsWith(`${parentId}-segment-`) === true
|
|
45
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentPart } from '../types'
|
|
1
|
+
import type { ContentPart, ToolResultOutcome } from '../types'
|
|
2
2
|
|
|
3
3
|
const CONTENT_PART_TYPES = new Set([
|
|
4
4
|
'text',
|
|
@@ -8,10 +8,17 @@ const CONTENT_PART_TYPES = new Set([
|
|
|
8
8
|
'document',
|
|
9
9
|
])
|
|
10
10
|
|
|
11
|
+
export function isToolResultOutcome(
|
|
12
|
+
value: unknown,
|
|
13
|
+
): value is ToolResultOutcome {
|
|
14
|
+
return value === 'cancelled' || value === 'denied'
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
18
|
* Structural check for a single `ContentPart`. A text part must carry a string
|
|
13
|
-
* `content
|
|
14
|
-
*
|
|
19
|
+
* `content`. Every other part carries a source with a string `value`; a file
|
|
20
|
+
* source's `value` is a non-empty opaque handle, and its optional `provider`
|
|
21
|
+
* is a string.
|
|
15
22
|
*/
|
|
16
23
|
export function isContentPart(value: unknown): value is ContentPart {
|
|
17
24
|
if (typeof value !== 'object' || value === null) return false
|
|
@@ -26,6 +33,14 @@ export function isContentPart(value: unknown): value is ContentPart {
|
|
|
26
33
|
if (typeof source !== 'object' || source === null) return false
|
|
27
34
|
const src = source as Record<string, unknown>
|
|
28
35
|
if (typeof src.value !== 'string') return false
|
|
36
|
+
// `file` sources carry an opaque handle in `value`; `provider`, when set,
|
|
37
|
+
// names the issuer.
|
|
38
|
+
if (src.type === 'file') {
|
|
39
|
+
return (
|
|
40
|
+
src.value.length > 0 &&
|
|
41
|
+
(src.provider === undefined || typeof src.provider === 'string')
|
|
42
|
+
)
|
|
43
|
+
}
|
|
29
44
|
// `data` sources require a mimeType (matches ContentPartDataSource); `url`
|
|
30
45
|
// sources don't. Requiring it here keeps the runtime guard consistent with
|
|
31
46
|
// the type and avoids emitting `data:undefined;base64,...` downstream.
|
|
@@ -45,6 +60,33 @@ export function isContentPartArray(
|
|
|
45
60
|
return Array.isArray(value) && value.length > 0 && value.every(isContentPart)
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Error text for a failed tool result: `output.error` when it is a string,
|
|
65
|
+
* else the output itself when it is a string, else a generic message.
|
|
66
|
+
* `StreamProcessor` and `chat()` history share it, so a reload shows the
|
|
67
|
+
* same text as the live stream.
|
|
68
|
+
*/
|
|
69
|
+
export function toolResultErrorText(output: unknown): string {
|
|
70
|
+
if (
|
|
71
|
+
output &&
|
|
72
|
+
typeof output === 'object' &&
|
|
73
|
+
'error' in output &&
|
|
74
|
+
typeof output.error === 'string'
|
|
75
|
+
) {
|
|
76
|
+
return output.error
|
|
77
|
+
}
|
|
78
|
+
return typeof output === 'string' ? output : 'Tool execution failed'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Parse tool result content as JSON. Plain text stays a string. */
|
|
82
|
+
export function parseToolOutput(content: string): unknown {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(content)
|
|
85
|
+
} catch {
|
|
86
|
+
return content
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
48
90
|
/**
|
|
49
91
|
* Normalize a tool's return value for transport:
|
|
50
92
|
* - string → unchanged
|