@tanstack/ai 0.45.1 → 0.47.1
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/dist/esm/activities/chat/index.d.ts +36 -11
- package/dist/esm/activities/chat/index.js +462 -66
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +1 -0
- package/dist/esm/activities/chat/messages.js +12 -7
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +7 -2
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -3
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +6 -3
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/generic-interrupts.d.ts +13 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js +8 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +4 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +54 -3
- package/dist/esm/activities/chat/middleware/types.js +16 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -5
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/unique-tool-names.d.ts +20 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js +57 -0
- package/dist/esm/activities/chat/tools/unique-tool-names.js.map +1 -0
- package/dist/esm/adapter-internals.d.ts +7 -0
- package/dist/esm/adapter-internals.js +5 -1
- package/dist/esm/client.d.ts +4 -0
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/generic-interrupt-continuation.d.ts +45 -0
- package/dist/esm/generic-interrupt-continuation.js +80 -0
- package/dist/esm/generic-interrupt-continuation.js.map +1 -0
- package/dist/esm/index.d.ts +9 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/interrupt-definition.d.ts +113 -0
- package/dist/esm/interrupt-definition.js +169 -0
- package/dist/esm/interrupt-definition.js.map +1 -0
- package/dist/esm/interrupt-resume.d.ts +3 -0
- package/dist/esm/interrupt-resume.js +77 -16
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.d.ts +12 -3
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.d.ts +2 -2
- package/dist/esm/middlewares/usage-attributes.js +9 -2
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/stream-to-response.d.ts +26 -0
- package/dist/esm/stream-to-response.js +1 -1
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.d.ts +123 -0
- package/dist/esm/stream-to-websocket.js +249 -0
- package/dist/esm/stream-to-websocket.js.map +1 -0
- package/dist/esm/types.d.ts +19 -10
- package/dist/esm/utilities/chat-params.js +10 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +2 -2
- package/skills/ai-core/media-generation/SKILL.md +13 -9
- package/skills/ai-core/middleware/SKILL.md +53 -44
- package/skills/ai-core/structured-outputs/SKILL.md +59 -55
- package/skills/ai-core/tool-calling/SKILL.md +54 -1
- package/src/activities/chat/index.ts +1076 -194
- package/src/activities/chat/messages.ts +11 -3
- package/src/activities/chat/middleware/builder.ts +29 -4
- package/src/activities/chat/middleware/compose.ts +95 -5
- package/src/activities/chat/middleware/define.ts +13 -3
- package/src/activities/chat/middleware/generic-interrupts.ts +26 -0
- package/src/activities/chat/middleware/index.ts +15 -0
- package/src/activities/chat/middleware/types.ts +127 -2
- package/src/activities/chat/stream/processor.ts +21 -0
- package/src/activities/chat/tools/unique-tool-names.ts +73 -0
- package/src/adapter-internals.ts +24 -0
- package/src/client.ts +20 -0
- package/src/generic-interrupt-continuation.ts +162 -0
- package/src/index.ts +51 -0
- package/src/interrupt-definition.ts +581 -0
- package/src/interrupt-resume.ts +156 -25
- package/src/interrupts.ts +13 -3
- package/src/middlewares/usage-attributes.ts +12 -2
- package/src/stream-to-response.ts +2 -2
- package/src/stream-to-websocket.ts +418 -0
- package/src/types.ts +21 -8
- package/src/utilities/chat-params.ts +16 -3
|
@@ -11,18 +11,22 @@ import { canonicalInterruptJson, digestInterruptJson } from "../../interrupt-ser
|
|
|
11
11
|
import "../../interrupts.js";
|
|
12
12
|
import { convertSchemaForStructuredOutput, convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./tools/schema-converter.js";
|
|
13
13
|
import { hashSchemaInput, normalizeApprovalSchema } from "./tools/approval-schema.js";
|
|
14
|
-
import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readUnopenedInterruptBinding, validateInterruptResumeBatch } from "../../interrupt-resume.js";
|
|
14
|
+
import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInterruptBinding, readUnopenedInterruptBinding, validateInterruptResumeBatch } from "../../interrupt-resume.js";
|
|
15
|
+
import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
|
|
16
|
+
import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
|
|
15
17
|
import { normalizeToolResult } from "../../utilities/tool-result.js";
|
|
16
18
|
import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
|
|
17
19
|
import { LazyToolManager } from "./tools/lazy-tool-manager.js";
|
|
20
|
+
import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
|
|
18
21
|
import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
|
|
19
22
|
import { maxIterations } from "./agent-loop-strategies.js";
|
|
20
|
-
import { convertMessagesToModelMessages, generateMessageId } from "./messages.js";
|
|
23
|
+
import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, safeJsonStringify } from "./messages.js";
|
|
21
24
|
import { MiddlewareRunner } from "./middleware/compose.js";
|
|
22
25
|
import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
|
|
23
26
|
import { provideRunDisconnect } from "./middleware/run-disconnect.js";
|
|
24
27
|
import { validateCapabilities } from "./middleware/validate.js";
|
|
25
28
|
import { MCPManager } from "./mcp/manager.js";
|
|
29
|
+
import { provideGenericInterruptDefinitionRegistry } from "./middleware/generic-interrupts.js";
|
|
26
30
|
import "./adapter.js";
|
|
27
31
|
import { devtoolsMiddleware } from "@tanstack/ai-event-client";
|
|
28
32
|
import { undoNullWidening } from "@tanstack/ai-utils";
|
|
@@ -50,38 +54,11 @@ function structuralInterruptFailure(error) {
|
|
|
50
54
|
};
|
|
51
55
|
}
|
|
52
56
|
function normalizePublicInterruptBinding(value, expectedInterruptId) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
interruptId: binding.interruptId,
|
|
59
|
-
interruptedRunId: binding.interruptedRunId,
|
|
60
|
-
generation: binding.generation,
|
|
61
|
-
responseSchemaHash: binding.responseSchemaHash,
|
|
62
|
-
...typeof binding.expiresAt === "string" ? { expiresAt: binding.expiresAt } : {}
|
|
63
|
-
};
|
|
64
|
-
if (binding.kind === "generic") return {
|
|
65
|
-
kind: binding.kind,
|
|
66
|
-
...base
|
|
67
|
-
};
|
|
68
|
-
if (typeof binding.toolName !== "string" || typeof binding.toolCallId !== "string") return;
|
|
69
|
-
if (binding.kind === "client-tool-execution" && typeof binding.outputSchemaHash === "string") return {
|
|
70
|
-
kind: binding.kind,
|
|
71
|
-
...base,
|
|
72
|
-
toolName: binding.toolName,
|
|
73
|
-
toolCallId: binding.toolCallId,
|
|
74
|
-
outputSchemaHash: binding.outputSchemaHash
|
|
75
|
-
};
|
|
76
|
-
if (binding.kind === "tool-approval" && Object.prototype.hasOwnProperty.call(binding, "originalArgs") && typeof binding.inputSchemaHash === "string" && typeof binding.approvalSchemaHash === "string") return {
|
|
77
|
-
kind: binding.kind,
|
|
78
|
-
...base,
|
|
79
|
-
toolName: binding.toolName,
|
|
80
|
-
toolCallId: binding.toolCallId,
|
|
81
|
-
originalArgs: binding.originalArgs,
|
|
82
|
-
inputSchemaHash: binding.inputSchemaHash,
|
|
83
|
-
approvalSchemaHash: binding.approvalSchemaHash
|
|
84
|
-
};
|
|
57
|
+
return readInterruptBinding({
|
|
58
|
+
id: expectedInterruptId,
|
|
59
|
+
reason: "",
|
|
60
|
+
metadata: { [INTERRUPT_BINDING_METADATA_KEY]: value }
|
|
61
|
+
});
|
|
85
62
|
}
|
|
86
63
|
/**
|
|
87
64
|
* Create typed options for the chat() function without executing.
|
|
@@ -119,12 +96,15 @@ function combineAbortSignals(a, b) {
|
|
|
119
96
|
}
|
|
120
97
|
var TextEngine = class {
|
|
121
98
|
adapter;
|
|
99
|
+
interruptDefinitions;
|
|
122
100
|
params;
|
|
123
101
|
systemPrompts;
|
|
124
102
|
tools;
|
|
125
103
|
loopStrategy;
|
|
126
104
|
toolCallManager;
|
|
127
105
|
lazyToolManager;
|
|
106
|
+
/** A public interruption terminal must always have this run's start event. */
|
|
107
|
+
hasPublicRunStarted = false;
|
|
128
108
|
initialMessageCount;
|
|
129
109
|
requestId;
|
|
130
110
|
streamId;
|
|
@@ -148,11 +128,14 @@ var TextEngine = class {
|
|
|
148
128
|
accumulatedThinking = [];
|
|
149
129
|
currentThinkingContent = "";
|
|
150
130
|
currentThinkingSignature = "";
|
|
131
|
+
hasSeenReasoningEvents = false;
|
|
151
132
|
eventOptions;
|
|
152
133
|
eventToolNames;
|
|
153
134
|
finishedEvent = null;
|
|
154
135
|
streamedToolErrorResults = /* @__PURE__ */ new Map();
|
|
155
136
|
deferredToolCallRunFinishedChunks = [];
|
|
137
|
+
/** The model terminal is held until afterModel can choose an interrupt. */
|
|
138
|
+
deferredModelRunFinishedChunks = [];
|
|
156
139
|
earlyTermination = false;
|
|
157
140
|
toolPhase = "continue";
|
|
158
141
|
cyclePhase = "processText";
|
|
@@ -162,6 +145,8 @@ var TextEngine = class {
|
|
|
162
145
|
resumeClientToolResults = /* @__PURE__ */ new Map();
|
|
163
146
|
resumeDeniedToolResults = /* @__PURE__ */ new Map();
|
|
164
147
|
resumeCancelledToolCallIds = /* @__PURE__ */ new Set();
|
|
148
|
+
resumeGenericInterrupts = /* @__PURE__ */ new Map();
|
|
149
|
+
resumeGenericInterruptRequests = /* @__PURE__ */ new Map();
|
|
165
150
|
threadId;
|
|
166
151
|
runIdOverride;
|
|
167
152
|
parentRunIdOverride;
|
|
@@ -183,6 +168,8 @@ var TextEngine = class {
|
|
|
183
168
|
disconnectListeners = [];
|
|
184
169
|
logger;
|
|
185
170
|
structuredOutputResult = null;
|
|
171
|
+
structuredOutputMessageId = null;
|
|
172
|
+
structuredOutputMessageCreatedAt = null;
|
|
186
173
|
combinedStartEmitted = false;
|
|
187
174
|
combinedStructuredMessageId = null;
|
|
188
175
|
validatedStructuredOutput = void 0;
|
|
@@ -193,6 +180,7 @@ var TextEngine = class {
|
|
|
193
180
|
constructor(config, logger) {
|
|
194
181
|
this.logger = logger;
|
|
195
182
|
this.adapter = config.adapter;
|
|
183
|
+
this.interruptDefinitions = new Map((config.params.interrupts ?? []).map((definition) => [definition.id, definition]));
|
|
196
184
|
this.finalStructuredOutput = config.finalStructuredOutput;
|
|
197
185
|
this.params = config.params;
|
|
198
186
|
this.systemPrompts = config.params.systemPrompts || [];
|
|
@@ -202,6 +190,7 @@ var TextEngine = class {
|
|
|
202
190
|
this.initialApprovals = approvals;
|
|
203
191
|
this.initialClientToolResults = clientToolResults;
|
|
204
192
|
this.messages = convertMessagesToModelMessages(config.params.messages);
|
|
193
|
+
assertUniqueToolNames(config.params.tools || []);
|
|
205
194
|
this.lazyToolManager = new LazyToolManager(config.params.tools || [], this.messages, config.params.lazyToolsConfig);
|
|
206
195
|
this.tools = this.lazyToolManager.getActiveTools();
|
|
207
196
|
this.toolCallManager = new ToolCallManager(this.tools);
|
|
@@ -263,6 +252,7 @@ var TextEngine = class {
|
|
|
263
252
|
this.disconnectListeners.push(listener);
|
|
264
253
|
if (this.disconnected) this.runDisconnectListener(listener);
|
|
265
254
|
} });
|
|
255
|
+
provideGenericInterruptDefinitionRegistry(this.middlewareCtx, { definitions: this.interruptDefinitions });
|
|
266
256
|
provideSandboxRuntime(this.middlewareCtx, {
|
|
267
257
|
logger: this.logger,
|
|
268
258
|
emit: (event) => {
|
|
@@ -321,7 +311,21 @@ var TextEngine = class {
|
|
|
321
311
|
const transformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, initialConfig);
|
|
322
312
|
this.applyMiddlewareConfig(transformedConfig);
|
|
323
313
|
await this.applyEphemeralInterruptResume(transformedConfig);
|
|
314
|
+
await this.applyDurableGenericInterruptResolution();
|
|
324
315
|
await this.middlewareRunner.runOnStart(this.middlewareCtx);
|
|
316
|
+
if (this.earlyTermination) {
|
|
317
|
+
yield* this.emitSuccessfulEarlyTermination();
|
|
318
|
+
if (!this.terminalHookCalled) {
|
|
319
|
+
this.terminalHookCalled = true;
|
|
320
|
+
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
321
|
+
finishReason: this.lastFinishReason,
|
|
322
|
+
duration: Date.now() - this.streamStartTime,
|
|
323
|
+
content: this.accumulatedContent,
|
|
324
|
+
usage: this.finishedEvent?.usage
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
325
329
|
if ((yield* this.checkForPendingToolCalls()) === "wait") return;
|
|
326
330
|
if (!(!!this.finalStructuredOutput && this.tools.length === 0 && this.finalStructuredOutput.nativeCombined !== true)) do {
|
|
327
331
|
if (this.earlyTermination || this.isCancelled()) return;
|
|
@@ -333,7 +337,19 @@ var TextEngine = class {
|
|
|
333
337
|
const iterConfig = this.buildMiddlewareConfig();
|
|
334
338
|
const iterTransformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, iterConfig);
|
|
335
339
|
this.applyMiddlewareConfig(iterTransformedConfig);
|
|
340
|
+
if (yield* this.emitBoundaryInterrupts("beforeModel", this.createSyntheticFinishedEvent())) {
|
|
341
|
+
this.setToolPhase("wait");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
336
344
|
yield* this.streamModelResponse();
|
|
345
|
+
if (yield* this.emitBoundaryInterrupts("afterModel", this.finishedEvent ?? this.createSyntheticFinishedEvent())) {
|
|
346
|
+
this.setToolPhase("wait");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (this.shouldExecuteToolPhase()) {
|
|
350
|
+
this.deferredToolCallRunFinishedChunks.push(...this.deferredModelRunFinishedChunks);
|
|
351
|
+
this.deferredModelRunFinishedChunks = [];
|
|
352
|
+
} else yield* this.flushDeferredModelRunFinishedChunks();
|
|
337
353
|
} else yield* this.processToolCalls();
|
|
338
354
|
this.endCycle();
|
|
339
355
|
} while (await this.shouldContinue());
|
|
@@ -355,6 +371,7 @@ var TextEngine = class {
|
|
|
355
371
|
duration: Date.now() - this.streamStartTime
|
|
356
372
|
});
|
|
357
373
|
} else {
|
|
374
|
+
this.addTerminalAssistantMessages();
|
|
358
375
|
this.terminalHookCalled = true;
|
|
359
376
|
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
360
377
|
finishReason: this.lastFinishReason,
|
|
@@ -458,6 +475,7 @@ var TextEngine = class {
|
|
|
458
475
|
this.accumulatedThinking = [];
|
|
459
476
|
this.currentThinkingContent = "";
|
|
460
477
|
this.currentThinkingSignature = "";
|
|
478
|
+
this.hasSeenReasoningEvents = false;
|
|
461
479
|
this.finishedEvent = null;
|
|
462
480
|
this.streamedToolErrorResults.clear();
|
|
463
481
|
this.middlewareCtx.currentMessageId = this.currentMessageId;
|
|
@@ -508,7 +526,10 @@ var TextEngine = class {
|
|
|
508
526
|
if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
|
|
509
527
|
this.combinedStartEmitted = true;
|
|
510
528
|
const startValue = chunk.value;
|
|
511
|
-
if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string")
|
|
529
|
+
if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") {
|
|
530
|
+
this.combinedStructuredMessageId = startValue.messageId;
|
|
531
|
+
this.captureStructuredOutputMessageIdentity(startValue.messageId);
|
|
532
|
+
}
|
|
512
533
|
}
|
|
513
534
|
let outboundChunk = chunk;
|
|
514
535
|
if (this.finalStructuredOutput?.source === "event" && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
|
|
@@ -521,6 +542,11 @@ var TextEngine = class {
|
|
|
521
542
|
};
|
|
522
543
|
this.combinedCompleteEmitted = true;
|
|
523
544
|
const value = chunk.value;
|
|
545
|
+
const completeMessageId = readCustomEventMessageId(value);
|
|
546
|
+
if (completeMessageId) {
|
|
547
|
+
this.combinedStructuredMessageId = completeMessageId;
|
|
548
|
+
this.captureStructuredOutputMessageIdentity(completeMessageId);
|
|
549
|
+
}
|
|
524
550
|
if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
|
|
525
551
|
...chunk,
|
|
526
552
|
value: {
|
|
@@ -553,10 +579,15 @@ var TextEngine = class {
|
|
|
553
579
|
const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
|
|
554
580
|
for (const outputChunk of outputChunks) {
|
|
555
581
|
if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
|
|
582
|
+
if (outputChunk.type === EventType.RUN_FINISHED) {
|
|
583
|
+
this.deferredModelRunFinishedChunks.push(outputChunk);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
556
586
|
if (this.shouldDeferToolCallRunFinished(outputChunk)) {
|
|
557
587
|
this.deferredToolCallRunFinishedChunks.push(outputChunk);
|
|
558
588
|
continue;
|
|
559
589
|
}
|
|
590
|
+
if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
|
|
560
591
|
this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
|
|
561
592
|
yield outputChunk;
|
|
562
593
|
this.middlewareCtx.chunkIndex++;
|
|
@@ -593,7 +624,10 @@ var TextEngine = class {
|
|
|
593
624
|
case "STEP_STARTED":
|
|
594
625
|
this.handleStepStartedEvent();
|
|
595
626
|
break;
|
|
596
|
-
case "STEP_FINISHED":
|
|
627
|
+
case "STEP_FINISHED":
|
|
628
|
+
this.handleStepFinishedEvent(chunk);
|
|
629
|
+
break;
|
|
630
|
+
case "REASONING_MESSAGE_CONTENT": this.handleReasoningMessageContentEvent(chunk);
|
|
597
631
|
}
|
|
598
632
|
}
|
|
599
633
|
handleTextMessageContentEvent(chunk) {
|
|
@@ -609,6 +643,10 @@ var TextEngine = class {
|
|
|
609
643
|
this.streamIdentityCaptured = true;
|
|
610
644
|
}
|
|
611
645
|
}
|
|
646
|
+
captureStructuredOutputMessageIdentity(messageId) {
|
|
647
|
+
this.structuredOutputMessageId = messageId;
|
|
648
|
+
this.structuredOutputMessageCreatedAt ??= /* @__PURE__ */ new Date();
|
|
649
|
+
}
|
|
612
650
|
handleToolCallStartEvent(chunk) {
|
|
613
651
|
if (typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "") this.captureStreamMessageIdentity(chunk.parentMessageId);
|
|
614
652
|
this.toolCallManager.addToolCallStartEvent(chunk);
|
|
@@ -657,9 +695,19 @@ var TextEngine = class {
|
|
|
657
695
|
this.finalizeCurrentThinkingStep();
|
|
658
696
|
}
|
|
659
697
|
handleStepFinishedEvent(chunk) {
|
|
660
|
-
if (
|
|
698
|
+
if (!this.hasSeenReasoningEvents) {
|
|
699
|
+
if (chunk.delta) this.currentThinkingContent += chunk.delta;
|
|
700
|
+
else if (chunk.content) {
|
|
701
|
+
if (chunk.content.startsWith(this.currentThinkingContent)) this.currentThinkingContent = chunk.content;
|
|
702
|
+
else if (!this.currentThinkingContent.startsWith(chunk.content)) this.currentThinkingContent += chunk.content;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
661
705
|
if (chunk.signature) this.currentThinkingSignature = chunk.signature;
|
|
662
706
|
}
|
|
707
|
+
handleReasoningMessageContentEvent(chunk) {
|
|
708
|
+
this.hasSeenReasoningEvents = true;
|
|
709
|
+
this.currentThinkingContent += chunk.delta;
|
|
710
|
+
}
|
|
663
711
|
/**
|
|
664
712
|
* Tools available for execution this turn. The discovery tool is dropped
|
|
665
713
|
* from the advertised set (`this.tools`) once every lazy tool is discovered,
|
|
@@ -696,6 +744,11 @@ var TextEngine = class {
|
|
|
696
744
|
if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent, argsMap)) yield* this.pipeThroughMiddleware(chunk);
|
|
697
745
|
return "continue";
|
|
698
746
|
}
|
|
747
|
+
this.middlewareCtx.phase = "beforeTools";
|
|
748
|
+
if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executablePendingCalls)) {
|
|
749
|
+
this.setToolPhase("wait");
|
|
750
|
+
return "wait";
|
|
751
|
+
}
|
|
699
752
|
const { approvals, clientToolResults } = this.collectClientState();
|
|
700
753
|
const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
|
|
701
754
|
onBeforeToolCall: async (toolCall, tool, args) => {
|
|
@@ -784,6 +837,10 @@ var TextEngine = class {
|
|
|
784
837
|
return;
|
|
785
838
|
}
|
|
786
839
|
this.middlewareCtx.phase = "beforeTools";
|
|
840
|
+
if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executableToolCalls)) {
|
|
841
|
+
this.setToolPhase("wait");
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
787
844
|
const { approvals, clientToolResults } = this.collectClientState();
|
|
788
845
|
const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
|
|
789
846
|
onBeforeToolCall: async (toolCall, tool, args) => {
|
|
@@ -824,14 +881,22 @@ var TextEngine = class {
|
|
|
824
881
|
needsApproval: executionResult.needsApproval,
|
|
825
882
|
needsClientExecution: executionResult.needsClientExecution
|
|
826
883
|
});
|
|
884
|
+
const afterToolBoundaryChunks = this.buildToolResultChunks(allResults, finishEvent);
|
|
885
|
+
const afterToolRequests = await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
|
|
886
|
+
if (afterToolRequests.length > 0) {
|
|
887
|
+
for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
|
|
888
|
+
yield* this.emitBoundaryInterrupts("afterTools", finishEvent, toolCalls, afterToolRequests);
|
|
889
|
+
this.setToolPhase("wait");
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
827
892
|
if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
|
|
828
|
-
if (allResults.length > 0) for (const chunk of
|
|
893
|
+
if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
|
|
829
894
|
const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
|
|
830
895
|
this.setToolPhase(emitted ? "wait" : "stop");
|
|
831
896
|
return;
|
|
832
897
|
}
|
|
833
898
|
yield* this.flushDeferredToolCallRunFinishedChunks();
|
|
834
|
-
const toolResultChunks =
|
|
899
|
+
const toolResultChunks = afterToolBoundaryChunks;
|
|
835
900
|
for (const chunk of toolResultChunks) yield* this.pipeThroughMiddleware(chunk);
|
|
836
901
|
if (this.lazyToolManager.hasNewlyDiscoveredTools()) {
|
|
837
902
|
this.tools = this.lazyToolManager.getActiveTools();
|
|
@@ -853,6 +918,37 @@ var TextEngine = class {
|
|
|
853
918
|
}
|
|
854
919
|
this.deferredToolCallRunFinishedChunks = [];
|
|
855
920
|
}
|
|
921
|
+
*flushDeferredModelRunFinishedChunks() {
|
|
922
|
+
for (const chunk of this.deferredModelRunFinishedChunks) {
|
|
923
|
+
this.logger.output(`type=${chunk.type}`, { chunk });
|
|
924
|
+
yield chunk;
|
|
925
|
+
this.middlewareCtx.chunkIndex++;
|
|
926
|
+
}
|
|
927
|
+
this.deferredModelRunFinishedChunks = [];
|
|
928
|
+
}
|
|
929
|
+
async *emitSyntheticRunStarted(finishEvent) {
|
|
930
|
+
if (this.hasPublicRunStarted) return;
|
|
931
|
+
yield* this.pipeThroughMiddleware({
|
|
932
|
+
type: EventType.RUN_STARTED,
|
|
933
|
+
runId: finishEvent.runId,
|
|
934
|
+
threadId: finishEvent.threadId,
|
|
935
|
+
model: finishEvent.model,
|
|
936
|
+
timestamp: Date.now()
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
async *emitSuccessfulEarlyTermination() {
|
|
940
|
+
this.lastFinishReason = "stop";
|
|
941
|
+
const finishEvent = {
|
|
942
|
+
...this.createSyntheticFinishedEvent(),
|
|
943
|
+
finishReason: "stop"
|
|
944
|
+
};
|
|
945
|
+
yield* this.emitSyntheticRunStarted(finishEvent);
|
|
946
|
+
yield* this.pipeThroughMiddleware({
|
|
947
|
+
...finishEvent,
|
|
948
|
+
timestamp: Date.now(),
|
|
949
|
+
outcome: { type: "success" }
|
|
950
|
+
});
|
|
951
|
+
}
|
|
856
952
|
discardDeferredToolCallRunFinishedChunks() {
|
|
857
953
|
this.deferredToolCallRunFinishedChunks = [];
|
|
858
954
|
}
|
|
@@ -871,6 +967,63 @@ var TextEngine = class {
|
|
|
871
967
|
}];
|
|
872
968
|
this.middlewareCtx.messages = this.messages;
|
|
873
969
|
}
|
|
970
|
+
addTerminalAssistantMessages() {
|
|
971
|
+
this.finalizeCurrentThinkingStep();
|
|
972
|
+
const structuredResult = this.structuredOutputResult;
|
|
973
|
+
const raw = structuredResult ? structuredResult.rawText || safeJsonStringify(structuredResult.data) : "";
|
|
974
|
+
const structuredOutput = structuredResult ? {
|
|
975
|
+
type: "structured-output",
|
|
976
|
+
status: "complete",
|
|
977
|
+
data: structuredResult.data,
|
|
978
|
+
partial: structuredResult.data,
|
|
979
|
+
raw,
|
|
980
|
+
...structuredResult.reasoning !== void 0 ? { reasoning: structuredResult.reasoning } : {}
|
|
981
|
+
} : void 0;
|
|
982
|
+
const nativeCombined = this.finalStructuredOutput?.nativeCombined === true;
|
|
983
|
+
const eventSourced = this.finalStructuredOutput?.source === "event";
|
|
984
|
+
const structuredId = this.structuredOutputMessageId ?? this.combinedStructuredMessageId ?? this.currentMessageId ?? this.createId("msg");
|
|
985
|
+
const splitStructuredMessage = Boolean(structuredOutput) && (!nativeCombined || eventSourced) && this.currentMessageId != null && structuredId !== this.currentMessageId;
|
|
986
|
+
const messages = [...this.middlewareCtx.messages];
|
|
987
|
+
const existingStructuredIndex = messages.findIndex((message) => message.role === "assistant" && message.id === structuredId);
|
|
988
|
+
const currentTurnAlreadyRecorded = messages.some((message) => message.role === "assistant" && message.id === this.currentMessageId);
|
|
989
|
+
const thinking = this.accumulatedThinking.length > 0 ? this.accumulatedThinking : void 0;
|
|
990
|
+
const startedLength = messages.length;
|
|
991
|
+
if (structuredOutput && existingStructuredIndex >= 0) {
|
|
992
|
+
const existing = messages[existingStructuredIndex];
|
|
993
|
+
if (existing) messages[existingStructuredIndex] = {
|
|
994
|
+
...existing,
|
|
995
|
+
content: raw || existing.content,
|
|
996
|
+
structuredOutput
|
|
997
|
+
};
|
|
998
|
+
} else if (structuredOutput && !splitStructuredMessage) {
|
|
999
|
+
if (!currentTurnAlreadyRecorded) messages.push({
|
|
1000
|
+
role: "assistant",
|
|
1001
|
+
content: this.accumulatedContent || raw || null,
|
|
1002
|
+
id: structuredId,
|
|
1003
|
+
createdAt: this.currentMessageCreatedAt ?? this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
|
|
1004
|
+
structuredOutput,
|
|
1005
|
+
...thinking ? { thinking } : {}
|
|
1006
|
+
});
|
|
1007
|
+
} else {
|
|
1008
|
+
if (!currentTurnAlreadyRecorded && (this.accumulatedContent !== "" || thinking)) messages.push({
|
|
1009
|
+
role: "assistant",
|
|
1010
|
+
content: this.accumulatedContent || null,
|
|
1011
|
+
id: this.currentMessageId ?? this.createId("msg"),
|
|
1012
|
+
createdAt: this.currentMessageCreatedAt ?? /* @__PURE__ */ new Date(),
|
|
1013
|
+
...thinking ? { thinking } : {}
|
|
1014
|
+
});
|
|
1015
|
+
if (structuredOutput) messages.push({
|
|
1016
|
+
role: "assistant",
|
|
1017
|
+
content: raw || null,
|
|
1018
|
+
id: structuredId,
|
|
1019
|
+
createdAt: this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
|
|
1020
|
+
structuredOutput
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
if (messages.length === startedLength && existingStructuredIndex < 0) return;
|
|
1024
|
+
this.messages = messages;
|
|
1025
|
+
this.middlewareCtx.messages = this.messages;
|
|
1026
|
+
}
|
|
874
1027
|
/**
|
|
875
1028
|
* Extract client state (approvals and client tool results) from original messages.
|
|
876
1029
|
* This is called in the constructor BEFORE converting to ModelMessage format,
|
|
@@ -911,7 +1064,10 @@ var TextEngine = class {
|
|
|
911
1064
|
clientToolResults
|
|
912
1065
|
};
|
|
913
1066
|
}
|
|
914
|
-
|
|
1067
|
+
genericInterruptId() {
|
|
1068
|
+
return this.createId("interrupt");
|
|
1069
|
+
}
|
|
1070
|
+
buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
|
|
915
1071
|
const interrupts = [];
|
|
916
1072
|
for (const approval of approvals) {
|
|
917
1073
|
const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
|
|
@@ -965,25 +1121,60 @@ var TextEngine = class {
|
|
|
965
1121
|
}
|
|
966
1122
|
});
|
|
967
1123
|
}
|
|
1124
|
+
for (const [index, request] of genericRequests.entries()) {
|
|
1125
|
+
const batchIndex = interrupts.length;
|
|
1126
|
+
const id = genericInterruptIds[index];
|
|
1127
|
+
if (!id) throw new Error("Generic interrupt id is unavailable.");
|
|
1128
|
+
const preEmission = createInterruptBinding(request, { batchIndex });
|
|
1129
|
+
interrupts.push({
|
|
1130
|
+
id,
|
|
1131
|
+
reason: request.reason,
|
|
1132
|
+
message: request.message,
|
|
1133
|
+
...preEmission.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(preEmission.descriptor.responseSchemaCanonicalJson) } : {},
|
|
1134
|
+
...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
|
|
1135
|
+
metadata: {
|
|
1136
|
+
[interruptBindingMetadataKey]: {
|
|
1137
|
+
v: 1,
|
|
1138
|
+
kind: "generic",
|
|
1139
|
+
interruptId: id,
|
|
1140
|
+
definitionId: preEmission.descriptor.definitionId,
|
|
1141
|
+
key: preEmission.descriptor.key,
|
|
1142
|
+
batchIndex,
|
|
1143
|
+
...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
|
|
1144
|
+
...preEmission.descriptor.payloadSchemaHash ? { payloadSchemaHash: preEmission.descriptor.payloadSchemaHash } : {},
|
|
1145
|
+
...preEmission.descriptor.responseSchemaHash !== void 0 ? { responseSchemaHash: preEmission.descriptor.responseSchemaHash } : {}
|
|
1146
|
+
},
|
|
1147
|
+
...preEmission.payload !== void 0 ? { [INTERRUPT_PAYLOAD_METADATA_KEY]: preEmission.payload } : {}
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1152
|
+
for (const interrupt of interrupts) {
|
|
1153
|
+
if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
|
|
1154
|
+
ids.add(interrupt.id);
|
|
1155
|
+
}
|
|
968
1156
|
return interrupts;
|
|
969
1157
|
}
|
|
970
|
-
buildInterruptFinishedChunk(finishEvent, approvals, clientRequests) {
|
|
1158
|
+
buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
|
|
971
1159
|
return {
|
|
972
1160
|
...finishEvent,
|
|
973
1161
|
timestamp: Date.now(),
|
|
974
1162
|
outcome: {
|
|
975
1163
|
type: "interrupt",
|
|
976
|
-
interrupts: this.buildActionableInterrupts(approvals, clientRequests)
|
|
1164
|
+
interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
|
|
977
1165
|
}
|
|
978
1166
|
};
|
|
979
1167
|
}
|
|
980
1168
|
buildMessagesSnapshotChunk() {
|
|
981
1169
|
const messages = this.messages.map((message, index) => {
|
|
982
1170
|
const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
|
|
1171
|
+
const id = message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`;
|
|
1172
|
+
const parts = message.role === "assistant" && (message.thinking?.length || message.structuredOutput) ? modelMessageToUIMessage(message, id).parts : void 0;
|
|
983
1173
|
return {
|
|
984
|
-
id
|
|
1174
|
+
id,
|
|
985
1175
|
role: message.role,
|
|
986
1176
|
...content !== void 0 ? { content } : {},
|
|
1177
|
+
...parts ? { parts } : {},
|
|
987
1178
|
..."toolCalls" in message && message.toolCalls ? { toolCalls: message.toolCalls } : {},
|
|
988
1179
|
..."toolCallId" in message && message.toolCallId ? { toolCallId: message.toolCallId } : {}
|
|
989
1180
|
};
|
|
@@ -1062,8 +1253,10 @@ var TextEngine = class {
|
|
|
1062
1253
|
};
|
|
1063
1254
|
yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
|
|
1064
1255
|
}
|
|
1065
|
-
async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests) {
|
|
1066
|
-
|
|
1256
|
+
async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
|
|
1257
|
+
yield* this.emitSyntheticRunStarted(finishEvent);
|
|
1258
|
+
const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
|
|
1259
|
+
const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
|
|
1067
1260
|
let terminalOutputs;
|
|
1068
1261
|
try {
|
|
1069
1262
|
terminalOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal);
|
|
@@ -1084,6 +1277,59 @@ var TextEngine = class {
|
|
|
1084
1277
|
}
|
|
1085
1278
|
return true;
|
|
1086
1279
|
}
|
|
1280
|
+
async *emitBoundaryInterrupts(phase, finishEvent, toolCalls = [], requests) {
|
|
1281
|
+
this.middlewareCtx.phase = phase;
|
|
1282
|
+
const boundaryRequests = requests ?? await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
|
|
1283
|
+
if (boundaryRequests.length === 0) return false;
|
|
1284
|
+
for (const request of boundaryRequests) if (this.interruptDefinitions.get(request.definition.id) !== request.definition) throw new Error(`Generic interrupt definition ${request.definition.id} is not registered on this chat.`);
|
|
1285
|
+
if (phase === "afterModel") {
|
|
1286
|
+
if (this.toolCallManager.hasToolCalls()) this.addAssistantToolCallMessage(this.toolCallManager.getToolCalls());
|
|
1287
|
+
else this.addAssistantTextMessageForInterrupt();
|
|
1288
|
+
}
|
|
1289
|
+
const actionable = this.getBoundaryActionableToolRequests(toolCalls);
|
|
1290
|
+
yield* this.emitActionableInterruptBoundary(finishEvent, actionable.approvals, actionable.clientRequests, boundaryRequests);
|
|
1291
|
+
return true;
|
|
1292
|
+
}
|
|
1293
|
+
addAssistantTextMessageForInterrupt() {
|
|
1294
|
+
if (this.accumulatedContent.length === 0) return;
|
|
1295
|
+
this.messages = [...this.messages, {
|
|
1296
|
+
role: "assistant",
|
|
1297
|
+
content: this.accumulatedContent
|
|
1298
|
+
}];
|
|
1299
|
+
this.middlewareCtx.messages = this.messages;
|
|
1300
|
+
}
|
|
1301
|
+
getBoundaryActionableToolRequests(toolCalls) {
|
|
1302
|
+
const { approvals, clientToolResults } = this.collectClientState();
|
|
1303
|
+
const approvalRequests = [];
|
|
1304
|
+
const clientRequests = [];
|
|
1305
|
+
for (const toolCall of toolCalls) {
|
|
1306
|
+
const tool = this.resolveExecutableTools([toolCall]).find((candidate) => candidate.name === toolCall.function.name);
|
|
1307
|
+
if (!tool) continue;
|
|
1308
|
+
let input = {};
|
|
1309
|
+
try {
|
|
1310
|
+
const parsed = JSON.parse(toolCall.function.arguments.trim() || "{}");
|
|
1311
|
+
input = parsed && typeof parsed === "object" ? parsed : {};
|
|
1312
|
+
} catch {
|
|
1313
|
+
input = {};
|
|
1314
|
+
}
|
|
1315
|
+
const approvalId = `approval_${toolCall.id}`;
|
|
1316
|
+
if (tool.needsApproval && !approvals.has(approvalId)) approvalRequests.push({
|
|
1317
|
+
toolCallId: toolCall.id,
|
|
1318
|
+
toolName: toolCall.function.name,
|
|
1319
|
+
input,
|
|
1320
|
+
approvalId
|
|
1321
|
+
});
|
|
1322
|
+
else if (!tool.execute && !clientToolResults.has(toolCall.id) && !this.resumeCancelledToolCallIds.has(toolCall.id)) clientRequests.push({
|
|
1323
|
+
toolCallId: toolCall.id,
|
|
1324
|
+
toolName: toolCall.function.name,
|
|
1325
|
+
input
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
approvals: approvalRequests,
|
|
1330
|
+
clientRequests
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1087
1333
|
completeEphemeralInterruptBindings(chunk) {
|
|
1088
1334
|
if (chunk.type !== EventType.RUN_FINISHED || chunk.outcome?.type !== "interrupt") return chunk;
|
|
1089
1335
|
const interruptedRunId = this.runIdOverride ?? this.requestId;
|
|
@@ -1230,7 +1476,7 @@ var TextEngine = class {
|
|
|
1230
1476
|
createSyntheticFinishedEvent() {
|
|
1231
1477
|
return {
|
|
1232
1478
|
type: "RUN_FINISHED",
|
|
1233
|
-
runId: this.
|
|
1479
|
+
runId: this.runIdOverride ?? this.requestId,
|
|
1234
1480
|
threadId: this.threadId,
|
|
1235
1481
|
model: this.params.model,
|
|
1236
1482
|
timestamp: Date.now(),
|
|
@@ -1416,6 +1662,7 @@ var TextEngine = class {
|
|
|
1416
1662
|
const buildSynthesizedStart = (timestamp = Date.now()) => {
|
|
1417
1663
|
const idForStart = structuredMessageId ?? generateMessageId();
|
|
1418
1664
|
structuredMessageId = idForStart;
|
|
1665
|
+
this.captureStructuredOutputMessageIdentity(idForStart);
|
|
1419
1666
|
return {
|
|
1420
1667
|
type: EventType.CUSTOM,
|
|
1421
1668
|
name: "structured-output.start",
|
|
@@ -1433,7 +1680,10 @@ var TextEngine = class {
|
|
|
1433
1680
|
if (!startEmitted && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") startEmitted = true;
|
|
1434
1681
|
if (!structuredMessageId) {
|
|
1435
1682
|
const extracted = extractMessageId(chunk);
|
|
1436
|
-
if (extracted)
|
|
1683
|
+
if (extracted) {
|
|
1684
|
+
structuredMessageId = extracted;
|
|
1685
|
+
this.captureStructuredOutputMessageIdentity(extracted);
|
|
1686
|
+
}
|
|
1437
1687
|
}
|
|
1438
1688
|
if (this.finalStructuredOutput.yieldChunks) {
|
|
1439
1689
|
if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
|
|
@@ -1460,7 +1710,8 @@ var TextEngine = class {
|
|
|
1460
1710
|
const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
|
|
1461
1711
|
this.structuredOutputResult = {
|
|
1462
1712
|
data: object,
|
|
1463
|
-
rawText: parsed.raw
|
|
1713
|
+
rawText: parsed.raw,
|
|
1714
|
+
...parsed.reasoning !== void 0 ? { reasoning: parsed.reasoning } : {}
|
|
1464
1715
|
};
|
|
1465
1716
|
const value = chunk.value;
|
|
1466
1717
|
if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
|
|
@@ -1717,6 +1968,7 @@ var TextEngine = class {
|
|
|
1717
1968
|
input: toolInputs.get(toolCall.id) ?? {}
|
|
1718
1969
|
});
|
|
1719
1970
|
}
|
|
1971
|
+
const genericPending = this.getGenericContinuationPending(interruptedRunId);
|
|
1720
1972
|
const pending = this.buildActionableInterrupts(approvalRequests, clientRequests).flatMap((descriptor) => {
|
|
1721
1973
|
const unopened = readUnopenedInterruptBinding(descriptor);
|
|
1722
1974
|
return unopened ? [{
|
|
@@ -1729,6 +1981,7 @@ var TextEngine = class {
|
|
|
1729
1981
|
}
|
|
1730
1982
|
}] : [];
|
|
1731
1983
|
});
|
|
1984
|
+
pending.push(...genericPending);
|
|
1732
1985
|
const validated = await validateInterruptResumeBatch({
|
|
1733
1986
|
threadId: this.threadId,
|
|
1734
1987
|
interruptedRunId,
|
|
@@ -1744,17 +1997,130 @@ var TextEngine = class {
|
|
|
1744
1997
|
...validated.resumeToolState,
|
|
1745
1998
|
approvals
|
|
1746
1999
|
});
|
|
2000
|
+
const genericResolutions = validated.resumeToolState.genericInterrupts;
|
|
2001
|
+
if (genericPending.length > 0 && genericResolutions) {
|
|
2002
|
+
const resolutions = genericPending.sort((left, right) => {
|
|
2003
|
+
return (left.binding.kind === "generic" ? left.binding.batchIndex ?? 0 : 0) - (right.binding.kind === "generic" ? right.binding.batchIndex ?? 0 : 0);
|
|
2004
|
+
}).flatMap((record) => {
|
|
2005
|
+
const resolution = genericResolutions.get(record.interruptId);
|
|
2006
|
+
if (!resolution || !record.genericRequest) return [];
|
|
2007
|
+
return [resolution.status === "resolved" ? {
|
|
2008
|
+
request: record.genericRequest,
|
|
2009
|
+
status: "resolved",
|
|
2010
|
+
response: resolution.payload
|
|
2011
|
+
} : {
|
|
2012
|
+
request: record.genericRequest,
|
|
2013
|
+
status: "cancelled"
|
|
2014
|
+
}];
|
|
2015
|
+
});
|
|
2016
|
+
const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
|
|
2017
|
+
for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
|
|
2018
|
+
all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
|
|
2019
|
+
});
|
|
2020
|
+
if (policy.toolResume === "stop") this.earlyTermination = true;
|
|
2021
|
+
else if (policy.toolResume === "cancel") for (const request of pendingToolCalls) this.resumeCancelledToolCallIds.add(request.id);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
getGenericContinuationPending(interruptedRunId) {
|
|
2025
|
+
const fail = (message) => {
|
|
2026
|
+
throw new InterruptResumeValidationError([{
|
|
2027
|
+
scope: "batch",
|
|
2028
|
+
threadId: this.threadId,
|
|
2029
|
+
interruptedRunId,
|
|
2030
|
+
generation: 0,
|
|
2031
|
+
interruptIds: [],
|
|
2032
|
+
code: "stale",
|
|
2033
|
+
message,
|
|
2034
|
+
source: "server",
|
|
2035
|
+
retryable: false
|
|
2036
|
+
}]);
|
|
2037
|
+
};
|
|
2038
|
+
const pending = [];
|
|
2039
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2040
|
+
const batchIndexes = /* @__PURE__ */ new Set();
|
|
2041
|
+
for (const resumeItem of this.params.resume ?? []) {
|
|
2042
|
+
const parsed = readGenericInterruptContinuation(resumeItem.metadata);
|
|
2043
|
+
if (parsed.status === "absent") continue;
|
|
2044
|
+
if (parsed.status === "invalid") return fail(parsed.message);
|
|
2045
|
+
const entry = parsed.value;
|
|
2046
|
+
const id = resumeItem.interruptId;
|
|
2047
|
+
const definition = this.interruptDefinitions.get(entry.definitionId);
|
|
2048
|
+
if (!definition) return fail(`Generic interrupt definition ${entry.definitionId} is unavailable.`);
|
|
2049
|
+
if (ids.has(id) || batchIndexes.has(entry.batchIndex)) return fail("Generic interrupt continuation contains duplicate entries.");
|
|
2050
|
+
ids.add(id);
|
|
2051
|
+
batchIndexes.add(entry.batchIndex);
|
|
2052
|
+
let request;
|
|
2053
|
+
try {
|
|
2054
|
+
request = rehydrateInterruptRequest(definition, {
|
|
2055
|
+
key: entry.key,
|
|
2056
|
+
reason: entry.reason,
|
|
2057
|
+
message: entry.message,
|
|
2058
|
+
...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
|
|
2059
|
+
...Object.prototype.hasOwnProperty.call(entry, "payload") ? { payload: entry.payload } : {}
|
|
2060
|
+
});
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
return fail(`Generic interrupt continuation ${id} is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
2063
|
+
}
|
|
2064
|
+
const emitted = createInterruptBinding(request, { batchIndex: entry.batchIndex });
|
|
2065
|
+
if (entry.responseSchemaHash !== emitted.descriptor.responseSchemaHash || entry.payloadSchemaHash !== emitted.descriptor.payloadSchemaHash) return fail(`Generic interrupt continuation ${id} does not match its definition.`);
|
|
2066
|
+
pending.push({
|
|
2067
|
+
interruptId: id,
|
|
2068
|
+
payload: {
|
|
2069
|
+
id,
|
|
2070
|
+
...emitted.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(emitted.descriptor.responseSchemaCanonicalJson) } : {}
|
|
2071
|
+
},
|
|
2072
|
+
binding: {
|
|
2073
|
+
v: 1,
|
|
2074
|
+
kind: "generic",
|
|
2075
|
+
interruptId: id,
|
|
2076
|
+
interruptedRunId,
|
|
2077
|
+
generation: 0,
|
|
2078
|
+
definitionId: entry.definitionId,
|
|
2079
|
+
key: entry.key,
|
|
2080
|
+
batchIndex: entry.batchIndex,
|
|
2081
|
+
...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
|
|
2082
|
+
...emitted.descriptor.payloadSchemaHash ? { payloadSchemaHash: emitted.descriptor.payloadSchemaHash } : {},
|
|
2083
|
+
...entry.responseSchemaHash !== void 0 ? { responseSchemaHash: entry.responseSchemaHash } : {}
|
|
2084
|
+
},
|
|
2085
|
+
genericRequest: request
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
return pending;
|
|
1747
2089
|
}
|
|
1748
2090
|
applyResumeToolState(state) {
|
|
1749
2091
|
if (state?.approvals) for (const [approvalId, resolution] of state.approvals) this.resumeApprovals.set(approvalId, resolution);
|
|
1750
2092
|
if (state?.clientToolResults) for (const [toolCallId, result] of state.clientToolResults) this.resumeClientToolResults.set(toolCallId, result);
|
|
1751
2093
|
if (state?.deniedToolResults) for (const [toolCallId, result] of state.deniedToolResults) this.resumeDeniedToolResults.set(toolCallId, result);
|
|
1752
2094
|
if (state?.cancelledToolCallIds) for (const toolCallId of state.cancelledToolCallIds) this.resumeCancelledToolCallIds.add(toolCallId);
|
|
2095
|
+
if (state?.genericInterrupts) for (const [interruptId, resolution] of state.genericInterrupts) this.resumeGenericInterrupts.set(interruptId, resolution);
|
|
2096
|
+
if (state?.genericInterruptRequests) for (const [interruptId, request] of state.genericInterruptRequests) this.resumeGenericInterruptRequests.set(interruptId, request);
|
|
2097
|
+
}
|
|
2098
|
+
async applyDurableGenericInterruptResolution() {
|
|
2099
|
+
if (this.resumeGenericInterruptRequests.size === 0) return;
|
|
2100
|
+
const resolutions = [...this.resumeGenericInterruptRequests.entries()].flatMap(([interruptId, request]) => {
|
|
2101
|
+
const resolution = this.resumeGenericInterrupts.get(interruptId);
|
|
2102
|
+
if (!resolution) return [];
|
|
2103
|
+
return [resolution.status === "resolved" ? {
|
|
2104
|
+
request,
|
|
2105
|
+
status: "resolved",
|
|
2106
|
+
response: resolution.payload
|
|
2107
|
+
} : {
|
|
2108
|
+
request,
|
|
2109
|
+
status: "cancelled"
|
|
2110
|
+
}];
|
|
2111
|
+
});
|
|
2112
|
+
const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
|
|
2113
|
+
for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
|
|
2114
|
+
all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
|
|
2115
|
+
});
|
|
2116
|
+
if (policy.toolResume === "stop") this.earlyTermination = true;
|
|
2117
|
+
else if (policy.toolResume === "cancel") for (const toolCall of this.getPendingToolCallsFromMessages()) this.resumeCancelledToolCallIds.add(toolCall.id);
|
|
1753
2118
|
}
|
|
1754
2119
|
applyMiddlewareConfig(config) {
|
|
1755
2120
|
this.applyResumeToolState(config.resumeToolState);
|
|
1756
2121
|
this.messages = config.messages;
|
|
1757
2122
|
this.systemPrompts = config.systemPrompts;
|
|
2123
|
+
assertUniqueToolNames(config.tools);
|
|
1758
2124
|
this.tools = config.tools;
|
|
1759
2125
|
this.params = {
|
|
1760
2126
|
...this.params,
|
|
@@ -1777,6 +2143,7 @@ var TextEngine = class {
|
|
|
1777
2143
|
async *pipeThroughMiddleware(chunk) {
|
|
1778
2144
|
const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
|
|
1779
2145
|
for (const outputChunk of outputChunks) {
|
|
2146
|
+
if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
|
|
1780
2147
|
yield outputChunk;
|
|
1781
2148
|
this.middlewareCtx.chunkIndex++;
|
|
1782
2149
|
}
|
|
@@ -1878,27 +2245,47 @@ var TextEngine = class {
|
|
|
1878
2245
|
* ```
|
|
1879
2246
|
*/
|
|
1880
2247
|
function chat(options) {
|
|
1881
|
-
|
|
2248
|
+
validateInterruptDefinitions(options.interrupts);
|
|
2249
|
+
validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
|
|
2250
|
+
if (options.tools) assertUniqueToolNames(options.tools);
|
|
1882
2251
|
const { outputSchema, stream } = options;
|
|
1883
|
-
if (outputSchema && stream === true) return runStreamingStructuredOutput({
|
|
1884
|
-
...options,
|
|
2252
|
+
if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
|
|
1885
2253
|
outputSchema,
|
|
1886
|
-
stream
|
|
1887
|
-
});
|
|
1888
|
-
if (outputSchema) return runAgenticStructuredOutput({
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
});
|
|
1892
|
-
if (stream === false) return runNonStreamingText({
|
|
1893
|
-
...options,
|
|
2254
|
+
stream: true
|
|
2255
|
+
}));
|
|
2256
|
+
if (outputSchema) return runAgenticStructuredOutput(toRuntimeTextActivityOptions(options, {
|
|
2257
|
+
outputSchema,
|
|
2258
|
+
stream: false
|
|
2259
|
+
}));
|
|
2260
|
+
if (stream === false) return runNonStreamingText(toRuntimeTextActivityOptions(options, {
|
|
1894
2261
|
outputSchema: void 0,
|
|
1895
|
-
stream
|
|
1896
|
-
});
|
|
1897
|
-
return runStreamingText({
|
|
1898
|
-
...options,
|
|
2262
|
+
stream: false
|
|
2263
|
+
}));
|
|
2264
|
+
return runStreamingText(toRuntimeTextActivityOptions(options, {
|
|
1899
2265
|
outputSchema: void 0,
|
|
1900
|
-
stream
|
|
1901
|
-
});
|
|
2266
|
+
stream: true
|
|
2267
|
+
}));
|
|
2268
|
+
}
|
|
2269
|
+
function readRuntimeMiddleware(middleware) {
|
|
2270
|
+
if (middleware === void 0) return void 0;
|
|
2271
|
+
if (!Array.isArray(middleware)) throw new TypeError("Chat middleware must be an array.");
|
|
2272
|
+
return middleware;
|
|
2273
|
+
}
|
|
2274
|
+
function toRuntimeTextActivityOptions(options, overrides) {
|
|
2275
|
+
const { middleware, ...rest } = options;
|
|
2276
|
+
return {
|
|
2277
|
+
...rest,
|
|
2278
|
+
...overrides,
|
|
2279
|
+
...middleware === void 0 ? {} : { middleware: readRuntimeMiddleware(middleware) }
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
function validateInterruptDefinitions(definitions) {
|
|
2283
|
+
if (!definitions) return;
|
|
2284
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2285
|
+
for (const definition of definitions) {
|
|
2286
|
+
if (seen.has(definition.id)) throw new Error(`Duplicate interrupt definition id: ${definition.id}`);
|
|
2287
|
+
seen.add(definition.id);
|
|
2288
|
+
}
|
|
1902
2289
|
}
|
|
1903
2290
|
/**
|
|
1904
2291
|
* Publish both delivery-side seams for `stream`.
|
|
@@ -1959,7 +2346,10 @@ async function* streamTextChunks(options, engineRef) {
|
|
|
1959
2346
|
* Runs the full agentic loop (if tools are provided) but returns collected text.
|
|
1960
2347
|
*/
|
|
1961
2348
|
function runNonStreamingText(options) {
|
|
1962
|
-
const stream = runStreamingText(
|
|
2349
|
+
const stream = runStreamingText({
|
|
2350
|
+
...options,
|
|
2351
|
+
stream: true
|
|
2352
|
+
});
|
|
1963
2353
|
return streamToText(stream);
|
|
1964
2354
|
}
|
|
1965
2355
|
/**
|
|
@@ -2027,6 +2417,12 @@ async function runAgenticStructuredOutput(options) {
|
|
|
2027
2417
|
* Uses an `unknown`-input runtime check rather than `as` casts so the engine
|
|
2028
2418
|
* stays cast-free in its hot path.
|
|
2029
2419
|
*/
|
|
2420
|
+
function readCustomEventMessageId(value) {
|
|
2421
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2422
|
+
if (!("messageId" in value)) return void 0;
|
|
2423
|
+
const messageId = value.messageId;
|
|
2424
|
+
return typeof messageId === "string" && messageId !== "" ? messageId : void 0;
|
|
2425
|
+
}
|
|
2030
2426
|
function readStructuredOutputCompleteValue(value) {
|
|
2031
2427
|
if (typeof value !== "object" || value === null) return null;
|
|
2032
2428
|
if (!("object" in value) || !("raw" in value)) return null;
|