@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
|
@@ -344,6 +344,7 @@ export interface ChatMiddlewareConfig {
|
|
|
344
344
|
export interface ChatResumeToolState {
|
|
345
345
|
approvals?: ReadonlyMap<string, ToolApprovalResolution> | undefined
|
|
346
346
|
clientToolResults?: ReadonlyMap<string, unknown> | undefined
|
|
347
|
+
clientToolErrors?: ReadonlyMap<string, string> | undefined
|
|
347
348
|
genericInterrupts?:
|
|
348
349
|
| ReadonlyMap<string, ChatResumeGenericResolution>
|
|
349
350
|
| undefined
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
StructuredOutputPart,
|
|
12
12
|
ThinkingPart,
|
|
13
13
|
ToolCallPart,
|
|
14
|
+
ToolResultOutcome,
|
|
14
15
|
ToolResultPart,
|
|
15
16
|
UIMessage,
|
|
16
17
|
} from '../../../types'
|
|
@@ -117,6 +118,7 @@ export function updateToolResultPart(
|
|
|
117
118
|
content: string | Array<ContentPart>,
|
|
118
119
|
state: ToolResultState,
|
|
119
120
|
error?: string,
|
|
121
|
+
outcome?: ToolResultOutcome,
|
|
120
122
|
): Array<UIMessage> {
|
|
121
123
|
return messages.map((msg) => {
|
|
122
124
|
if (msg.id !== messageId) {
|
|
@@ -134,6 +136,7 @@ export function updateToolResultPart(
|
|
|
134
136
|
toolCallId,
|
|
135
137
|
content,
|
|
136
138
|
state,
|
|
139
|
+
...(outcome !== undefined && { outcome }),
|
|
137
140
|
...(error && { error }),
|
|
138
141
|
}
|
|
139
142
|
|
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
import { getChunkRunId } from '../../../utilities/chunk-ids'
|
|
37
37
|
import type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'
|
|
38
38
|
import {
|
|
39
|
+
isContentPartArray,
|
|
40
|
+
isToolResultOutcome,
|
|
39
41
|
normalizeToolResult,
|
|
40
42
|
toolResultErrorText,
|
|
41
43
|
} from '../../../utilities/tool-result'
|
|
@@ -181,6 +183,23 @@ function interruptBatchHasGeneric(interrupts: Array<Interrupt>): boolean {
|
|
|
181
183
|
})
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
/**
|
|
187
|
+
* The canonical arguments string for a `TOOL_CALL_END.input`, or `undefined`
|
|
188
|
+
* when JSON cannot carry it: `JSON.stringify` returns `undefined` (despite
|
|
189
|
+
* its declared type) for a top-level function or symbol and throws on BigInt
|
|
190
|
+
* and circular references. `null` is not tool arguments either, so it also
|
|
191
|
+
* keeps the streamed value instead of writing `arguments = "null"`.
|
|
192
|
+
*/
|
|
193
|
+
function serializeToolInput(input: unknown): string | undefined {
|
|
194
|
+
if (input === undefined || input === null) return undefined
|
|
195
|
+
try {
|
|
196
|
+
const serialized: unknown = JSON.stringify(input)
|
|
197
|
+
return typeof serialized === 'string' ? serialized : undefined
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
/**
|
|
185
204
|
* StreamProcessor - State machine for processing AI response streams
|
|
186
205
|
*
|
|
@@ -1953,11 +1972,13 @@ export class StreamProcessor {
|
|
|
1953
1972
|
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
1954
1973
|
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
1955
1974
|
*
|
|
1956
|
-
* If TOOL_CALL_END carries parsed `input`,
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1959
|
-
* server_tool_use / web_search — issue #839) and
|
|
1960
|
-
*
|
|
1975
|
+
* If TOOL_CALL_END carries parsed `input`, it is the canonical arguments:
|
|
1976
|
+
* write it into the accumulated string and override the rendered part's
|
|
1977
|
+
* `input` with it. That covers adapters that deliver the whole input on END
|
|
1978
|
+
* (e.g. Anthropic server_tool_use / web_search — issue #839) and adapters
|
|
1979
|
+
* that stream the wire arguments and then normalize them (OpenAI strict-mode
|
|
1980
|
+
* null widening, undone by the adapter after #939), so `arguments` and
|
|
1981
|
+
* `input` never disagree on the persisted part.
|
|
1961
1982
|
*
|
|
1962
1983
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
1963
1984
|
*/
|
|
@@ -1980,16 +2001,15 @@ export class StreamProcessor {
|
|
|
1980
2001
|
// Transition the tool call to input-complete (the authoritative completion signal)
|
|
1981
2002
|
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)
|
|
1982
2003
|
if (existingToolCall && existingToolCall.state !== 'input-complete') {
|
|
1983
|
-
//
|
|
1984
|
-
//
|
|
1985
|
-
//
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
}
|
|
2004
|
+
// The parsed input replaces the accumulated arguments string, so
|
|
2005
|
+
// completeToolCall's strict parse surfaces the canonical value on the
|
|
2006
|
+
// ToolCallPart even when the streamed deltas carried a provider-side
|
|
2007
|
+
// reshaping of it. An input JSON cannot carry is not canonical: both
|
|
2008
|
+
// `arguments` and `input` then stay with the streamed value, so the
|
|
2009
|
+
// two never disagree.
|
|
2010
|
+
const serializedInput = serializeToolInput(input)
|
|
2011
|
+
if (serializedInput !== undefined) {
|
|
2012
|
+
existingToolCall.arguments = serializedInput
|
|
1993
2013
|
}
|
|
1994
2014
|
|
|
1995
2015
|
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)
|
|
@@ -1998,7 +2018,7 @@ export class StreamProcessor {
|
|
|
1998
2018
|
// Canonicalize on the parsed input: overrides the accumulated-args parse
|
|
1999
2019
|
// that completeToolCall wrote (adapters may coerce values differently
|
|
2000
2020
|
// between streamed args and the final structured input).
|
|
2001
|
-
if (
|
|
2021
|
+
if (serializedInput !== undefined) {
|
|
2002
2022
|
existingToolCall.parsedArguments = input
|
|
2003
2023
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
2004
2024
|
id: existingToolCall.id,
|
|
@@ -2042,9 +2062,14 @@ export class StreamProcessor {
|
|
|
2042
2062
|
if (!messageId) return
|
|
2043
2063
|
|
|
2044
2064
|
const extra = chunk as AdapterYieldChunk
|
|
2065
|
+
const rawToolResultOutcome = tanstackMetadata(chunk)?.toolResultOutcome
|
|
2066
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
2067
|
+
? rawToolResultOutcome
|
|
2068
|
+
: undefined
|
|
2045
2069
|
const isOutputError =
|
|
2046
2070
|
extra.state === 'output-error' ||
|
|
2047
|
-
tanstackMetadata(chunk)?.state === 'output-error'
|
|
2071
|
+
tanstackMetadata(chunk)?.state === 'output-error' ||
|
|
2072
|
+
toolResultOutcome !== undefined
|
|
2048
2073
|
|
|
2049
2074
|
// Step 1: Update the tool-call part's output field
|
|
2050
2075
|
let output: unknown
|
|
@@ -2069,9 +2094,14 @@ export class StreamProcessor {
|
|
|
2069
2094
|
this.messages,
|
|
2070
2095
|
messageId,
|
|
2071
2096
|
chunk.toolCallId,
|
|
2072
|
-
|
|
2097
|
+
// The server sends a ContentPart[] result as a JSON string. Keep the
|
|
2098
|
+
// parsed array so uiMessagesToWire can carry it in metadata.
|
|
2099
|
+
isContentPartArray(output)
|
|
2100
|
+
? output
|
|
2101
|
+
: aguiContentToContentParts(chunk.content),
|
|
2073
2102
|
resultState,
|
|
2074
2103
|
resultState === 'error' ? toolResultErrorText(output) : undefined,
|
|
2104
|
+
toolResultOutcome,
|
|
2075
2105
|
)
|
|
2076
2106
|
this.emitMessagesChange()
|
|
2077
2107
|
}
|
|
@@ -2678,11 +2708,11 @@ export class StreamProcessor {
|
|
|
2678
2708
|
toolCall.parsedArguments = undefined
|
|
2679
2709
|
}
|
|
2680
2710
|
|
|
2681
|
-
// Don't downgrade the rendered part of a call that already reached
|
|
2682
|
-
// terminal 'error' state (e.g.
|
|
2711
|
+
// Don't downgrade the rendered part of a call that already reached a
|
|
2712
|
+
// terminal 'error' or 'complete' state (e.g. a tool result arrived
|
|
2683
2713
|
// without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream
|
|
2684
|
-
// safety net must not clobber a
|
|
2685
|
-
if (this.
|
|
2714
|
+
// safety net must not clobber a finished call back to 'input-complete'.
|
|
2715
|
+
if (this.isToolCallPartTerminal(toolCall.id)) {
|
|
2686
2716
|
return
|
|
2687
2717
|
}
|
|
2688
2718
|
|
|
@@ -2728,11 +2758,11 @@ export class StreamProcessor {
|
|
|
2728
2758
|
}
|
|
2729
2759
|
|
|
2730
2760
|
/**
|
|
2731
|
-
* Whether the rendered tool-call part for the given id has reached
|
|
2732
|
-
* terminal 'error' state. Used to prevent the completion
|
|
2733
|
-
* downgrading a
|
|
2761
|
+
* Whether the rendered tool-call part for the given id has reached a
|
|
2762
|
+
* terminal 'error' or 'complete' state. Used to prevent the completion
|
|
2763
|
+
* safety net from downgrading a finished call back to 'input-complete'.
|
|
2734
2764
|
*/
|
|
2735
|
-
private
|
|
2765
|
+
private isToolCallPartTerminal(toolCallId: string): boolean {
|
|
2736
2766
|
// `initialMessages` may be ModelMessage-shaped (no `parts`) — e.g. the
|
|
2737
2767
|
// common pattern of seeding a processor with the same messages passed to
|
|
2738
2768
|
// `chat()`. Guard the access so iterating them never throws.
|
|
@@ -2742,7 +2772,7 @@ export class StreamProcessor {
|
|
|
2742
2772
|
(part) =>
|
|
2743
2773
|
part.type === 'tool-call' &&
|
|
2744
2774
|
part.id === toolCallId &&
|
|
2745
|
-
part.state === 'error',
|
|
2775
|
+
(part.state === 'error' || part.state === 'complete'),
|
|
2746
2776
|
),
|
|
2747
2777
|
)
|
|
2748
2778
|
}
|
|
@@ -2,7 +2,12 @@ import { normalizeToolResult } from '../../../utilities/tool-result'
|
|
|
2
2
|
import { tanstackMetadata } from '../../../utilities/merge-metadata'
|
|
3
3
|
import { isProviderExecutedToolCall } from '../../../utilities/provider-executed'
|
|
4
4
|
import type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
StandardSchemaValidationError,
|
|
7
|
+
isStandardSchema,
|
|
8
|
+
parseWithStandardSchema,
|
|
9
|
+
validateWithStandardSchema,
|
|
10
|
+
} from './schema-converter'
|
|
6
11
|
import type { ToolApprovalResolution } from '../../../interrupts'
|
|
7
12
|
import type {
|
|
8
13
|
AnyTool,
|
|
@@ -19,6 +24,8 @@ import type {
|
|
|
19
24
|
ToolCallEndEvent,
|
|
20
25
|
ToolCallStartEvent,
|
|
21
26
|
ToolExecutionContext,
|
|
27
|
+
ToolInputResponse,
|
|
28
|
+
ToolResultOutcome,
|
|
22
29
|
ToolOutputState,
|
|
23
30
|
} from '../../../types'
|
|
24
31
|
import type {
|
|
@@ -462,6 +469,8 @@ export interface ToolResult {
|
|
|
462
469
|
toolName: string
|
|
463
470
|
result: any
|
|
464
471
|
state?: 'output-available' | 'output-error'
|
|
472
|
+
/** Set when the user or middleware cancelled or denied the tool call; state is output-error. */
|
|
473
|
+
outcome?: ToolResultOutcome
|
|
465
474
|
/** Duration of tool execution in milliseconds (only for server-executed tools) */
|
|
466
475
|
duration?: number
|
|
467
476
|
/**
|
|
@@ -490,9 +499,38 @@ export interface ClientToolRequest {
|
|
|
490
499
|
input: any
|
|
491
500
|
}
|
|
492
501
|
|
|
502
|
+
/** Form or sampling input that paused a server tool. */
|
|
503
|
+
export interface McpInputRequest {
|
|
504
|
+
toolCallId: string
|
|
505
|
+
toolName: string
|
|
506
|
+
kind: 'form' | 'sampling'
|
|
507
|
+
request: unknown
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
interface McpInputRequiredThrow {
|
|
511
|
+
name: 'MCPInputRequiredError'
|
|
512
|
+
kind: 'form' | 'sampling'
|
|
513
|
+
request: unknown
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function isMcpInputRequired(value: unknown): value is McpInputRequiredThrow {
|
|
517
|
+
if (typeof value !== 'object' || value === null) return false
|
|
518
|
+
if (!('name' in value) || value.name !== 'MCPInputRequiredError') {
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
if (!('kind' in value)) return false
|
|
522
|
+
const kindIsFormOrSampling =
|
|
523
|
+
value.kind === 'form' || value.kind === 'sampling'
|
|
524
|
+
if (!kindIsFormOrSampling) return false
|
|
525
|
+
return 'request' in value
|
|
526
|
+
}
|
|
527
|
+
|
|
493
528
|
export interface ToolResumeExecutionState {
|
|
529
|
+
clientToolErrors?: ReadonlyMap<string, string>
|
|
494
530
|
deniedToolResults?: ReadonlyMap<string, unknown>
|
|
495
531
|
cancelledToolCallIds?: ReadonlySet<string>
|
|
532
|
+
/** Answers to `mcp_input` interrupts, by tool call id. */
|
|
533
|
+
inputResponses?: ReadonlyMap<string, ToolInputResponse>
|
|
496
534
|
}
|
|
497
535
|
|
|
498
536
|
function approvalResolution(
|
|
@@ -527,6 +565,8 @@ interface ExecuteToolCallsResult {
|
|
|
527
565
|
needsApproval: Array<ApprovalRequest>
|
|
528
566
|
/** Tools that need client-side execution */
|
|
529
567
|
needsClientExecution: Array<ClientToolRequest>
|
|
568
|
+
/** Server tools that paused for MCP form or sampling input */
|
|
569
|
+
inputRequired: Array<McpInputRequest>
|
|
530
570
|
/** Interrupts raised by subagents that run as tools */
|
|
531
571
|
subagentInterrupts: Array<Interrupt>
|
|
532
572
|
}
|
|
@@ -639,6 +679,7 @@ export async function* executeServerTool<TContext = unknown>(
|
|
|
639
679
|
pendingEvents: Array<CustomEvent | StreamChunk>,
|
|
640
680
|
results: Array<ToolResult>,
|
|
641
681
|
middlewareHooks?: ToolExecutionMiddlewareHooks,
|
|
682
|
+
inputRequired?: Array<McpInputRequest>,
|
|
642
683
|
subagentInterrupts?: Array<Interrupt>,
|
|
643
684
|
): AsyncGenerator<CustomEvent | StreamChunk, void, void> {
|
|
644
685
|
const startTime = Date.now()
|
|
@@ -744,6 +785,18 @@ export async function* executeServerTool<TContext = unknown>(
|
|
|
744
785
|
throw error
|
|
745
786
|
}
|
|
746
787
|
|
|
788
|
+
// Same shape as MCPInputRequiredError. Pause instead of a tool error.
|
|
789
|
+
if (isMcpInputRequired(error)) {
|
|
790
|
+
if (!inputRequired) throw error
|
|
791
|
+
inputRequired.push({
|
|
792
|
+
toolCallId: toolCall.id,
|
|
793
|
+
toolName,
|
|
794
|
+
kind: error.kind,
|
|
795
|
+
request: error.request,
|
|
796
|
+
})
|
|
797
|
+
return
|
|
798
|
+
}
|
|
799
|
+
|
|
747
800
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
748
801
|
results.push({
|
|
749
802
|
toolCallId: toolCall.id,
|
|
@@ -768,17 +821,35 @@ export async function* executeServerTool<TContext = unknown>(
|
|
|
768
821
|
}
|
|
769
822
|
}
|
|
770
823
|
|
|
771
|
-
function buildClientToolResult(
|
|
824
|
+
async function buildClientToolResult(
|
|
772
825
|
toolCallId: string,
|
|
773
826
|
toolName: string,
|
|
774
827
|
tool: AnyTool,
|
|
775
828
|
rawResult: unknown,
|
|
776
829
|
input?: unknown,
|
|
777
|
-
|
|
830
|
+
errorText?: string,
|
|
831
|
+
): Promise<ToolResult> {
|
|
832
|
+
if (errorText !== undefined) {
|
|
833
|
+
return {
|
|
834
|
+
toolCallId,
|
|
835
|
+
toolName,
|
|
836
|
+
result: { error: errorText },
|
|
837
|
+
input,
|
|
838
|
+
state: 'output-error',
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
778
842
|
try {
|
|
779
843
|
let result = rawResult
|
|
780
844
|
if (tool.outputSchema && isStandardSchema(tool.outputSchema)) {
|
|
781
|
-
|
|
845
|
+
const validation = await validateWithStandardSchema<unknown>(
|
|
846
|
+
tool.outputSchema,
|
|
847
|
+
result,
|
|
848
|
+
)
|
|
849
|
+
if (!validation.success) {
|
|
850
|
+
throw new StandardSchemaValidationError(validation.issues)
|
|
851
|
+
}
|
|
852
|
+
result = validation.data
|
|
782
853
|
}
|
|
783
854
|
|
|
784
855
|
const parsed =
|
|
@@ -835,6 +906,7 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
835
906
|
const results: Array<ToolResult> = []
|
|
836
907
|
const needsApproval: Array<ApprovalRequest> = []
|
|
837
908
|
const needsClientExecution: Array<ClientToolRequest> = []
|
|
909
|
+
const inputRequired: Array<McpInputRequest> = []
|
|
838
910
|
const subagentInterrupts: Array<Interrupt> = []
|
|
839
911
|
|
|
840
912
|
// Create tool lookup map
|
|
@@ -891,6 +963,7 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
891
963
|
toolName,
|
|
892
964
|
result: { error: 'Tool execution cancelled' },
|
|
893
965
|
state: 'output-error',
|
|
966
|
+
outcome: 'cancelled',
|
|
894
967
|
})
|
|
895
968
|
continue
|
|
896
969
|
}
|
|
@@ -942,10 +1015,12 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
942
1015
|
|
|
943
1016
|
// Create a ToolExecutionContext for this tool call with event emission
|
|
944
1017
|
const pendingEvents: Array<CustomEvent | StreamChunk> = []
|
|
1018
|
+
const inputResponse = resumeState?.inputResponses?.get(toolCall.id)
|
|
945
1019
|
const context = {
|
|
946
1020
|
toolCallId: toolCall.id,
|
|
947
1021
|
context: userContext,
|
|
948
1022
|
abortSignal,
|
|
1023
|
+
...(inputResponse !== undefined ? { inputResponse } : {}),
|
|
949
1024
|
emitCustomEvent: (
|
|
950
1025
|
eventName: string,
|
|
951
1026
|
value: Record<string, any>,
|
|
@@ -980,14 +1055,16 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
980
1055
|
if (approved) {
|
|
981
1056
|
input = editedApprovalArgs(resolution) ?? input
|
|
982
1057
|
// Approved - check if client has executed
|
|
983
|
-
|
|
1058
|
+
const clientError = resumeState?.clientToolErrors?.get(toolCall.id)
|
|
1059
|
+
if (clientResults.has(toolCall.id) || clientError !== undefined) {
|
|
984
1060
|
results.push(
|
|
985
|
-
buildClientToolResult(
|
|
1061
|
+
await buildClientToolResult(
|
|
986
1062
|
toolCall.id,
|
|
987
1063
|
toolName,
|
|
988
1064
|
tool,
|
|
989
1065
|
clientResults.get(toolCall.id),
|
|
990
1066
|
input,
|
|
1067
|
+
clientError,
|
|
991
1068
|
),
|
|
992
1069
|
)
|
|
993
1070
|
} else {
|
|
@@ -1008,6 +1085,7 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
1008
1085
|
deniedApprovalResult(resolution),
|
|
1009
1086
|
input,
|
|
1010
1087
|
state: 'output-error',
|
|
1088
|
+
outcome: 'denied',
|
|
1011
1089
|
})
|
|
1012
1090
|
}
|
|
1013
1091
|
} else {
|
|
@@ -1021,14 +1099,16 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
1021
1099
|
}
|
|
1022
1100
|
} else {
|
|
1023
1101
|
// No approval needed - check if client has executed
|
|
1024
|
-
|
|
1102
|
+
const clientError = resumeState?.clientToolErrors?.get(toolCall.id)
|
|
1103
|
+
if (clientResults.has(toolCall.id) || clientError !== undefined) {
|
|
1025
1104
|
results.push(
|
|
1026
|
-
buildClientToolResult(
|
|
1105
|
+
await buildClientToolResult(
|
|
1027
1106
|
toolCall.id,
|
|
1028
1107
|
toolName,
|
|
1029
1108
|
tool,
|
|
1030
1109
|
clientResults.get(toolCall.id),
|
|
1031
1110
|
input,
|
|
1111
|
+
clientError,
|
|
1032
1112
|
),
|
|
1033
1113
|
)
|
|
1034
1114
|
} else {
|
|
@@ -1077,6 +1157,7 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
1077
1157
|
pendingEvents,
|
|
1078
1158
|
results,
|
|
1079
1159
|
middlewareHooks,
|
|
1160
|
+
inputRequired,
|
|
1080
1161
|
subagentInterrupts,
|
|
1081
1162
|
)
|
|
1082
1163
|
} else {
|
|
@@ -1089,6 +1170,7 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
1089
1170
|
deniedApprovalResult(resolution),
|
|
1090
1171
|
input,
|
|
1091
1172
|
state: 'output-error',
|
|
1173
|
+
outcome: 'denied',
|
|
1092
1174
|
})
|
|
1093
1175
|
}
|
|
1094
1176
|
} else {
|
|
@@ -1126,9 +1208,16 @@ export async function* executeToolCalls<TContext = unknown>(
|
|
|
1126
1208
|
pendingEvents,
|
|
1127
1209
|
results,
|
|
1128
1210
|
middlewareHooks,
|
|
1211
|
+
inputRequired,
|
|
1129
1212
|
subagentInterrupts,
|
|
1130
1213
|
)
|
|
1131
1214
|
}
|
|
1132
1215
|
|
|
1133
|
-
return {
|
|
1216
|
+
return {
|
|
1217
|
+
results,
|
|
1218
|
+
needsApproval,
|
|
1219
|
+
needsClientExecution,
|
|
1220
|
+
inputRequired,
|
|
1221
|
+
subagentInterrupts,
|
|
1222
|
+
}
|
|
1134
1223
|
}
|
|
@@ -99,6 +99,7 @@ export interface ServerTool<
|
|
|
99
99
|
outputSchema?: TOutput
|
|
100
100
|
needsApproval?: TNeedsApproval
|
|
101
101
|
approvalSchema?: TApprovalSchema
|
|
102
|
+
execution?: 'task'
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/**
|
|
@@ -127,6 +128,7 @@ export interface ClientTool<
|
|
|
127
128
|
outputSchema?: TOutput
|
|
128
129
|
needsApproval?: TNeedsApproval
|
|
129
130
|
approvalSchema?: TApprovalSchema
|
|
131
|
+
execution?: 'task'
|
|
130
132
|
lazy?: boolean
|
|
131
133
|
metadata?: Record<string, unknown>
|
|
132
134
|
execute?: ToolExecuteFunction<TInput, TOutput, TContext>
|
|
@@ -158,6 +160,7 @@ export interface ToolDefinitionInstance<
|
|
|
158
160
|
outputSchema: TOutput
|
|
159
161
|
needsApproval?: TNeedsApproval
|
|
160
162
|
approvalSchema: TApprovalSchema
|
|
163
|
+
execution?: 'task'
|
|
161
164
|
readonly [toolApprovalCapability]?: {
|
|
162
165
|
needsApproval: TNeedsApproval
|
|
163
166
|
approvalSchema: TApprovalSchema
|
|
@@ -221,6 +224,7 @@ export type ToolDefinitionConfig<
|
|
|
221
224
|
outputSchema?: TOutput
|
|
222
225
|
lazy?: boolean
|
|
223
226
|
metadata?: Record<string, unknown>
|
|
227
|
+
execution?: 'task'
|
|
224
228
|
} & ApprovalConfig<TNeedsApproval, TApprovalSchema>
|
|
225
229
|
|
|
226
230
|
/**
|
|
@@ -353,6 +357,7 @@ export function toolDefinition<
|
|
|
353
357
|
const outputSchema = config.outputSchema as TOutput
|
|
354
358
|
const approvalSchema = config.approvalSchema as TApprovalSchema
|
|
355
359
|
const needsApproval = config.needsApproval as TNeedsApproval | undefined
|
|
360
|
+
const execution = config.execution
|
|
356
361
|
|
|
357
362
|
const definition: ToolDefinition<
|
|
358
363
|
TInput,
|
|
@@ -367,6 +372,7 @@ export function toolDefinition<
|
|
|
367
372
|
outputSchema,
|
|
368
373
|
approvalSchema,
|
|
369
374
|
needsApproval,
|
|
375
|
+
execution,
|
|
370
376
|
server<TContext = unknown>(
|
|
371
377
|
execute: ToolExecuteFunction<TInput, TOutput, TContext>,
|
|
372
378
|
): ServerTool<
|
|
@@ -385,6 +391,7 @@ export function toolDefinition<
|
|
|
385
391
|
outputSchema,
|
|
386
392
|
approvalSchema,
|
|
387
393
|
needsApproval,
|
|
394
|
+
execution,
|
|
388
395
|
execute,
|
|
389
396
|
}
|
|
390
397
|
},
|
|
@@ -407,6 +414,7 @@ export function toolDefinition<
|
|
|
407
414
|
outputSchema,
|
|
408
415
|
approvalSchema,
|
|
409
416
|
needsApproval,
|
|
417
|
+
execution,
|
|
410
418
|
...(execute !== undefined && { execute }),
|
|
411
419
|
}
|
|
412
420
|
},
|
|
@@ -131,6 +131,10 @@ export interface EvaluateAdapterResult {
|
|
|
131
131
|
model: string
|
|
132
132
|
answers: Record<string, WireAnswer>
|
|
133
133
|
usage: TokenUsage
|
|
134
|
+
/** Provider response id, for example to look the request up later. */
|
|
135
|
+
id?: string
|
|
136
|
+
/** Upstream provider that served the request, when a router reports it. */
|
|
137
|
+
provider?: string
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
/**
|
|
@@ -100,6 +100,10 @@ export interface EvaluateResultMeta {
|
|
|
100
100
|
/** Resolved model id from the provider. */
|
|
101
101
|
model: string
|
|
102
102
|
usage: TokenUsage
|
|
103
|
+
/** Provider response id, when the adapter returns one. */
|
|
104
|
+
id?: string
|
|
105
|
+
/** Upstream provider that served the request, when the adapter returns one. */
|
|
106
|
+
provider?: string
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
/**
|
|
@@ -576,6 +580,8 @@ export async function decide<
|
|
|
576
580
|
return withMeta(answers, {
|
|
577
581
|
model: result.model,
|
|
578
582
|
usage: result.usage,
|
|
583
|
+
...(result.id !== undefined && { id: result.id }),
|
|
584
|
+
...(result.provider !== undefined && { provider: result.provider }),
|
|
579
585
|
})
|
|
580
586
|
} catch (error) {
|
|
581
587
|
const duration = Date.now() - startTime
|
package/src/client.ts
CHANGED
package/src/index.ts
CHANGED
package/src/interrupt-resume.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
isStandardSchema,
|
|
15
15
|
validateWithStandardSchema,
|
|
16
16
|
} from './activities/chat/tools/schema-converter'
|
|
17
|
+
import { tanstackMetadata } from './utilities/merge-metadata'
|
|
17
18
|
import type {
|
|
18
19
|
InterruptBinding,
|
|
19
20
|
InterruptSubmissionError,
|
|
@@ -95,6 +96,33 @@ function stringField(
|
|
|
95
96
|
return typeof value[key] === 'string' ? value[key] : undefined
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
type ClientToolResumeResult =
|
|
100
|
+
| { state: 'output-available'; output: unknown }
|
|
101
|
+
| { state: 'output-error'; errorText: string }
|
|
102
|
+
|
|
103
|
+
function clientToolResult(
|
|
104
|
+
entry: RunAgentResumeItem,
|
|
105
|
+
): ClientToolResumeResult | null {
|
|
106
|
+
if (tanstackMetadata(entry)?.state === 'output-error') {
|
|
107
|
+
const result = objectValue(entry.payload)
|
|
108
|
+
if (
|
|
109
|
+
!result ||
|
|
110
|
+
Object.keys(result).length !== 1 ||
|
|
111
|
+
typeof result.error !== 'string'
|
|
112
|
+
) {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
state: 'output-error',
|
|
117
|
+
errorText: result.error,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
state: 'output-available',
|
|
122
|
+
output: entry.payload,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
98
126
|
function normalizeIssuePath(
|
|
99
127
|
path: ReadonlyArray<unknown> | undefined,
|
|
100
128
|
): ReadonlyArray<string | number> | undefined {
|
|
@@ -528,7 +556,19 @@ export async function validateInterruptResumeBatch(
|
|
|
528
556
|
if (schemaDrifted) continue
|
|
529
557
|
|
|
530
558
|
if (binding.kind === 'client-tool-execution') {
|
|
531
|
-
|
|
559
|
+
const result = clientToolResult(entry)
|
|
560
|
+
if (!result) {
|
|
561
|
+
errors.push(
|
|
562
|
+
interruptItemError(
|
|
563
|
+
input,
|
|
564
|
+
record.interruptId,
|
|
565
|
+
'invalid-tool-output',
|
|
566
|
+
`Tool ${binding.toolName} result is invalid.`,
|
|
567
|
+
),
|
|
568
|
+
)
|
|
569
|
+
continue
|
|
570
|
+
}
|
|
571
|
+
if (result.state === 'output-available' && responseSchema !== undefined) {
|
|
532
572
|
await pushSchemaIssues({
|
|
533
573
|
request: input,
|
|
534
574
|
errors,
|
|
@@ -539,13 +579,16 @@ export async function validateInterruptResumeBatch(
|
|
|
539
579
|
label: `Tool ${binding.toolName} output is invalid`,
|
|
540
580
|
})
|
|
541
581
|
}
|
|
542
|
-
if (
|
|
582
|
+
if (
|
|
583
|
+
result.state === 'output-available' &&
|
|
584
|
+
tool.outputSchema !== undefined
|
|
585
|
+
) {
|
|
543
586
|
await pushSchemaIssues({
|
|
544
587
|
request: input,
|
|
545
588
|
errors,
|
|
546
589
|
interruptId: record.interruptId,
|
|
547
590
|
schema: tool.outputSchema,
|
|
548
|
-
value:
|
|
591
|
+
value: result.output,
|
|
549
592
|
code: 'invalid-tool-output',
|
|
550
593
|
label: `Tool ${binding.toolName} output is invalid`,
|
|
551
594
|
})
|
|
@@ -682,6 +725,7 @@ export async function validateInterruptResumeBatch(
|
|
|
682
725
|
const canonical = canonicalizeInterruptResolutions(input.resume ?? [])
|
|
683
726
|
const approvals = new Map<string, ToolApprovalResolution>()
|
|
684
727
|
const clientToolResults = new Map<string, unknown>()
|
|
728
|
+
const clientToolErrors = new Map<string, string>()
|
|
685
729
|
const genericInterrupts = new Map<
|
|
686
730
|
string,
|
|
687
731
|
| { interruptId: string; status: 'resolved'; payload: unknown }
|
|
@@ -738,7 +782,13 @@ export async function validateInterruptResumeBatch(
|
|
|
738
782
|
continue
|
|
739
783
|
}
|
|
740
784
|
if (binding.kind === 'client-tool-execution') {
|
|
741
|
-
|
|
785
|
+
const result = clientToolResult(entry)
|
|
786
|
+
if (!result) continue
|
|
787
|
+
if (result.state === 'output-error') {
|
|
788
|
+
clientToolErrors.set(binding.toolCallId, result.errorText)
|
|
789
|
+
} else {
|
|
790
|
+
clientToolResults.set(binding.toolCallId, result.output)
|
|
791
|
+
}
|
|
742
792
|
continue
|
|
743
793
|
}
|
|
744
794
|
const envelope = objectValue(entry.payload)
|
|
@@ -781,6 +831,7 @@ export async function validateInterruptResumeBatch(
|
|
|
781
831
|
resumeToolState: {
|
|
782
832
|
approvals,
|
|
783
833
|
clientToolResults,
|
|
834
|
+
clientToolErrors,
|
|
784
835
|
genericInterrupts,
|
|
785
836
|
deniedToolResults,
|
|
786
837
|
cancelledToolCallIds,
|