@tanstack/ai 0.61.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/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 +156 -57
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +30 -18
- 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 +2 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +11 -9
- package/dist/esm/activities/chat/stream/processor.js +38 -18
- 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 +55 -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/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/client.d.ts +1 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- 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 +42 -1
- package/dist/esm/utilities/ag-ui-wire.js +5 -3
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/tool-result.d.ts +2 -1
- package/dist/esm/utilities/tool-result.js +4 -1
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/chat-experience/SKILL.md +120 -0
- package/skills/ai-core/tool-calling/SKILL.md +103 -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 +235 -62
- package/src/activities/chat/messages.ts +40 -5
- package/src/activities/chat/middleware/types.ts +1 -0
- package/src/activities/chat/stream/message-updaters.ts +3 -0
- package/src/activities/chat/stream/processor.ts +57 -27
- package/src/activities/chat/tools/tool-calls.ts +98 -9
- package/src/activities/chat/tools/tool-definition.ts +8 -0
- package/src/activities/evaluate/adapter.ts +4 -0
- package/src/activities/evaluate/index.ts +6 -0
- package/src/client.ts +1 -0
- package/src/index.ts +1 -0
- package/src/interrupt-resume.ts +55 -4
- package/src/middlewares/otel.ts +161 -3
- package/src/types.ts +38 -1
- package/src/utilities/ag-ui-wire.ts +12 -1
- package/src/utilities/tool-result.ts +7 -1
|
@@ -113,6 +113,7 @@ import type {
|
|
|
113
113
|
import type {
|
|
114
114
|
ApprovalRequest,
|
|
115
115
|
ClientToolRequest,
|
|
116
|
+
McpInputRequest,
|
|
116
117
|
ToolResult,
|
|
117
118
|
} from './tools/tool-calls'
|
|
118
119
|
import type { ApprovalSchemaConfig } from './tools/tool-definition'
|
|
@@ -148,6 +149,7 @@ import type {
|
|
|
148
149
|
ToolCallEndEvent,
|
|
149
150
|
ToolCallResultEvent,
|
|
150
151
|
ToolCallStartEvent,
|
|
152
|
+
ToolInputResponse,
|
|
151
153
|
UIMessage,
|
|
152
154
|
} from '../../types'
|
|
153
155
|
import type {
|
|
@@ -880,8 +882,10 @@ class TextEngine<
|
|
|
880
882
|
private readonly initialClientToolResults: Map<string, any>
|
|
881
883
|
private readonly resumeApprovals = new Map<string, ToolApprovalResolution>()
|
|
882
884
|
private readonly resumeClientToolResults = new Map<string, any>()
|
|
885
|
+
private readonly resumeClientToolErrors = new Map<string, string>()
|
|
883
886
|
private readonly resumeDeniedToolResults = new Map<string, unknown>()
|
|
884
887
|
private readonly resumeCancelledToolCallIds = new Set<string>()
|
|
888
|
+
private readonly resumeInputResponses = new Map<string, ToolInputResponse>()
|
|
885
889
|
private readonly resumeGenericInterrupts = new Map<
|
|
886
890
|
string,
|
|
887
891
|
ChatResumeGenericResolution
|
|
@@ -952,6 +956,8 @@ class TextEngine<
|
|
|
952
956
|
message: string
|
|
953
957
|
code?: string
|
|
954
958
|
cause?: unknown
|
|
959
|
+
/** Full model text, when a structured-output parse failed (#1485). */
|
|
960
|
+
rawText?: string
|
|
955
961
|
} | null = null
|
|
956
962
|
private combinedCompleteEmitted = false
|
|
957
963
|
private readonly finalStructuredOutput?: {
|
|
@@ -1189,10 +1195,54 @@ class TextEngine<
|
|
|
1189
1195
|
message: string
|
|
1190
1196
|
code?: string
|
|
1191
1197
|
cause?: unknown
|
|
1198
|
+
rawText?: string
|
|
1192
1199
|
} | null {
|
|
1193
1200
|
return this.finalizationError
|
|
1194
1201
|
}
|
|
1195
1202
|
|
|
1203
|
+
private async runTerminalHook(): Promise<void> {
|
|
1204
|
+
if (this.terminalHookCalled || this.isCancelled()) return
|
|
1205
|
+
|
|
1206
|
+
this.terminalHookCalled = true
|
|
1207
|
+
|
|
1208
|
+
if (this.finalizationError) {
|
|
1209
|
+
const errForHook = new Error(
|
|
1210
|
+
this.finalizationError.message,
|
|
1211
|
+
this.finalizationError.cause !== undefined
|
|
1212
|
+
? { cause: this.finalizationError.cause }
|
|
1213
|
+
: undefined,
|
|
1214
|
+
)
|
|
1215
|
+
if (this.finalizationError.code !== undefined) {
|
|
1216
|
+
Object.defineProperty(errForHook, 'code', {
|
|
1217
|
+
value: this.finalizationError.code,
|
|
1218
|
+
enumerable: true,
|
|
1219
|
+
})
|
|
1220
|
+
}
|
|
1221
|
+
if (this.finalizationError.rawText !== undefined) {
|
|
1222
|
+
Object.defineProperty(errForHook, 'rawText', {
|
|
1223
|
+
value: this.finalizationError.rawText,
|
|
1224
|
+
enumerable: true,
|
|
1225
|
+
})
|
|
1226
|
+
}
|
|
1227
|
+
await this.middlewareRunner.runOnError(this.middlewareCtx, {
|
|
1228
|
+
error: errForHook,
|
|
1229
|
+
duration: Date.now() - this.streamStartTime,
|
|
1230
|
+
})
|
|
1231
|
+
return
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
this.addTerminalAssistantMessages()
|
|
1235
|
+
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
1236
|
+
finishReason: this.lastFinishReason,
|
|
1237
|
+
duration: Date.now() - this.streamStartTime,
|
|
1238
|
+
content: this.accumulatedContent,
|
|
1239
|
+
usage: rebuildTokenUsage(
|
|
1240
|
+
this.finishedEvent?.usage,
|
|
1241
|
+
tanstackMetadata(this.finishedEvent ?? undefined)?.usage,
|
|
1242
|
+
),
|
|
1243
|
+
})
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1196
1246
|
async *run(): AsyncGenerator<StreamChunk> {
|
|
1197
1247
|
this.beforeRun()
|
|
1198
1248
|
this.logger.agentLoop('run started', {
|
|
@@ -1238,7 +1288,8 @@ class TextEngine<
|
|
|
1238
1288
|
}
|
|
1239
1289
|
|
|
1240
1290
|
const pendingPhase = yield* this.checkForPendingToolCalls()
|
|
1241
|
-
if (pendingPhase === 'wait') {
|
|
1291
|
+
if (pendingPhase === 'stop' || pendingPhase === 'wait') {
|
|
1292
|
+
await this.runTerminalHook()
|
|
1242
1293
|
return
|
|
1243
1294
|
}
|
|
1244
1295
|
|
|
@@ -1347,46 +1398,11 @@ class TextEngine<
|
|
|
1347
1398
|
}
|
|
1348
1399
|
}
|
|
1349
1400
|
|
|
1350
|
-
//
|
|
1351
|
-
//
|
|
1352
|
-
//
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
this.toolPhase !== 'wait' &&
|
|
1356
|
-
!this.isCancelled()
|
|
1357
|
-
) {
|
|
1358
|
-
if (this.finalizationError) {
|
|
1359
|
-
this.terminalHookCalled = true
|
|
1360
|
-
const errForHook = new Error(
|
|
1361
|
-
this.finalizationError.message,
|
|
1362
|
-
this.finalizationError.cause !== undefined
|
|
1363
|
-
? { cause: this.finalizationError.cause }
|
|
1364
|
-
: undefined,
|
|
1365
|
-
)
|
|
1366
|
-
if (this.finalizationError.code !== undefined) {
|
|
1367
|
-
Object.defineProperty(errForHook, 'code', {
|
|
1368
|
-
value: this.finalizationError.code,
|
|
1369
|
-
enumerable: true,
|
|
1370
|
-
})
|
|
1371
|
-
}
|
|
1372
|
-
await this.middlewareRunner.runOnError(this.middlewareCtx, {
|
|
1373
|
-
error: errForHook,
|
|
1374
|
-
duration: Date.now() - this.streamStartTime,
|
|
1375
|
-
})
|
|
1376
|
-
} else {
|
|
1377
|
-
this.addTerminalAssistantMessages()
|
|
1378
|
-
this.terminalHookCalled = true
|
|
1379
|
-
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
1380
|
-
finishReason: this.lastFinishReason,
|
|
1381
|
-
duration: Date.now() - this.streamStartTime,
|
|
1382
|
-
content: this.accumulatedContent,
|
|
1383
|
-
usage: rebuildTokenUsage(
|
|
1384
|
-
this.finishedEvent?.usage,
|
|
1385
|
-
tanstackMetadata(this.finishedEvent ?? undefined)?.usage,
|
|
1386
|
-
),
|
|
1387
|
-
})
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1401
|
+
// A client-tool or approval wait ends this server-side chat invocation.
|
|
1402
|
+
// Structured-output finalization stays paused until the caller submits
|
|
1403
|
+
// the result in a new invocation, but middleware must still observe one
|
|
1404
|
+
// terminal hook for the current invocation.
|
|
1405
|
+
await this.runTerminalHook()
|
|
1390
1406
|
} catch (error: unknown) {
|
|
1391
1407
|
if (
|
|
1392
1408
|
error instanceof Error &&
|
|
@@ -2181,8 +2197,10 @@ class TextEngine<
|
|
|
2181
2197
|
this.middlewareCtx.context,
|
|
2182
2198
|
this.toolAbortSignal,
|
|
2183
2199
|
{
|
|
2200
|
+
clientToolErrors: this.resumeClientToolErrors,
|
|
2184
2201
|
deniedToolResults: this.resumeDeniedToolResults,
|
|
2185
2202
|
cancelledToolCallIds: this.resumeCancelledToolCallIds,
|
|
2203
|
+
inputResponses: this.resumeInputResponses,
|
|
2186
2204
|
},
|
|
2187
2205
|
)
|
|
2188
2206
|
|
|
@@ -2207,11 +2225,12 @@ class TextEngine<
|
|
|
2207
2225
|
}),
|
|
2208
2226
|
)
|
|
2209
2227
|
|
|
2210
|
-
|
|
2228
|
+
const pauseForDecision =
|
|
2211
2229
|
executionResult.needsApproval.length > 0 ||
|
|
2212
2230
|
executionResult.needsClientExecution.length > 0 ||
|
|
2231
|
+
executionResult.inputRequired.length > 0 ||
|
|
2213
2232
|
executionResult.subagentInterrupts.length > 0
|
|
2214
|
-
) {
|
|
2233
|
+
if (pauseForDecision) {
|
|
2215
2234
|
this.discardDeferredToolCallRunFinishedChunks()
|
|
2216
2235
|
|
|
2217
2236
|
if (allResults.length > 0) {
|
|
@@ -2228,6 +2247,7 @@ class TextEngine<
|
|
|
2228
2247
|
executionResult.needsApproval,
|
|
2229
2248
|
executionResult.needsClientExecution,
|
|
2230
2249
|
[],
|
|
2250
|
+
executionResult.inputRequired,
|
|
2231
2251
|
executionResult.subagentInterrupts,
|
|
2232
2252
|
)
|
|
2233
2253
|
this.setToolPhase(emitted ? 'wait' : 'stop')
|
|
@@ -2365,8 +2385,10 @@ class TextEngine<
|
|
|
2365
2385
|
this.middlewareCtx.context,
|
|
2366
2386
|
this.toolAbortSignal,
|
|
2367
2387
|
{
|
|
2388
|
+
clientToolErrors: this.resumeClientToolErrors,
|
|
2368
2389
|
deniedToolResults: this.resumeDeniedToolResults,
|
|
2369
2390
|
cancelledToolCallIds: this.resumeCancelledToolCallIds,
|
|
2391
|
+
inputResponses: this.resumeInputResponses,
|
|
2370
2392
|
},
|
|
2371
2393
|
)
|
|
2372
2394
|
|
|
@@ -2414,16 +2436,18 @@ class TextEngine<
|
|
|
2414
2436
|
finishEvent,
|
|
2415
2437
|
toolCalls,
|
|
2416
2438
|
afterToolRequests,
|
|
2439
|
+
executionResult.inputRequired,
|
|
2417
2440
|
)
|
|
2418
2441
|
this.setToolPhase('wait')
|
|
2419
2442
|
return
|
|
2420
2443
|
}
|
|
2421
2444
|
|
|
2422
|
-
|
|
2445
|
+
const pauseForDecision =
|
|
2423
2446
|
executionResult.needsApproval.length > 0 ||
|
|
2424
2447
|
executionResult.needsClientExecution.length > 0 ||
|
|
2448
|
+
executionResult.inputRequired.length > 0 ||
|
|
2425
2449
|
executionResult.subagentInterrupts.length > 0
|
|
2426
|
-
) {
|
|
2450
|
+
if (pauseForDecision) {
|
|
2427
2451
|
if (allResults.length > 0) {
|
|
2428
2452
|
for (const chunk of afterToolBoundaryChunks) {
|
|
2429
2453
|
yield* this.pipeThroughMiddleware(chunk)
|
|
@@ -2435,6 +2459,7 @@ class TextEngine<
|
|
|
2435
2459
|
executionResult.needsApproval,
|
|
2436
2460
|
executionResult.needsClientExecution,
|
|
2437
2461
|
[],
|
|
2462
|
+
executionResult.inputRequired,
|
|
2438
2463
|
executionResult.subagentInterrupts,
|
|
2439
2464
|
)
|
|
2440
2465
|
this.setToolPhase(emitted ? 'wait' : 'stop')
|
|
@@ -2853,6 +2878,7 @@ class TextEngine<
|
|
|
2853
2878
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2854
2879
|
> = [],
|
|
2855
2880
|
genericInterruptIds: ReadonlyArray<string> = [],
|
|
2881
|
+
inputRequired: ReadonlyArray<McpInputRequest> = [],
|
|
2856
2882
|
childInterrupts: ReadonlyArray<Interrupt> = [],
|
|
2857
2883
|
): Array<Interrupt> {
|
|
2858
2884
|
const interrupts: Array<Interrupt> = []
|
|
@@ -2971,6 +2997,30 @@ class TextEngine<
|
|
|
2971
2997
|
})
|
|
2972
2998
|
}
|
|
2973
2999
|
|
|
3000
|
+
for (const pendingInput of inputRequired) {
|
|
3001
|
+
const id = `mcp_input_${pendingInput.toolCallId}`
|
|
3002
|
+
interrupts.push({
|
|
3003
|
+
id,
|
|
3004
|
+
reason: 'mcp_input',
|
|
3005
|
+
message: `Input required to run ${pendingInput.toolName}`,
|
|
3006
|
+
toolCallId: pendingInput.toolCallId,
|
|
3007
|
+
metadata: {
|
|
3008
|
+
toolName: pendingInput.toolName,
|
|
3009
|
+
// A generic binding with no definition id. `useChat` shows it as a
|
|
3010
|
+
// generic interrupt with `resolveInterrupt` and `cancel`. The answer
|
|
3011
|
+
// is not validated here. The MCP server checks it.
|
|
3012
|
+
[interruptBindingMetadataKey]: {
|
|
3013
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
3014
|
+
kind: 'generic',
|
|
3015
|
+
interruptId: id,
|
|
3016
|
+
},
|
|
3017
|
+
[INTERRUPT_PAYLOAD_METADATA_KEY]: {
|
|
3018
|
+
kind: pendingInput.kind,
|
|
3019
|
+
request: pendingInput.request,
|
|
3020
|
+
},
|
|
3021
|
+
},
|
|
3022
|
+
})
|
|
3023
|
+
}
|
|
2974
3024
|
// A subagent tool call raised these. They keep their `subagentRunId`.
|
|
2975
3025
|
interrupts.push(...childInterrupts)
|
|
2976
3026
|
|
|
@@ -2995,6 +3045,7 @@ class TextEngine<
|
|
|
2995
3045
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2996
3046
|
> = [],
|
|
2997
3047
|
genericInterruptIds?: ReadonlyArray<string>,
|
|
3048
|
+
inputRequired: ReadonlyArray<McpInputRequest> = [],
|
|
2998
3049
|
childInterrupts?: ReadonlyArray<Interrupt>,
|
|
2999
3050
|
): StreamChunk {
|
|
3000
3051
|
return {
|
|
@@ -3007,6 +3058,7 @@ class TextEngine<
|
|
|
3007
3058
|
clientRequests,
|
|
3008
3059
|
genericRequests,
|
|
3009
3060
|
genericInterruptIds,
|
|
3061
|
+
inputRequired,
|
|
3010
3062
|
childInterrupts,
|
|
3011
3063
|
),
|
|
3012
3064
|
},
|
|
@@ -3157,6 +3209,7 @@ class TextEngine<
|
|
|
3157
3209
|
genericRequests: ReadonlyArray<
|
|
3158
3210
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
3159
3211
|
> = [],
|
|
3212
|
+
inputRequired: ReadonlyArray<McpInputRequest> = [],
|
|
3160
3213
|
childInterrupts: ReadonlyArray<Interrupt> = [],
|
|
3161
3214
|
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
3162
3215
|
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
@@ -3171,6 +3224,7 @@ class TextEngine<
|
|
|
3171
3224
|
clientRequests,
|
|
3172
3225
|
genericRequests,
|
|
3173
3226
|
genericInterruptIds,
|
|
3227
|
+
inputRequired,
|
|
3174
3228
|
childInterrupts,
|
|
3175
3229
|
),
|
|
3176
3230
|
)
|
|
@@ -3207,6 +3261,7 @@ class TextEngine<
|
|
|
3207
3261
|
requests?: ReadonlyArray<
|
|
3208
3262
|
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
3209
3263
|
>,
|
|
3264
|
+
inputRequired: ReadonlyArray<McpInputRequest> = [],
|
|
3210
3265
|
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
3211
3266
|
this.middlewareCtx.phase = phase
|
|
3212
3267
|
const boundaryRequests =
|
|
@@ -3242,6 +3297,7 @@ class TextEngine<
|
|
|
3242
3297
|
actionable.approvals,
|
|
3243
3298
|
actionable.clientRequests,
|
|
3244
3299
|
boundaryRequests,
|
|
3300
|
+
inputRequired,
|
|
3245
3301
|
)
|
|
3246
3302
|
return true
|
|
3247
3303
|
}
|
|
@@ -3289,6 +3345,7 @@ class TextEngine<
|
|
|
3289
3345
|
} else if (
|
|
3290
3346
|
!tool.execute &&
|
|
3291
3347
|
!clientToolResults.has(toolCall.id) &&
|
|
3348
|
+
!this.resumeClientToolErrors.has(toolCall.id) &&
|
|
3292
3349
|
!this.resumeCancelledToolCallIds.has(toolCall.id)
|
|
3293
3350
|
) {
|
|
3294
3351
|
clientRequests.push({
|
|
@@ -3396,9 +3453,15 @@ class TextEngine<
|
|
|
3396
3453
|
content: wireContent,
|
|
3397
3454
|
role: 'tool' as const,
|
|
3398
3455
|
}
|
|
3456
|
+
// An outcome always comes with output-error.
|
|
3399
3457
|
chunks.push(
|
|
3400
3458
|
(result.state === 'output-error'
|
|
3401
|
-
? withTanstackMetadata(resultChunk, {
|
|
3459
|
+
? withTanstackMetadata(resultChunk, {
|
|
3460
|
+
state: result.state,
|
|
3461
|
+
...(result.outcome !== undefined && {
|
|
3462
|
+
toolResultOutcome: result.outcome,
|
|
3463
|
+
}),
|
|
3464
|
+
})
|
|
3402
3465
|
: resultChunk) as StreamChunk,
|
|
3403
3466
|
)
|
|
3404
3467
|
|
|
@@ -3419,22 +3482,63 @@ class TextEngine<
|
|
|
3419
3482
|
return false
|
|
3420
3483
|
}
|
|
3421
3484
|
})
|
|
3422
|
-
|
|
3423
|
-
const
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3485
|
+
const isDeniedResult = result.outcome === 'denied'
|
|
3486
|
+
const existingToolResultIdx = isDeniedResult
|
|
3487
|
+
? this.messages.findIndex(
|
|
3488
|
+
(message) =>
|
|
3489
|
+
message.role === 'tool' &&
|
|
3490
|
+
message.toolCallId === result.toolCallId,
|
|
3491
|
+
)
|
|
3492
|
+
: -1
|
|
3493
|
+
const resultMessageIdx =
|
|
3494
|
+
existingToolResultIdx >= 0 ? existingToolResultIdx : placeholderIdx
|
|
3495
|
+
|
|
3496
|
+
// Only a denied result keeps the old message's fields. A replaced
|
|
3497
|
+
// pendingExecution placeholder must not leak into the real result.
|
|
3498
|
+
const existingToolMessage =
|
|
3499
|
+
existingToolResultIdx >= 0
|
|
3500
|
+
? this.messages[existingToolResultIdx]
|
|
3501
|
+
: undefined
|
|
3502
|
+
const errorField = result.state === 'output-error' && {
|
|
3503
|
+
error: toolResultErrorText(parseToolOutput(wireContent)),
|
|
3430
3504
|
}
|
|
3505
|
+
const replacementMessage: ModelMessage =
|
|
3506
|
+
existingToolMessage?.role === 'tool'
|
|
3507
|
+
? {
|
|
3508
|
+
...existingToolMessage,
|
|
3509
|
+
content,
|
|
3510
|
+
toolCallId: result.toolCallId,
|
|
3511
|
+
...errorField,
|
|
3512
|
+
}
|
|
3513
|
+
: {
|
|
3514
|
+
role: 'tool',
|
|
3515
|
+
content,
|
|
3516
|
+
toolCallId: result.toolCallId,
|
|
3517
|
+
...errorField,
|
|
3518
|
+
}
|
|
3519
|
+
const newToolMessage: ModelMessage =
|
|
3520
|
+
result.outcome !== undefined
|
|
3521
|
+
? withTanstackMetadata(replacementMessage, {
|
|
3522
|
+
toolResultOutcome: result.outcome,
|
|
3523
|
+
})
|
|
3524
|
+
: replacementMessage
|
|
3431
3525
|
|
|
3432
|
-
if (
|
|
3433
|
-
|
|
3434
|
-
...this.messages.slice(0,
|
|
3526
|
+
if (resultMessageIdx >= 0) {
|
|
3527
|
+
const replacedMessages = [
|
|
3528
|
+
...this.messages.slice(0, resultMessageIdx),
|
|
3435
3529
|
newToolMessage,
|
|
3436
|
-
...this.messages.slice(
|
|
3530
|
+
...this.messages.slice(resultMessageIdx + 1),
|
|
3437
3531
|
]
|
|
3532
|
+
this.messages = isDeniedResult
|
|
3533
|
+
? replacedMessages.filter(
|
|
3534
|
+
(message, index) =>
|
|
3535
|
+
index === resultMessageIdx ||
|
|
3536
|
+
!(
|
|
3537
|
+
message.role === 'tool' &&
|
|
3538
|
+
message.toolCallId === result.toolCallId
|
|
3539
|
+
),
|
|
3540
|
+
)
|
|
3541
|
+
: replacedMessages
|
|
3438
3542
|
} else {
|
|
3439
3543
|
this.messages = [...this.messages, newToolMessage]
|
|
3440
3544
|
}
|
|
@@ -3465,7 +3569,10 @@ class TextEngine<
|
|
|
3465
3569
|
}
|
|
3466
3570
|
|
|
3467
3571
|
// Only mark as complete if NOT pending execution
|
|
3468
|
-
if (
|
|
3572
|
+
if (
|
|
3573
|
+
!hasPendingExecution &&
|
|
3574
|
+
!this.resumeDeniedToolResults.has(message.toolCallId)
|
|
3575
|
+
) {
|
|
3469
3576
|
completedToolIds.add(message.toolCallId)
|
|
3470
3577
|
}
|
|
3471
3578
|
}
|
|
@@ -3853,6 +3960,9 @@ class TextEngine<
|
|
|
3853
3960
|
// Track whether a RUN_ERROR has been yielded to streaming consumers so
|
|
3854
3961
|
// we don't emit a duplicate synthetic one at the end.
|
|
3855
3962
|
let runErrorYielded = false
|
|
3963
|
+
// The full model text. Adapter errors carry only a 200-character preview,
|
|
3964
|
+
// so a Promise caller could not recover the answer (#1485).
|
|
3965
|
+
let rawText = ''
|
|
3856
3966
|
|
|
3857
3967
|
// Pipe chunks through middleware; yield to consumer only when yieldChunks=true
|
|
3858
3968
|
for await (const raw of providerStream) {
|
|
@@ -3964,14 +4074,20 @@ class TextEngine<
|
|
|
3964
4074
|
await this.runOnUsageFromChunk(chunk)
|
|
3965
4075
|
}
|
|
3966
4076
|
|
|
4077
|
+
if (chunk.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
4078
|
+
rawText += chunk.delta
|
|
4079
|
+
}
|
|
4080
|
+
|
|
3967
4081
|
if (chunk.type === EventType.RUN_ERROR) {
|
|
3968
4082
|
// RunErrorEvent already exposes `message` and `code` after narrowing.
|
|
4083
|
+
// A native stream has no adapter error to capture, but its RUN_ERROR
|
|
4084
|
+
// can carry the provider's error body as `rawEvent` (#1005).
|
|
4085
|
+
const cause = fallbackAdapterError ?? chunk.rawEvent
|
|
3969
4086
|
this.finalizationError = {
|
|
3970
4087
|
message: chunk.message,
|
|
3971
4088
|
...(chunk.code ? { code: chunk.code } : {}),
|
|
3972
|
-
...(
|
|
3973
|
-
|
|
3974
|
-
: {}),
|
|
4089
|
+
...(cause !== undefined ? { cause } : {}),
|
|
4090
|
+
...(rawText ? { rawText } : {}),
|
|
3975
4091
|
}
|
|
3976
4092
|
}
|
|
3977
4093
|
|
|
@@ -4155,6 +4271,7 @@ class TextEngine<
|
|
|
4155
4271
|
message: `Failed to parse structured output as JSON. Content: ${detail}`,
|
|
4156
4272
|
code: 'structured-output-parse-failed',
|
|
4157
4273
|
cause: err,
|
|
4274
|
+
rawText,
|
|
4158
4275
|
}
|
|
4159
4276
|
}
|
|
4160
4277
|
}
|
|
@@ -4265,6 +4382,7 @@ class TextEngine<
|
|
|
4265
4382
|
resumeToolState: {
|
|
4266
4383
|
approvals: this.resumeApprovals,
|
|
4267
4384
|
clientToolResults: this.resumeClientToolResults,
|
|
4385
|
+
clientToolErrors: this.resumeClientToolErrors,
|
|
4268
4386
|
deniedToolResults: this.resumeDeniedToolResults,
|
|
4269
4387
|
cancelledToolCallIds: this.resumeCancelledToolCallIds,
|
|
4270
4388
|
},
|
|
@@ -4407,6 +4525,26 @@ class TextEngine<
|
|
|
4407
4525
|
: []
|
|
4408
4526
|
})
|
|
4409
4527
|
pending.push(...genericPending)
|
|
4528
|
+
// An `mcp_input_*` answer is valid only for a server tool call that is
|
|
4529
|
+
// still pending in history.
|
|
4530
|
+
const mcpInputCallIds = new Map<string, string>()
|
|
4531
|
+
for (const toolCall of pendingToolCalls) {
|
|
4532
|
+
const interruptId = `mcp_input_${toolCall.id}`
|
|
4533
|
+
if (!resumeInterruptIds.has(interruptId)) continue
|
|
4534
|
+
if (!toolsByCallId.get(toolCall.id)?.execute) continue
|
|
4535
|
+
mcpInputCallIds.set(interruptId, toolCall.id)
|
|
4536
|
+
pending.push({
|
|
4537
|
+
interruptId,
|
|
4538
|
+
payload: { id: interruptId },
|
|
4539
|
+
binding: {
|
|
4540
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
4541
|
+
kind: 'generic',
|
|
4542
|
+
interruptId,
|
|
4543
|
+
interruptedRunId,
|
|
4544
|
+
generation: 0,
|
|
4545
|
+
},
|
|
4546
|
+
})
|
|
4547
|
+
}
|
|
4410
4548
|
const validated = await validateInterruptResumeBatch({
|
|
4411
4549
|
threadId: this.threadId,
|
|
4412
4550
|
interruptedRunId,
|
|
@@ -4436,6 +4574,16 @@ class TextEngine<
|
|
|
4436
4574
|
})
|
|
4437
4575
|
|
|
4438
4576
|
const genericResolutions = validated.resumeToolState.genericInterrupts
|
|
4577
|
+
for (const [interruptId, toolCallId] of mcpInputCallIds) {
|
|
4578
|
+
const resolution = genericResolutions?.get(interruptId)
|
|
4579
|
+
if (!resolution) continue
|
|
4580
|
+
this.resumeInputResponses.set(
|
|
4581
|
+
toolCallId,
|
|
4582
|
+
resolution.status === 'resolved'
|
|
4583
|
+
? { status: 'resolved', payload: resolution.payload }
|
|
4584
|
+
: { status: 'cancelled' },
|
|
4585
|
+
)
|
|
4586
|
+
}
|
|
4439
4587
|
if (genericPending.length > 0 && genericResolutions) {
|
|
4440
4588
|
const resolutions = genericPending
|
|
4441
4589
|
.sort((left, right) => {
|
|
@@ -4485,6 +4633,7 @@ class TextEngine<
|
|
|
4485
4633
|
this.earlyTermination = true
|
|
4486
4634
|
} else if (policy.toolResume === 'cancel') {
|
|
4487
4635
|
for (const request of pendingToolCalls) {
|
|
4636
|
+
if (this.resumeDeniedToolResults.has(request.id)) continue
|
|
4488
4637
|
this.resumeCancelledToolCallIds.add(request.id)
|
|
4489
4638
|
}
|
|
4490
4639
|
}
|
|
@@ -4626,6 +4775,11 @@ class TextEngine<
|
|
|
4626
4775
|
this.resumeClientToolResults.set(toolCallId, result)
|
|
4627
4776
|
}
|
|
4628
4777
|
}
|
|
4778
|
+
if (state?.clientToolErrors) {
|
|
4779
|
+
for (const [toolCallId, errorText] of state.clientToolErrors) {
|
|
4780
|
+
this.resumeClientToolErrors.set(toolCallId, errorText)
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4629
4783
|
if (state?.deniedToolResults) {
|
|
4630
4784
|
for (const [toolCallId, result] of state.deniedToolResults) {
|
|
4631
4785
|
this.resumeDeniedToolResults.set(toolCallId, result)
|
|
@@ -4685,6 +4839,7 @@ class TextEngine<
|
|
|
4685
4839
|
this.earlyTermination = true
|
|
4686
4840
|
} else if (policy.toolResume === 'cancel') {
|
|
4687
4841
|
for (const toolCall of this.getPendingToolCallsFromMessages()) {
|
|
4842
|
+
if (this.resumeDeniedToolResults.has(toolCall.id)) continue
|
|
4688
4843
|
this.resumeCancelledToolCallIds.add(toolCall.id)
|
|
4689
4844
|
}
|
|
4690
4845
|
}
|
|
@@ -4839,6 +4994,7 @@ class TextEngine<
|
|
|
4839
4994
|
results: Array<ToolResult>
|
|
4840
4995
|
needsApproval: Array<ApprovalRequest>
|
|
4841
4996
|
needsClientExecution: Array<ClientToolRequest>
|
|
4997
|
+
inputRequired: Array<McpInputRequest>
|
|
4842
4998
|
subagentInterrupts: Array<Interrupt>
|
|
4843
4999
|
},
|
|
4844
5000
|
void
|
|
@@ -4849,6 +5005,7 @@ class TextEngine<
|
|
|
4849
5005
|
results: Array<ToolResult>
|
|
4850
5006
|
needsApproval: Array<ApprovalRequest>
|
|
4851
5007
|
needsClientExecution: Array<ClientToolRequest>
|
|
5008
|
+
inputRequired: Array<McpInputRequest>
|
|
4852
5009
|
subagentInterrupts: Array<Interrupt>
|
|
4853
5010
|
},
|
|
4854
5011
|
void
|
|
@@ -5287,6 +5444,16 @@ async function* runRoutedSubagents(
|
|
|
5287
5444
|
yield* runChatEngine(options, engineRef)
|
|
5288
5445
|
return
|
|
5289
5446
|
}
|
|
5447
|
+
// ponytail: a router cannot write input yet. Add it to the router pick if
|
|
5448
|
+
// someone needs it.
|
|
5449
|
+
const withInput = bag.agents.find(
|
|
5450
|
+
(agent: DefinedAgent) => agent.inputSchema !== undefined,
|
|
5451
|
+
)
|
|
5452
|
+
if (withInput) {
|
|
5453
|
+
throw new Error(
|
|
5454
|
+
`Subagent "${withInput.name}" has an inputSchema. inputSchema needs tool mode: remove subagents.router so the model writes the input.`,
|
|
5455
|
+
)
|
|
5456
|
+
}
|
|
5290
5457
|
const threadId = options.threadId ?? `thread-${Date.now()}`
|
|
5291
5458
|
const runId = options.runId ?? `run-${Date.now()}`
|
|
5292
5459
|
const messages = options.messages ?? []
|
|
@@ -5742,6 +5909,12 @@ async function runAgenticStructuredOutput<TSchema extends SchemaInput>(
|
|
|
5742
5909
|
enumerable: true,
|
|
5743
5910
|
})
|
|
5744
5911
|
}
|
|
5912
|
+
if (finalizationError.rawText !== undefined) {
|
|
5913
|
+
Object.defineProperty(err, 'rawText', {
|
|
5914
|
+
value: finalizationError.rawText,
|
|
5915
|
+
enumerable: true,
|
|
5916
|
+
})
|
|
5917
|
+
}
|
|
5745
5918
|
throw err
|
|
5746
5919
|
}
|
|
5747
5920
|
|
|
@@ -4,9 +4,13 @@ import {
|
|
|
4
4
|
} from '../../utilities/provider-executed'
|
|
5
5
|
import {
|
|
6
6
|
isContentPartArray,
|
|
7
|
+
isToolResultOutcome,
|
|
7
8
|
normalizeToolResult,
|
|
8
9
|
} from '../../utilities/tool-result'
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
tanstackMetadata,
|
|
12
|
+
withTanstackMetadata,
|
|
13
|
+
} from '../../utilities/merge-metadata'
|
|
10
14
|
import {
|
|
11
15
|
splitSubagentWire,
|
|
12
16
|
subagentWireText,
|
|
@@ -29,6 +33,7 @@ import type {
|
|
|
29
33
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
34
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
31
35
|
}
|
|
36
|
+
|
|
32
37
|
// ===========================
|
|
33
38
|
// Message Converters
|
|
34
39
|
// ===========================
|
|
@@ -546,6 +551,7 @@ function assistantMetadata(
|
|
|
546
551
|
const tanstack: TanStackMessageMetadata = {}
|
|
547
552
|
if (previous?.model !== undefined) tanstack.model = previous.model
|
|
548
553
|
if (previous?.runId !== undefined) tanstack.runId = previous.runId
|
|
554
|
+
if (previous?.run?.id !== undefined) tanstack.run = { id: previous.run.id }
|
|
549
555
|
if (previous?.signature !== undefined) tanstack.signature = previous.signature
|
|
550
556
|
if (fromParts.length > 0) tanstack.uiResources = fromParts
|
|
551
557
|
const result = { ...current }
|
|
@@ -729,6 +735,11 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
729
735
|
(part.state === 'complete' || part.state === 'error') &&
|
|
730
736
|
!emittedToolResultIds.has(part.toolCallId)
|
|
731
737
|
) {
|
|
738
|
+
const metadata =
|
|
739
|
+
part.outcome === undefined
|
|
740
|
+
? part.metadata
|
|
741
|
+
: withTanstackMetadata(part, { toolResultOutcome: part.outcome })
|
|
742
|
+
.metadata
|
|
732
743
|
messageList.push({
|
|
733
744
|
...(part.id !== undefined && { id: part.id }),
|
|
734
745
|
...optionalCreatedAt(part),
|
|
@@ -736,7 +747,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
736
747
|
content: part.content,
|
|
737
748
|
toolCallId: part.toolCallId,
|
|
738
749
|
...(part.name !== undefined && { name: part.name }),
|
|
739
|
-
...(
|
|
750
|
+
...(metadata !== undefined && { metadata }),
|
|
740
751
|
...(part.error !== undefined && { error: part.error }),
|
|
741
752
|
})
|
|
742
753
|
emittedToolResultIds.add(part.toolCallId)
|
|
@@ -841,6 +852,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
841
852
|
: 'User denied this action',
|
|
842
853
|
}),
|
|
843
854
|
toolCallId: part.id,
|
|
855
|
+
...(approved === false && {
|
|
856
|
+
metadata: { tanstack: { toolResultOutcome: 'denied' } },
|
|
857
|
+
}),
|
|
844
858
|
})
|
|
845
859
|
emittedToolResultIds.add(part.id)
|
|
846
860
|
}
|
|
@@ -908,11 +922,21 @@ export function modelMessageToUIMessage(
|
|
|
908
922
|
}
|
|
909
923
|
parts.push(structuredOutput)
|
|
910
924
|
} else if (modelMessage.role === 'tool' && modelMessage.toolCallId) {
|
|
925
|
+
const rawToolResultOutcome =
|
|
926
|
+
tanstackMetadata(modelMessage)?.toolResultOutcome
|
|
927
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
928
|
+
? rawToolResultOutcome
|
|
929
|
+
: undefined
|
|
930
|
+
const resultState =
|
|
931
|
+
modelMessage.error === undefined && toolResultOutcome === undefined
|
|
932
|
+
? 'complete'
|
|
933
|
+
: 'error'
|
|
911
934
|
parts.push({
|
|
912
935
|
type: 'tool-result',
|
|
913
936
|
toolCallId: modelMessage.toolCallId,
|
|
914
937
|
content: toolResultContent(modelMessage.content),
|
|
915
|
-
state:
|
|
938
|
+
state: resultState,
|
|
939
|
+
...(toolResultOutcome !== undefined && { outcome: toolResultOutcome }),
|
|
916
940
|
...(modelMessage.id !== undefined && { id: modelMessage.id }),
|
|
917
941
|
...(modelMessage.name !== undefined && { name: modelMessage.name }),
|
|
918
942
|
...(modelMessage.metadata !== undefined && {
|
|
@@ -1228,6 +1252,14 @@ export function modelMessagesToUIMessages(
|
|
|
1228
1252
|
currentAssistantMessage.role === 'assistant'
|
|
1229
1253
|
) {
|
|
1230
1254
|
const content = toolResultContent(msg.content)
|
|
1255
|
+
const rawToolResultOutcome = tanstackMetadata(msg)?.toolResultOutcome
|
|
1256
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
1257
|
+
? rawToolResultOutcome
|
|
1258
|
+
: undefined
|
|
1259
|
+
const resultState =
|
|
1260
|
+
msg.error === undefined && toolResultOutcome === undefined
|
|
1261
|
+
? 'complete'
|
|
1262
|
+
: 'error'
|
|
1231
1263
|
const toolCallPart = currentAssistantMessage.parts.find(
|
|
1232
1264
|
(part): part is ToolCallPart =>
|
|
1233
1265
|
part.type === 'tool-call' && part.id === msg.toolCallId,
|
|
@@ -1238,14 +1270,17 @@ export function modelMessagesToUIMessages(
|
|
|
1238
1270
|
typeof content === 'string'
|
|
1239
1271
|
? parseToolResultContent(content)
|
|
1240
1272
|
: content
|
|
1241
|
-
toolCallPart.state =
|
|
1273
|
+
toolCallPart.state = resultState
|
|
1242
1274
|
}
|
|
1243
1275
|
|
|
1244
1276
|
currentAssistantMessage.parts.push({
|
|
1245
1277
|
type: 'tool-result',
|
|
1246
1278
|
toolCallId: msg.toolCallId,
|
|
1247
1279
|
content,
|
|
1248
|
-
state:
|
|
1280
|
+
state: resultState,
|
|
1281
|
+
...(toolResultOutcome !== undefined && {
|
|
1282
|
+
outcome: toolResultOutcome,
|
|
1283
|
+
}),
|
|
1249
1284
|
...(msg.id !== undefined &&
|
|
1250
1285
|
msg.id !== currentAssistantMessage.id && { id: msg.id }),
|
|
1251
1286
|
...(msg.name !== undefined && { name: msg.name }),
|