@tanstack/ai 0.42.0 → 0.43.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 +15 -1
- package/dist/esm/activities/chat/adapter.js +23 -16
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
- package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
- package/dist/esm/activities/chat/cancel.d.ts +40 -0
- package/dist/esm/activities/chat/cancel.js +54 -0
- package/dist/esm/activities/chat/cancel.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +28 -21
- package/dist/esm/activities/chat/index.js +2100 -1813
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
- package/dist/esm/activities/chat/mcp/manager.js +90 -77
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
- package/dist/esm/activities/chat/messages.js +397 -346
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.js +17 -15
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
- package/dist/esm/activities/chat/middleware/compose.js +623 -531
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.js +12 -5
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
- package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
- package/dist/esm/activities/chat/middleware/locks.js +71 -0
- package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
- package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
- package/dist/esm/activities/chat/middleware/run-store.js +176 -0
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
- package/dist/esm/activities/chat/middleware/validate.js +23 -28
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
- package/dist/esm/activities/chat/stream/json-parser.js +39 -25
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
- package/dist/esm/activities/chat/stream/processor.js +1341 -1542
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.js +69 -53
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
- package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
- package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.js +85 -47
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +22 -15
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +4 -0
- package/dist/esm/activities/generateAudio/index.js +141 -105
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +22 -15
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +4 -0
- package/dist/esm/activities/generateImage/index.js +155 -111
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +22 -15
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
- package/dist/esm/activities/generateSpeech/index.js +159 -110
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +22 -15
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
- package/dist/esm/activities/generateTranscription/index.js +159 -100
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +36 -29
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +143 -19
- package/dist/esm/activities/generateVideo/index.js +456 -279
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.js +60 -48
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/index.js +8 -34
- package/dist/esm/activities/middleware/index.d.ts +1 -1
- package/dist/esm/activities/middleware/run.d.ts +10 -0
- package/dist/esm/activities/middleware/run.js +53 -29
- package/dist/esm/activities/middleware/run.js.map +1 -1
- package/dist/esm/activities/middleware/types.d.ts +44 -6
- package/dist/esm/activities/stream-generation-result.d.ts +4 -1
- package/dist/esm/activities/stream-generation-result.js +79 -44
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +22 -15
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.d.ts +27 -0
- package/dist/esm/activities/summarize/index.js +268 -102
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -1
- package/dist/esm/adapter-internals.js +4 -11
- package/dist/esm/client.d.ts +25 -3
- package/dist/esm/client.js +131 -64
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/custom-events.d.ts +76 -0
- package/dist/esm/custom-events.js +37 -0
- package/dist/esm/custom-events.js.map +1 -0
- package/dist/esm/delivery-detach.d.ts +50 -0
- package/dist/esm/delivery-detach.js +71 -0
- package/dist/esm/delivery-detach.js.map +1 -0
- package/dist/esm/delivery-disconnect.d.ts +62 -0
- package/dist/esm/delivery-disconnect.js +81 -0
- package/dist/esm/delivery-disconnect.js.map +1 -0
- package/dist/esm/extend-adapter.js +19 -17
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +24 -6
- package/dist/esm/index.js +30 -98
- package/dist/esm/interrupt-resume.d.ts +71 -0
- package/dist/esm/interrupt-resume.js +438 -0
- package/dist/esm/interrupt-resume.js.map +1 -0
- package/dist/esm/interrupt-serialization.d.ts +12 -0
- package/dist/esm/interrupt-serialization.js +178 -0
- package/dist/esm/interrupt-serialization.js.map +1 -0
- package/dist/esm/interrupts.d.ts +84 -0
- package/dist/esm/interrupts.js +31 -0
- package/dist/esm/interrupts.js.map +1 -0
- package/dist/esm/locks.d.ts +10 -0
- package/dist/esm/locks.js +2 -0
- package/dist/esm/logger/console-logger.js +101 -78
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +104 -89
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +54 -49
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +1 -1
- package/dist/esm/middlewares/content-guard.js +142 -148
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/index.js +2 -6
- package/dist/esm/middlewares/otel.js +598 -732
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.js +47 -40
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/realtime/event-emitter.js +24 -25
- package/dist/esm/realtime/event-emitter.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +5 -9
- package/dist/esm/realtime/index.js +29 -6
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/scope.d.ts +47 -0
- package/dist/esm/stream-durability.d.ts +171 -0
- package/dist/esm/stream-durability.js +295 -0
- package/dist/esm/stream-durability.js.map +1 -0
- package/dist/esm/stream-to-response.d.ts +178 -13
- package/dist/esm/stream-to-response.js +663 -115
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +30 -16
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/system-prompts.js +27 -21
- package/dist/esm/system-prompts.js.map +1 -1
- package/dist/esm/tool-registry.js +72 -45
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/tools/provider-tool.js +14 -5
- package/dist/esm/tools/provider-tool.js.map +1 -1
- package/dist/esm/types.d.ts +321 -42
- package/dist/esm/types.js +2 -0
- package/dist/esm/utilities/ag-ui-wire.js +79 -93
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +26 -4
- package/dist/esm/utilities/chat-params.js +218 -92
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/errors.js +28 -18
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +46 -41
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/numbers.js +13 -10
- package/dist/esm/utilities/numbers.js.map +1 -1
- package/dist/esm/utilities/provider-executed.js +20 -11
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/sampling-keys.js +31 -19
- package/dist/esm/utilities/sampling-keys.js.map +1 -1
- package/dist/esm/utilities/tool-result.js +42 -30
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/dist/esm/utilities/usage.js +27 -9
- package/dist/esm/utilities/usage.js.map +1 -1
- package/dist/esm/utils.js +26 -18
- package/dist/esm/utils.js.map +1 -1
- package/package.json +10 -6
- package/skills/ai-core/SKILL.md +69 -18
- package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
- package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +98 -11
- package/skills/ai-core/client-persistence/SKILL.md +277 -0
- package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/skills/ai-core/locks/SKILL.md +143 -0
- package/skills/ai-core/media-generation/SKILL.md +144 -12
- package/skills/ai-core/middleware/SKILL.md +258 -33
- package/skills/ai-core/structured-outputs/SKILL.md +1 -1
- package/skills/ai-core/tool-calling/SKILL.md +54 -61
- package/src/activities/chat/agent-loop-strategies.ts +5 -39
- package/src/activities/chat/cancel.ts +81 -0
- package/src/activities/chat/index.ts +1091 -200
- package/src/activities/chat/mcp/manager.ts +4 -4
- package/src/activities/chat/mcp/types.ts +2 -2
- package/src/activities/chat/messages.ts +5 -3
- package/src/activities/chat/middleware/builder.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +186 -9
- package/src/activities/chat/middleware/index.ts +26 -0
- package/src/activities/chat/middleware/locks.ts +102 -0
- package/src/activities/chat/middleware/pending-turn.ts +47 -0
- package/src/activities/chat/middleware/run-disconnect.ts +62 -0
- package/src/activities/chat/middleware/run-store.ts +412 -0
- package/src/activities/chat/middleware/types.ts +62 -1
- package/src/activities/chat/stream/processor.ts +189 -5
- package/src/activities/chat/tools/approval-schema.ts +205 -0
- package/src/activities/chat/tools/tool-calls.ts +106 -13
- package/src/activities/chat/tools/tool-definition.ts +210 -39
- package/src/activities/generateAudio/index.ts +20 -3
- package/src/activities/generateImage/index.ts +20 -3
- package/src/activities/generateSpeech/index.ts +25 -3
- package/src/activities/generateTranscription/index.ts +26 -3
- package/src/activities/generateVideo/index.ts +345 -82
- package/src/activities/middleware/index.ts +2 -0
- package/src/activities/middleware/run.ts +31 -0
- package/src/activities/middleware/types.ts +49 -5
- package/src/activities/stream-generation-result.ts +30 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -0
- package/src/activities/summarize/index.ts +200 -10
- package/src/adapter-internals.ts +10 -1
- package/src/client.ts +244 -0
- package/src/custom-events.ts +107 -0
- package/src/delivery-detach.ts +72 -0
- package/src/delivery-disconnect.ts +84 -0
- package/src/index.ts +138 -1
- package/src/interrupt-resume.ts +824 -0
- package/src/interrupt-serialization.ts +183 -0
- package/src/interrupts.ts +146 -0
- package/src/locks.ts +17 -0
- package/src/logger/types.ts +1 -1
- package/src/middlewares/otel.ts +1 -0
- package/src/realtime/index.ts +5 -9
- package/src/scope.ts +47 -0
- package/src/stream-durability.ts +598 -0
- package/src/stream-to-response.ts +1051 -95
- package/src/strip-to-spec-middleware.ts +3 -3
- package/src/types.ts +405 -45
- package/src/utilities/chat-params.ts +245 -55
- package/dist/esm/activities/index.js.map +0 -1
- package/dist/esm/adapter-internals.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/middlewares/index.js.map +0 -1
|
@@ -50,6 +50,7 @@ import type {
|
|
|
50
50
|
} from './types'
|
|
51
51
|
import type {
|
|
52
52
|
ContentPart,
|
|
53
|
+
Interrupt,
|
|
53
54
|
MessagePart,
|
|
54
55
|
ModelMessage,
|
|
55
56
|
StreamChunk,
|
|
@@ -917,6 +918,12 @@ export class StreamProcessor {
|
|
|
917
918
|
* `tool-call` part is absent from the snapshot, carry the `tool-call` part
|
|
918
919
|
* forward from the pre-snapshot state (state and output untouched) so a
|
|
919
920
|
* subsequent `addToolResult(toolCallId)` can still locate the call.
|
|
921
|
+
*
|
|
922
|
+
* Post-pass (c): AG-UI wire snapshots rebuild `tool-call` parts as
|
|
923
|
+
* `input-complete` without `output` (ModelMessage has no result field on
|
|
924
|
+
* the call). After anchoring results, copy each `tool-result` onto its
|
|
925
|
+
* matching `tool-call` (and prefer pre-snapshot complete/output when the
|
|
926
|
+
* snapshot is poorer) so server tools keep the same UI shape as client tools.
|
|
920
927
|
*/
|
|
921
928
|
private reconcileSnapshotToolCalls(
|
|
922
929
|
snapshot: Array<UIMessage>,
|
|
@@ -1016,7 +1023,87 @@ export class StreamProcessor {
|
|
|
1016
1023
|
target.parts = parts
|
|
1017
1024
|
}
|
|
1018
1025
|
|
|
1019
|
-
return reconciled
|
|
1026
|
+
return this.enrichSnapshotToolCallsFromResults(reconciled, prevToolCalls)
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Post-pass (c): fold `tool-result` content into sibling `tool-call` parts
|
|
1031
|
+
* and prefer pre-snapshot complete/output when the snapshot rebuilt a
|
|
1032
|
+
* poorer `input-complete` call (AG-UI ModelMessage has no result on calls).
|
|
1033
|
+
*/
|
|
1034
|
+
private enrichSnapshotToolCallsFromResults(
|
|
1035
|
+
messages: Array<UIMessage>,
|
|
1036
|
+
prevToolCalls: Map<string, ToolCallPart>,
|
|
1037
|
+
): Array<UIMessage> {
|
|
1038
|
+
const resultsByCallId = new Map<string, ToolResultPart>()
|
|
1039
|
+
for (const msg of messages) {
|
|
1040
|
+
for (const part of msg.parts) {
|
|
1041
|
+
if (part.type === 'tool-result') {
|
|
1042
|
+
resultsByCallId.set(part.toolCallId, part)
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return messages.map((msg) => {
|
|
1048
|
+
const parts = msg.parts.map((part) => {
|
|
1049
|
+
if (part.type !== 'tool-call') return part
|
|
1050
|
+
|
|
1051
|
+
const prev = prevToolCalls.get(part.id)
|
|
1052
|
+
const result = resultsByCallId.get(part.id)
|
|
1053
|
+
let next: ToolCallPart = part
|
|
1054
|
+
|
|
1055
|
+
// Prefer a pre-snapshot call that already carried output/complete —
|
|
1056
|
+
// the client observed TOOL_CALL_END/RESULT before the snapshot wipe.
|
|
1057
|
+
if (
|
|
1058
|
+
prev &&
|
|
1059
|
+
(prev.output !== undefined ||
|
|
1060
|
+
prev.state === 'complete' ||
|
|
1061
|
+
prev.state === 'error') &&
|
|
1062
|
+
(part.output === undefined ||
|
|
1063
|
+
part.state === 'input-complete' ||
|
|
1064
|
+
part.state === 'input-streaming' ||
|
|
1065
|
+
part.state === 'awaiting-input')
|
|
1066
|
+
) {
|
|
1067
|
+
next = {
|
|
1068
|
+
...part,
|
|
1069
|
+
...(prev.output !== undefined ? { output: prev.output } : {}),
|
|
1070
|
+
state: prev.state,
|
|
1071
|
+
...(prev.approval !== undefined ? { approval: prev.approval } : {}),
|
|
1072
|
+
...(prev.metadata !== undefined ? { metadata: prev.metadata } : {}),
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Apply sibling tool-result when the call still has no output.
|
|
1077
|
+
if (result && next.output === undefined) {
|
|
1078
|
+
let output: unknown
|
|
1079
|
+
if (Array.isArray(result.content)) {
|
|
1080
|
+
output = result.content
|
|
1081
|
+
} else {
|
|
1082
|
+
try {
|
|
1083
|
+
output = JSON.parse(result.content)
|
|
1084
|
+
} catch {
|
|
1085
|
+
output = result.content
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const errorText =
|
|
1089
|
+
result.state === 'error'
|
|
1090
|
+
? this.extractToolResultError(output)
|
|
1091
|
+
: undefined
|
|
1092
|
+
next = {
|
|
1093
|
+
...next,
|
|
1094
|
+
output: errorText ? { error: errorText } : output,
|
|
1095
|
+
state: result.state === 'error' ? 'error' : 'complete',
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return next
|
|
1100
|
+
})
|
|
1101
|
+
// Rebuild only when a part object identity changed.
|
|
1102
|
+
const partsChanged = parts.some(
|
|
1103
|
+
(part, index) => part !== msg.parts[index],
|
|
1104
|
+
)
|
|
1105
|
+
return partsChanged ? { ...msg, parts } : msg
|
|
1106
|
+
})
|
|
1020
1107
|
}
|
|
1021
1108
|
|
|
1022
1109
|
/**
|
|
@@ -1449,6 +1536,10 @@ export class StreamProcessor {
|
|
|
1449
1536
|
this.finishReason = chunk.finishReason ?? null
|
|
1450
1537
|
this.activeRuns.delete(chunk.runId)
|
|
1451
1538
|
|
|
1539
|
+
if (chunk.outcome?.type === 'interrupt') {
|
|
1540
|
+
this.handleInterrupts(chunk.outcome.interrupts)
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1452
1543
|
if (this.activeRuns.size === 0) {
|
|
1453
1544
|
this.isDone = true
|
|
1454
1545
|
this.completeAllToolCalls()
|
|
@@ -1456,6 +1547,74 @@ export class StreamProcessor {
|
|
|
1456
1547
|
}
|
|
1457
1548
|
}
|
|
1458
1549
|
|
|
1550
|
+
private handleInterrupts(interrupts: Array<Interrupt>): void {
|
|
1551
|
+
for (const interrupt of interrupts) {
|
|
1552
|
+
const metadata =
|
|
1553
|
+
interrupt.metadata && typeof interrupt.metadata === 'object'
|
|
1554
|
+
? interrupt.metadata
|
|
1555
|
+
: {}
|
|
1556
|
+
const kind = typeof metadata.kind === 'string' ? metadata.kind : undefined
|
|
1557
|
+
const toolCallId = interrupt.toolCallId
|
|
1558
|
+
if (!toolCallId) continue
|
|
1559
|
+
|
|
1560
|
+
const toolName =
|
|
1561
|
+
typeof metadata.toolName === 'string'
|
|
1562
|
+
? metadata.toolName
|
|
1563
|
+
: this.findToolCallName(toolCallId)
|
|
1564
|
+
const input = Object.hasOwn(metadata, 'input') ? metadata.input : {}
|
|
1565
|
+
|
|
1566
|
+
if (kind === 'approval' || interrupt.reason === 'approval_required') {
|
|
1567
|
+
// An interrupt terminal arrives after MESSAGES_SNAPSHOT, which may have
|
|
1568
|
+
// rebuilt the message list without the live active-message/tool-call
|
|
1569
|
+
// maps. Fall back to locating the assistant message that actually owns
|
|
1570
|
+
// the tool-call part so the approval UI (part.state) still updates.
|
|
1571
|
+
const resolvedMessageId =
|
|
1572
|
+
this.getActiveAssistantMessageId() ??
|
|
1573
|
+
this.toolCallToMessage.get(toolCallId) ??
|
|
1574
|
+
this.messages.find(
|
|
1575
|
+
(m) =>
|
|
1576
|
+
m.role === 'assistant' &&
|
|
1577
|
+
m.parts.some(
|
|
1578
|
+
(p) => p.type === 'tool-call' && p.id === toolCallId,
|
|
1579
|
+
),
|
|
1580
|
+
)?.id
|
|
1581
|
+
if (resolvedMessageId) {
|
|
1582
|
+
this.messages = updateToolCallApproval(
|
|
1583
|
+
this.messages,
|
|
1584
|
+
resolvedMessageId,
|
|
1585
|
+
toolCallId,
|
|
1586
|
+
interrupt.id,
|
|
1587
|
+
)
|
|
1588
|
+
this.emitMessagesChange()
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
this.events.onApprovalRequest?.({
|
|
1592
|
+
toolCallId,
|
|
1593
|
+
toolName,
|
|
1594
|
+
input,
|
|
1595
|
+
approvalId: interrupt.id,
|
|
1596
|
+
})
|
|
1597
|
+
continue
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
if (kind === 'client_tool' || interrupt.reason === 'client_tool_input') {
|
|
1601
|
+
this.events.onToolCall?.({
|
|
1602
|
+
toolCallId,
|
|
1603
|
+
toolName,
|
|
1604
|
+
input,
|
|
1605
|
+
})
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
private findToolCallName(toolCallId: string): string {
|
|
1611
|
+
for (const state of this.messageStates.values()) {
|
|
1612
|
+
const toolCall = state.toolCalls.get(toolCallId)
|
|
1613
|
+
if (toolCall) return toolCall.name
|
|
1614
|
+
}
|
|
1615
|
+
return ''
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1459
1618
|
/**
|
|
1460
1619
|
* Handle RUN_ERROR event
|
|
1461
1620
|
*/
|
|
@@ -1676,10 +1835,15 @@ export class StreamProcessor {
|
|
|
1676
1835
|
/**
|
|
1677
1836
|
* Handle CUSTOM event.
|
|
1678
1837
|
*
|
|
1679
|
-
* Handles
|
|
1680
|
-
* - 'tool-input-available':
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
1838
|
+
* Handles custom events consumed by the processor:
|
|
1839
|
+
* - 'tool-input-available': Legacy/replay-compatible input for client tool
|
|
1840
|
+
* execution. Fires onToolCall.
|
|
1841
|
+
* - 'approval-requested': Legacy/replay-compatible input for tool approval.
|
|
1842
|
+
* Updates tool-call part state and fires onApprovalRequest.
|
|
1843
|
+
*
|
|
1844
|
+
* Current core streams represent user-actionable waits through
|
|
1845
|
+
* RUN_FINISHED.outcome.type === 'interrupt'; these custom events are not the
|
|
1846
|
+
* source of truth for new emissions.
|
|
1683
1847
|
*
|
|
1684
1848
|
* @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details
|
|
1685
1849
|
*/
|
|
@@ -1910,6 +2074,13 @@ export class StreamProcessor {
|
|
|
1910
2074
|
return
|
|
1911
2075
|
}
|
|
1912
2076
|
|
|
2077
|
+
// RUN_FINISHED interrupt handling can mark the rendered part as waiting on
|
|
2078
|
+
// user action before the completion safety net runs. Keep the parsed
|
|
2079
|
+
// argument bookkeeping above, but do not downgrade that visible state.
|
|
2080
|
+
if (this.isToolCallPartAwaitingUserAction(toolCall.id)) {
|
|
2081
|
+
return
|
|
2082
|
+
}
|
|
2083
|
+
|
|
1913
2084
|
// Update UIMessage. The arguments are complete now, so surface the parsed
|
|
1914
2085
|
// input on the part. For adapters that skip TOOL_CALL_ARGS the arguments
|
|
1915
2086
|
// string was back-filled from TOOL_CALL_END.input, so this parse matches
|
|
@@ -1937,6 +2108,19 @@ export class StreamProcessor {
|
|
|
1937
2108
|
)
|
|
1938
2109
|
}
|
|
1939
2110
|
|
|
2111
|
+
private isToolCallPartAwaitingUserAction(toolCallId: string): boolean {
|
|
2112
|
+
return this.messages.some((msg) =>
|
|
2113
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `parts` is typed as required, but seeded ModelMessage-shaped messages can lack it at runtime.
|
|
2114
|
+
msg.parts?.some(
|
|
2115
|
+
(part) =>
|
|
2116
|
+
part.type === 'tool-call' &&
|
|
2117
|
+
part.id === toolCallId &&
|
|
2118
|
+
(part.state === 'approval-requested' ||
|
|
2119
|
+
part.state === 'approval-responded'),
|
|
2120
|
+
),
|
|
2121
|
+
)
|
|
2122
|
+
}
|
|
2123
|
+
|
|
1940
2124
|
/**
|
|
1941
2125
|
* Whether the rendered tool-call part for the given id has reached the
|
|
1942
2126
|
* terminal 'error' state. Used to prevent the completion safety net from
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalInterruptJson,
|
|
3
|
+
digestInterruptJson,
|
|
4
|
+
} from '../../../interrupt-serialization'
|
|
5
|
+
import { isStandardJSONSchema, isStandardSchema } from './schema-converter'
|
|
6
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
7
|
+
import type { JSONSchema, SchemaInput } from '../../../types'
|
|
8
|
+
import type { ApprovalSchemaConfig } from './tool-definition'
|
|
9
|
+
|
|
10
|
+
export interface NormalizedSchemaInput {
|
|
11
|
+
source: SchemaInput
|
|
12
|
+
validator?: StandardSchemaV1
|
|
13
|
+
jsonSchema?: JSONSchema
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NormalizedApprovalSchema {
|
|
17
|
+
branches: {
|
|
18
|
+
approve: NormalizedSchemaInput | null
|
|
19
|
+
reject: NormalizedSchemaInput | null
|
|
20
|
+
}
|
|
21
|
+
responseSchema: JSONSchema
|
|
22
|
+
responseSchemaHash: string
|
|
23
|
+
approvalSchemaHash: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const jsonSchemaKeywords = new Set([
|
|
27
|
+
'$schema',
|
|
28
|
+
'$id',
|
|
29
|
+
'$ref',
|
|
30
|
+
'$defs',
|
|
31
|
+
'type',
|
|
32
|
+
'properties',
|
|
33
|
+
'required',
|
|
34
|
+
'additionalProperties',
|
|
35
|
+
'items',
|
|
36
|
+
'oneOf',
|
|
37
|
+
'anyOf',
|
|
38
|
+
'allOf',
|
|
39
|
+
'enum',
|
|
40
|
+
'const',
|
|
41
|
+
'format',
|
|
42
|
+
'minimum',
|
|
43
|
+
'maximum',
|
|
44
|
+
'minLength',
|
|
45
|
+
'maxLength',
|
|
46
|
+
'pattern',
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
50
|
+
return (
|
|
51
|
+
value !== null &&
|
|
52
|
+
typeof value === 'object' &&
|
|
53
|
+
!Array.isArray(value) &&
|
|
54
|
+
(Object.getPrototypeOf(value) === Object.prototype ||
|
|
55
|
+
Object.getPrototypeOf(value) === null)
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isRawJsonSchema(value: unknown): value is JSONSchema {
|
|
60
|
+
return (
|
|
61
|
+
isPlainRecord(value) &&
|
|
62
|
+
Object.keys(value).some((key) => jsonSchemaKeywords.has(key))
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isSchemaInput(value: unknown): value is SchemaInput {
|
|
67
|
+
return (
|
|
68
|
+
isStandardSchema(value) ||
|
|
69
|
+
isStandardJSONSchema(value) ||
|
|
70
|
+
isRawJsonSchema(value)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isApprovalBranchMap(
|
|
75
|
+
value: unknown,
|
|
76
|
+
): value is { approve?: SchemaInput; reject?: SchemaInput } {
|
|
77
|
+
if (!isPlainRecord(value)) return false
|
|
78
|
+
const keys = Object.keys(value)
|
|
79
|
+
return (
|
|
80
|
+
keys.length > 0 &&
|
|
81
|
+
keys.every((key) => key === 'approve' || key === 'reject') &&
|
|
82
|
+
keys.every((key) => isSchemaInput(value[key]))
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toJsonSchema(value: Record<string, unknown>): JSONSchema {
|
|
87
|
+
const result: JSONSchema = {}
|
|
88
|
+
for (const [key, item] of Object.entries(value)) {
|
|
89
|
+
result[key] = item
|
|
90
|
+
}
|
|
91
|
+
return result
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function schemaToWire(schema: SchemaInput): NormalizedSchemaInput {
|
|
95
|
+
if (isStandardSchema(schema)) {
|
|
96
|
+
const jsonSchema = isStandardJSONSchema(schema)
|
|
97
|
+
? toJsonSchema(
|
|
98
|
+
schema['~standard'].jsonSchema.input({
|
|
99
|
+
target: 'draft-2020-12',
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
: undefined
|
|
103
|
+
return {
|
|
104
|
+
source: schema,
|
|
105
|
+
validator: schema,
|
|
106
|
+
...(jsonSchema !== undefined && { jsonSchema }),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (isRawJsonSchema(schema)) {
|
|
110
|
+
// The library does not compile or validate raw JSON Schema; it is carried
|
|
111
|
+
// on the wire and hashed as-is. Validation is the application's job.
|
|
112
|
+
return { source: schema, jsonSchema: schema }
|
|
113
|
+
}
|
|
114
|
+
throw new TypeError('Expected a supported SchemaInput.')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function decisionEnvelope(input: {
|
|
118
|
+
approved: boolean
|
|
119
|
+
payload: NormalizedSchemaInput | null
|
|
120
|
+
inputSchema: NormalizedSchemaInput | null
|
|
121
|
+
}): JSONSchema {
|
|
122
|
+
const properties: Record<string, JSONSchema> = {
|
|
123
|
+
approved: { const: input.approved },
|
|
124
|
+
}
|
|
125
|
+
const required = ['approved']
|
|
126
|
+
if (input.approved && input.inputSchema) {
|
|
127
|
+
properties['editedArgs'] = input.inputSchema.jsonSchema ?? {}
|
|
128
|
+
}
|
|
129
|
+
if (input.payload) {
|
|
130
|
+
properties['payload'] = input.payload.jsonSchema ?? {}
|
|
131
|
+
required.push('payload')
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties,
|
|
136
|
+
required,
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function normalizeApprovalSchema(
|
|
142
|
+
approvalSchema: ApprovalSchemaConfig | undefined,
|
|
143
|
+
inputSchema?: SchemaInput,
|
|
144
|
+
): NormalizedApprovalSchema {
|
|
145
|
+
const normalizedInput =
|
|
146
|
+
inputSchema === undefined ? null : schemaToWire(inputSchema)
|
|
147
|
+
let approve: NormalizedSchemaInput | null = null
|
|
148
|
+
let reject: NormalizedSchemaInput | null = null
|
|
149
|
+
|
|
150
|
+
if (approvalSchema !== undefined) {
|
|
151
|
+
if (isStandardSchema(approvalSchema) || isRawJsonSchema(approvalSchema)) {
|
|
152
|
+
approve = schemaToWire(approvalSchema)
|
|
153
|
+
reject = approve
|
|
154
|
+
} else if (isApprovalBranchMap(approvalSchema)) {
|
|
155
|
+
approve =
|
|
156
|
+
approvalSchema.approve === undefined
|
|
157
|
+
? null
|
|
158
|
+
: schemaToWire(approvalSchema.approve)
|
|
159
|
+
reject =
|
|
160
|
+
approvalSchema.reject === undefined
|
|
161
|
+
? null
|
|
162
|
+
: schemaToWire(approvalSchema.reject)
|
|
163
|
+
} else {
|
|
164
|
+
throw new TypeError(
|
|
165
|
+
'approvalSchema must be a SchemaInput or a nonempty map containing approve or reject.',
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const responseSchema: JSONSchema = {
|
|
171
|
+
oneOf: [
|
|
172
|
+
decisionEnvelope({
|
|
173
|
+
approved: true,
|
|
174
|
+
payload: approve,
|
|
175
|
+
inputSchema: normalizedInput,
|
|
176
|
+
}),
|
|
177
|
+
decisionEnvelope({
|
|
178
|
+
approved: false,
|
|
179
|
+
payload: reject,
|
|
180
|
+
inputSchema: null,
|
|
181
|
+
}),
|
|
182
|
+
],
|
|
183
|
+
}
|
|
184
|
+
const responseCanonical = canonicalInterruptJson(responseSchema)
|
|
185
|
+
const approvalCanonical = canonicalInterruptJson({
|
|
186
|
+
approve: approve?.jsonSchema ?? null,
|
|
187
|
+
reject: reject?.jsonSchema ?? null,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
branches: { approve, reject },
|
|
192
|
+
responseSchema,
|
|
193
|
+
responseSchemaHash: digestInterruptJson(responseCanonical),
|
|
194
|
+
approvalSchemaHash: digestInterruptJson(approvalCanonical),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function hashSchemaInput(schema: SchemaInput | undefined): string {
|
|
199
|
+
if (schema === undefined) return digestInterruptJson('undefined')
|
|
200
|
+
const normalized = schemaToWire(schema)
|
|
201
|
+
const identity = normalized.jsonSchema ?? {
|
|
202
|
+
standardValidator: 'unserialized',
|
|
203
|
+
}
|
|
204
|
+
return digestInterruptJson(canonicalInterruptJson(identity))
|
|
205
|
+
}
|