@bolt-foundry/gambit-core 0.8.5-rc.9 → 0.8.5
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 +6 -5
- package/cards/context.card.md +4 -4
- package/decks/anthropic/agent-sdk/PROMPT.md +9 -0
- package/decks/openai/codex-sdk/PROMPT.md +9 -0
- package/decks/openai/codex-sdk/codex_client.ts +109 -0
- package/decks/openai/codex-sdk/codex_sdk_bridge.deck.ts +36 -0
- package/esm/cards/context.card.md +9 -0
- package/esm/cards/end.card.md +10 -0
- package/esm/cards/generate-test-input.card.md +12 -0
- package/esm/cards/respond.card.md +10 -0
- package/esm/decks/anthropic/agent-sdk/PROMPT.md +9 -0
- package/esm/decks/openai/codex-sdk/PROMPT.md +9 -0
- package/esm/decks/openai/codex-sdk/codex_client.ts +109 -0
- package/esm/decks/openai/codex-sdk/codex_sdk_bridge.deck.ts +36 -0
- package/esm/mod.d.ts +1 -1
- package/esm/mod.d.ts.map +1 -1
- package/esm/schemas/graders/contexts/conversation.ts +40 -0
- package/esm/schemas/graders/contexts/conversation.zod.ts +1 -0
- package/esm/schemas/graders/contexts/conversation_tools.ts +63 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.ts +1 -0
- package/esm/schemas/graders/contexts/tools.ts +5 -0
- package/esm/schemas/graders/contexts/tools.zod.ts +1 -0
- package/esm/schemas/graders/contexts/turn.ts +17 -0
- package/esm/schemas/graders/contexts/turn.zod.ts +1 -0
- package/esm/schemas/graders/contexts/turn_tools.ts +63 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.ts +1 -0
- package/esm/schemas/graders/grader_output.ts +15 -0
- package/esm/schemas/graders/grader_output.zod.ts +1 -0
- package/esm/schemas/graders/respond.ts +19 -0
- package/esm/schemas/graders/respond.zod.ts +1 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.ts +6 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.ts +1 -0
- package/esm/schemas/scenarios/plain_chat_output.ts +5 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.ts +1 -0
- package/esm/snippets/context.md +8 -0
- package/esm/snippets/end.md +10 -0
- package/esm/snippets/generate-test-input.md +12 -0
- package/esm/snippets/init.md +12 -0
- package/esm/snippets/respond.md +10 -0
- package/esm/snippets/scenario-participant.md +10 -0
- package/esm/src/constants.d.ts +0 -1
- package/esm/src/constants.d.ts.map +1 -1
- package/esm/src/constants.js +0 -4
- package/esm/src/loader.d.ts.map +1 -1
- package/esm/src/loader.js +101 -0
- package/esm/src/markdown.d.ts.map +1 -1
- package/esm/src/markdown.js +109 -9
- package/esm/src/runtime.d.ts +16 -1
- package/esm/src/runtime.d.ts.map +1 -1
- package/esm/src/runtime.js +1607 -311
- package/esm/src/types.d.ts +25 -1
- package/esm/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/script/cards/context.card.md +9 -0
- package/script/cards/end.card.md +10 -0
- package/script/cards/generate-test-input.card.md +12 -0
- package/script/cards/respond.card.md +10 -0
- package/script/decks/anthropic/agent-sdk/PROMPT.md +9 -0
- package/script/decks/openai/codex-sdk/PROMPT.md +9 -0
- package/script/decks/openai/codex-sdk/codex_client.ts +109 -0
- package/script/decks/openai/codex-sdk/codex_sdk_bridge.deck.ts +36 -0
- package/script/mod.d.ts +1 -1
- package/script/mod.d.ts.map +1 -1
- package/script/schemas/graders/contexts/conversation.ts +40 -0
- package/script/schemas/graders/contexts/conversation.zod.ts +1 -0
- package/script/schemas/graders/contexts/conversation_tools.ts +63 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.ts +1 -0
- package/script/schemas/graders/contexts/tools.ts +5 -0
- package/script/schemas/graders/contexts/tools.zod.ts +1 -0
- package/script/schemas/graders/contexts/turn.ts +17 -0
- package/script/schemas/graders/contexts/turn.zod.ts +1 -0
- package/script/schemas/graders/contexts/turn_tools.ts +63 -0
- package/script/schemas/graders/contexts/turn_tools.zod.ts +1 -0
- package/script/schemas/graders/grader_output.ts +15 -0
- package/script/schemas/graders/grader_output.zod.ts +1 -0
- package/script/schemas/graders/respond.ts +19 -0
- package/script/schemas/graders/respond.zod.ts +1 -0
- package/script/schemas/scenarios/plain_chat_input_optional.ts +6 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.ts +1 -0
- package/script/schemas/scenarios/plain_chat_output.ts +5 -0
- package/script/schemas/scenarios/plain_chat_output.zod.ts +1 -0
- package/script/snippets/context.md +8 -0
- package/script/snippets/end.md +10 -0
- package/script/snippets/generate-test-input.md +12 -0
- package/script/snippets/init.md +12 -0
- package/script/snippets/respond.md +10 -0
- package/script/snippets/scenario-participant.md +10 -0
- package/script/src/constants.d.ts +0 -1
- package/script/src/constants.d.ts.map +1 -1
- package/script/src/constants.js +1 -5
- package/script/src/loader.d.ts.map +1 -1
- package/script/src/loader.js +101 -0
- package/script/src/markdown.d.ts.map +1 -1
- package/script/src/markdown.js +109 -9
- package/script/src/runtime.d.ts +16 -1
- package/script/src/runtime.d.ts.map +1 -1
- package/script/src/runtime.js +1606 -310
- package/script/src/types.d.ts +25 -1
- package/script/src/types.d.ts.map +1 -1
- package/snippets/context.md +8 -0
- package/snippets/end.md +10 -0
- package/snippets/generate-test-input.md +12 -0
- package/snippets/init.md +12 -0
- package/snippets/respond.md +10 -0
- package/snippets/scenario-participant.md +10 -0
package/esm/src/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as dntShim from "../_dnt.shims.js";
|
|
2
2
|
import * as path from "../deps/jsr.io/@std/path/1.1.4/mod.js";
|
|
3
|
-
import { DEFAULT_GUARDRAILS, DEFAULT_STATUS_DELAY_MS,
|
|
3
|
+
import { DEFAULT_GUARDRAILS, DEFAULT_STATUS_DELAY_MS, GAMBIT_TOOL_CONTEXT, GAMBIT_TOOL_END, GAMBIT_TOOL_INIT, GAMBIT_TOOL_RESPOND, } from "./constants.js";
|
|
4
4
|
import { loadDeck } from "./loader.js";
|
|
5
5
|
import { canReadPath, canRunCommand, canRunPath, canWritePath, intersectPermissions, resolveEffectivePermissions, } from "./permissions.js";
|
|
6
6
|
import { ExecToolUnsupportedHostError, executeBuiltinCommand, } from "./runtime_exec_host.js";
|
|
@@ -21,6 +21,7 @@ const WORKER_SANDBOX_ENV = "GAMBIT_DECK_WORKER_SANDBOX";
|
|
|
21
21
|
const WORKER_TIMEOUT_MESSAGE = "Timeout exceeded";
|
|
22
22
|
const RUN_CANCELED_MESSAGE = "Run canceled";
|
|
23
23
|
const WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE = "workerSandbox is unsupported when `signal` is provided.";
|
|
24
|
+
const INTERMEDIATE_OUTPUT_DISALLOWED_CODE = "intermediate_output_disallowed";
|
|
24
25
|
const INSPECT_WORKER_TIMEOUT_MS = 1_500;
|
|
25
26
|
const INSPECT_WORKER_TIMEOUT_MESSAGE = "Deck inspection timed out";
|
|
26
27
|
const BUILTIN_TOOL_READ_FILE = "read_file";
|
|
@@ -28,12 +29,21 @@ const BUILTIN_TOOL_LIST_DIR = "list_dir";
|
|
|
28
29
|
const BUILTIN_TOOL_GREP_FILES = "grep_files";
|
|
29
30
|
const BUILTIN_TOOL_APPLY_PATCH = "apply_patch";
|
|
30
31
|
const BUILTIN_TOOL_EXEC = "exec";
|
|
32
|
+
const BUILTIN_TOOL_EMIT_OUTPUT_ITEM = "gambit_emit_output_item";
|
|
33
|
+
const BUILTIN_TOOL_CONSUME_ASYNC_ACTION = "gambit_consume_async_action";
|
|
34
|
+
const ASYNC_ACTION_JOBS_META_KEY = "gambit_async_action_jobs_v1";
|
|
35
|
+
const ASYNC_ACTION_JOBS_META_VERSION = 1;
|
|
36
|
+
const ASYNC_ACTION_JOB_INTERRUPTED_CODE = "async_action_interrupted";
|
|
37
|
+
const ASYNC_ACTION_JOB_MAX_WAIT_MS = 5000;
|
|
38
|
+
const ASYNC_ACTION_JOB_MAX_EVENTS = 512;
|
|
31
39
|
const BUILTIN_TOOL_NAMES = new Set([
|
|
32
40
|
BUILTIN_TOOL_READ_FILE,
|
|
33
41
|
BUILTIN_TOOL_LIST_DIR,
|
|
34
42
|
BUILTIN_TOOL_GREP_FILES,
|
|
35
43
|
BUILTIN_TOOL_APPLY_PATCH,
|
|
36
44
|
BUILTIN_TOOL_EXEC,
|
|
45
|
+
BUILTIN_TOOL_EMIT_OUTPUT_ITEM,
|
|
46
|
+
BUILTIN_TOOL_CONSUME_ASYNC_ACTION,
|
|
37
47
|
]);
|
|
38
48
|
const TRUSTED_SCHEMA_IMPORT_PREFIXES = [
|
|
39
49
|
"@bolt-foundry/gambit-core/schemas",
|
|
@@ -166,7 +176,10 @@ function normalizePermissionBaseDir(set, baseDir) {
|
|
|
166
176
|
};
|
|
167
177
|
}
|
|
168
178
|
function deadlineForRun(guardrails, existing) {
|
|
169
|
-
const
|
|
179
|
+
const timeoutMs = guardrails.timeoutMs === 0
|
|
180
|
+
? Number.POSITIVE_INFINITY
|
|
181
|
+
: guardrails.timeoutMs;
|
|
182
|
+
const timeoutDeadline = performance.now() + timeoutMs;
|
|
170
183
|
if (typeof existing === "number" && Number.isFinite(existing)) {
|
|
171
184
|
return Math.min(existing, timeoutDeadline);
|
|
172
185
|
}
|
|
@@ -193,6 +206,22 @@ function ensureRunActive(deadlineMs, signal) {
|
|
|
193
206
|
throwIfCanceled(signal);
|
|
194
207
|
ensureNotExpired(deadlineMs);
|
|
195
208
|
}
|
|
209
|
+
function mergeAbortSignals(signalA, signalB) {
|
|
210
|
+
if (!signalA)
|
|
211
|
+
return signalB;
|
|
212
|
+
if (!signalB)
|
|
213
|
+
return signalA;
|
|
214
|
+
if (signalA.aborted || signalB.aborted) {
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
controller.abort();
|
|
217
|
+
return controller.signal;
|
|
218
|
+
}
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
const abort = () => controller.abort();
|
|
221
|
+
signalA.addEventListener("abort", abort, { once: true });
|
|
222
|
+
signalB.addEventListener("abort", abort, { once: true });
|
|
223
|
+
return controller.signal;
|
|
224
|
+
}
|
|
196
225
|
function isTrustedSchemaImportKey(key) {
|
|
197
226
|
const normalized = key.trim();
|
|
198
227
|
if (!normalized)
|
|
@@ -329,7 +358,7 @@ export async function runDeck(opts) {
|
|
|
329
358
|
if (workerSandboxRequested && !isWorkerSandboxHostSupported()) {
|
|
330
359
|
throw new WorkerSandboxUnsupportedHostError();
|
|
331
360
|
}
|
|
332
|
-
if (workerSandboxRequested && opts.signal) {
|
|
361
|
+
if (workerSandboxRequested && opts.signal && inferredRoot) {
|
|
333
362
|
throw new WorkerSandboxSignalUnsupportedError();
|
|
334
363
|
}
|
|
335
364
|
const workerSandbox = workerSandboxRequested;
|
|
@@ -425,6 +454,9 @@ export async function runDeck(opts) {
|
|
|
425
454
|
responsesMode: opts.responsesMode,
|
|
426
455
|
permissions: permissions.effective,
|
|
427
456
|
permissionsTrace: permissions.trace,
|
|
457
|
+
intermediateOutputAllow: opts.intermediateOutputAllow,
|
|
458
|
+
onIntermediateOutputItem: opts.onIntermediateOutputItem,
|
|
459
|
+
intermediateOutputErrorContext: opts.intermediateOutputErrorContext,
|
|
428
460
|
workspacePermissions: opts.workspacePermissions,
|
|
429
461
|
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
430
462
|
sessionPermissions: opts.sessionPermissions,
|
|
@@ -456,6 +488,9 @@ export async function runDeck(opts) {
|
|
|
456
488
|
responsesMode: opts.responsesMode,
|
|
457
489
|
permissions: permissions.effective,
|
|
458
490
|
permissionsTrace: permissions.trace,
|
|
491
|
+
intermediateOutputAllow: opts.intermediateOutputAllow,
|
|
492
|
+
onIntermediateOutputItem: opts.onIntermediateOutputItem,
|
|
493
|
+
intermediateOutputErrorContext: opts.intermediateOutputErrorContext,
|
|
459
494
|
workspacePermissions: opts.workspacePermissions,
|
|
460
495
|
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
461
496
|
sessionPermissions: opts.sessionPermissions,
|
|
@@ -501,6 +536,10 @@ export async function runDeck(opts) {
|
|
|
501
536
|
};
|
|
502
537
|
const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
|
|
503
538
|
ensureRunActive(runDeadlineMs, opts.signal);
|
|
539
|
+
const hasModelParams = Boolean(deck.modelParams?.model || deck.modelParams?.temperature !== undefined);
|
|
540
|
+
if (hasModelParams) {
|
|
541
|
+
assertLegacySyntheticResponseToolsRemoved(deck);
|
|
542
|
+
}
|
|
504
543
|
ensureSchemaPresence(deck, isRoot);
|
|
505
544
|
const resolvedInput = resolveInput({
|
|
506
545
|
deck,
|
|
@@ -514,7 +553,7 @@ export async function runDeck(opts) {
|
|
|
514
553
|
!opts.inOrchestrationWorker &&
|
|
515
554
|
isRoot &&
|
|
516
555
|
!opts.onTool &&
|
|
517
|
-
|
|
556
|
+
hasModelParams;
|
|
518
557
|
if (useOrchestrationWorker) {
|
|
519
558
|
return await runLlmDeckInWorker({
|
|
520
559
|
deckPath: deck.path,
|
|
@@ -536,6 +575,9 @@ export async function runDeck(opts) {
|
|
|
536
575
|
responsesMode: opts.responsesMode,
|
|
537
576
|
permissions: permissions.effective,
|
|
538
577
|
permissionsTrace: permissions.trace,
|
|
578
|
+
intermediateOutputAllow: opts.intermediateOutputAllow,
|
|
579
|
+
onIntermediateOutputItem: opts.onIntermediateOutputItem,
|
|
580
|
+
intermediateOutputErrorContext: opts.intermediateOutputErrorContext,
|
|
539
581
|
workspacePermissions: opts.workspacePermissions,
|
|
540
582
|
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
541
583
|
sessionPermissions: opts.sessionPermissions,
|
|
@@ -558,7 +600,7 @@ export async function runDeck(opts) {
|
|
|
558
600
|
permissions: permissions.trace,
|
|
559
601
|
});
|
|
560
602
|
}
|
|
561
|
-
if (
|
|
603
|
+
if (hasModelParams) {
|
|
562
604
|
return await runLlmDeck({
|
|
563
605
|
deck,
|
|
564
606
|
guardrails: effectiveGuardrails,
|
|
@@ -579,6 +621,9 @@ export async function runDeck(opts) {
|
|
|
579
621
|
responsesMode: opts.responsesMode,
|
|
580
622
|
permissions: permissions.effective,
|
|
581
623
|
permissionsTrace: permissions.trace,
|
|
624
|
+
intermediateOutputAllow: opts.intermediateOutputAllow,
|
|
625
|
+
onIntermediateOutputItem: opts.onIntermediateOutputItem,
|
|
626
|
+
intermediateOutputErrorContext: opts.intermediateOutputErrorContext,
|
|
582
627
|
workspacePermissions: opts.workspacePermissions,
|
|
583
628
|
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
584
629
|
sessionPermissions: opts.sessionPermissions,
|
|
@@ -611,6 +656,9 @@ export async function runDeck(opts) {
|
|
|
611
656
|
responsesMode: opts.responsesMode,
|
|
612
657
|
permissions: permissions.effective,
|
|
613
658
|
permissionsTrace: permissions.trace,
|
|
659
|
+
intermediateOutputAllow: opts.intermediateOutputAllow,
|
|
660
|
+
onIntermediateOutputItem: opts.onIntermediateOutputItem,
|
|
661
|
+
intermediateOutputErrorContext: opts.intermediateOutputErrorContext,
|
|
614
662
|
workspacePermissions: opts.workspacePermissions,
|
|
615
663
|
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
616
664
|
sessionPermissions: opts.sessionPermissions,
|
|
@@ -686,10 +734,75 @@ function resolveContextSchema(deck) {
|
|
|
686
734
|
function resolveResponseSchema(deck) {
|
|
687
735
|
return deck.responseSchema ?? deck.outputSchema;
|
|
688
736
|
}
|
|
737
|
+
function toResponseTextConfig(deck) {
|
|
738
|
+
const responseSchema = resolveResponseSchema(deck);
|
|
739
|
+
if (!responseSchema)
|
|
740
|
+
return undefined;
|
|
741
|
+
const schema = toJsonSchema(responseSchema);
|
|
742
|
+
if (!isStructuredResponseSchema(schema))
|
|
743
|
+
return undefined;
|
|
744
|
+
return {
|
|
745
|
+
format: {
|
|
746
|
+
type: "json_schema",
|
|
747
|
+
name: responseSchemaName(deck),
|
|
748
|
+
schema,
|
|
749
|
+
strict: true,
|
|
750
|
+
},
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function isStructuredResponseSchema(schema) {
|
|
754
|
+
const type = schema.type;
|
|
755
|
+
if (typeof type === "string") {
|
|
756
|
+
return type !== "string";
|
|
757
|
+
}
|
|
758
|
+
if (Array.isArray(type)) {
|
|
759
|
+
return !type.every((entry) => entry === "string");
|
|
760
|
+
}
|
|
761
|
+
const properties = schema.properties;
|
|
762
|
+
if (properties && typeof properties === "object" && !Array.isArray(properties)) {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
function responseSchemaName(deck) {
|
|
768
|
+
const raw = deck.label ??
|
|
769
|
+
path.basename(deck.path, path.extname(deck.path)) ??
|
|
770
|
+
"gambit_response";
|
|
771
|
+
const normalized = raw.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64);
|
|
772
|
+
return normalized || "gambit_response";
|
|
773
|
+
}
|
|
774
|
+
const REMOVED_LEGACY_RESPONSE_TOOL_NAMES = new Set([
|
|
775
|
+
GAMBIT_TOOL_RESPOND,
|
|
776
|
+
GAMBIT_TOOL_END,
|
|
777
|
+
]);
|
|
778
|
+
const LEGACY_RESPONSE_TOOL_MIGRATION_GUIDANCE = [
|
|
779
|
+
`Legacy synthetic response tools (${Array.from(REMOVED_LEGACY_RESPONSE_TOOL_NAMES).join(", ")}) are removed.`,
|
|
780
|
+
"Remove `respond`/`allowEnd` from deck metadata and remove any `gambit://respond`, `gambit://end`, `gambit://snippets/respond.md`, or `gambit://snippets/end.md` embeds.",
|
|
781
|
+
"Return normal assistant output that matches `responseSchema` instead of synthetic completion tools.",
|
|
782
|
+
].join(" ");
|
|
783
|
+
function legacyResponseToolMigrationError(details) {
|
|
784
|
+
return new Error(`[gambit] ${details} ${LEGACY_RESPONSE_TOOL_MIGRATION_GUIDANCE}`);
|
|
785
|
+
}
|
|
786
|
+
function syntheticContextToolInvocationError(deckPath) {
|
|
787
|
+
return new Error(`[gambit] Model emitted synthetic context tool for deck ${deckPath}. gambit_context is injected automatically when context input is provided. Do not call gambit_context or gambit_init from the model.`);
|
|
788
|
+
}
|
|
789
|
+
function assertLegacySyntheticResponseToolsRemoved(deck) {
|
|
790
|
+
if (!deck.respond && !deck.allowEnd)
|
|
791
|
+
return;
|
|
792
|
+
const enabled = [];
|
|
793
|
+
if (deck.respond)
|
|
794
|
+
enabled.push("respond");
|
|
795
|
+
if (deck.allowEnd)
|
|
796
|
+
enabled.push("allowEnd");
|
|
797
|
+
throw legacyResponseToolMigrationError(`Deck ${deck.path} enables removed synthetic response controls (${enabled.join(", ")}).`);
|
|
798
|
+
}
|
|
689
799
|
function isContextToolName(name) {
|
|
690
800
|
return name === GAMBIT_TOOL_CONTEXT || name === GAMBIT_TOOL_INIT;
|
|
691
801
|
}
|
|
692
802
|
function ensureSchemaPresence(deck, isRoot) {
|
|
803
|
+
for (const extension of deck.responseItemExtensions ?? []) {
|
|
804
|
+
assertZodSchema(extension.dataSchema, `responseItemExtensions["${extension.type}"].dataSchema`);
|
|
805
|
+
}
|
|
693
806
|
if (!isRoot) {
|
|
694
807
|
const contextSchema = resolveContextSchema(deck);
|
|
695
808
|
const responseSchema = resolveResponseSchema(deck);
|
|
@@ -861,6 +974,132 @@ function responseItemsFromMessages(messages) {
|
|
|
861
974
|
}
|
|
862
975
|
return items;
|
|
863
976
|
}
|
|
977
|
+
const CORE_RESPONSE_ITEM_TYPES = new Set([
|
|
978
|
+
"message",
|
|
979
|
+
"function_call",
|
|
980
|
+
"function_call_output",
|
|
981
|
+
"reasoning",
|
|
982
|
+
]);
|
|
983
|
+
function isCoreResponseItemType(type) {
|
|
984
|
+
return CORE_RESPONSE_ITEM_TYPES.has(type);
|
|
985
|
+
}
|
|
986
|
+
function isResponseExtensionItem(item) {
|
|
987
|
+
return item.type.includes(":");
|
|
988
|
+
}
|
|
989
|
+
function canonicalizeJsonValue(value) {
|
|
990
|
+
if (value === null)
|
|
991
|
+
return null;
|
|
992
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
993
|
+
return value;
|
|
994
|
+
if (typeof value === "number") {
|
|
995
|
+
return Number.isFinite(value) ? value : String(value);
|
|
996
|
+
}
|
|
997
|
+
if (Array.isArray(value)) {
|
|
998
|
+
return value.map((entry) => canonicalizeJsonValue(entry));
|
|
999
|
+
}
|
|
1000
|
+
if (value && typeof value === "object") {
|
|
1001
|
+
const record = value;
|
|
1002
|
+
const sortedKeys = Object.keys(record).sort((a, b) => a.localeCompare(b));
|
|
1003
|
+
const out = {};
|
|
1004
|
+
for (const key of sortedKeys) {
|
|
1005
|
+
const next = record[key];
|
|
1006
|
+
if (next === undefined)
|
|
1007
|
+
continue;
|
|
1008
|
+
out[key] = canonicalizeJsonValue(next);
|
|
1009
|
+
}
|
|
1010
|
+
return out;
|
|
1011
|
+
}
|
|
1012
|
+
return String(value);
|
|
1013
|
+
}
|
|
1014
|
+
function createResponseItemEmissionValidator(deck) {
|
|
1015
|
+
const schemaByType = new Map();
|
|
1016
|
+
for (const extension of deck.responseItemExtensions ?? []) {
|
|
1017
|
+
schemaByType.set(extension.type, extension.dataSchema);
|
|
1018
|
+
}
|
|
1019
|
+
return (item, source) => {
|
|
1020
|
+
const type = item.type;
|
|
1021
|
+
if (isCoreResponseItemType(type))
|
|
1022
|
+
return item;
|
|
1023
|
+
if (!type.includes(":")) {
|
|
1024
|
+
throw new Error(`[gambit] Deck ${deck.path} emitted undeclared non-namespaced response extension item type "${type}" (${source}).`);
|
|
1025
|
+
}
|
|
1026
|
+
const schema = schemaByType.get(type);
|
|
1027
|
+
if (!schema) {
|
|
1028
|
+
throw new Error(`[gambit] Deck ${deck.path} emitted undeclared response extension item type "${type}" (${source}). Declare responseItemExtensions with dataSchema.`);
|
|
1029
|
+
}
|
|
1030
|
+
const asRecord = item;
|
|
1031
|
+
if (!Object.hasOwn(asRecord, "data")) {
|
|
1032
|
+
throw new Error(`[gambit] Deck ${deck.path} emitted extension item "${type}" without data payload (${source}).`);
|
|
1033
|
+
}
|
|
1034
|
+
const data = validateWithSchema(schema, asRecord.data);
|
|
1035
|
+
return {
|
|
1036
|
+
type: type,
|
|
1037
|
+
id: typeof asRecord.id === "string" ? asRecord.id : undefined,
|
|
1038
|
+
data: canonicalizeJsonValue(data),
|
|
1039
|
+
};
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function validateResponseOutputItems(items, validateItem, source) {
|
|
1043
|
+
return items.map((item, index) => validateItem(item, `${source}[${index}]`));
|
|
1044
|
+
}
|
|
1045
|
+
function validateResponseEventItems(event, validateItem) {
|
|
1046
|
+
if (event.type === "response.output_item.added" ||
|
|
1047
|
+
event.type === "response.output_item.done") {
|
|
1048
|
+
return {
|
|
1049
|
+
...event,
|
|
1050
|
+
item: validateItem(event.item, `${event.type}[${event.output_index}]`),
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
if (event.type === "response.completed" || event.type === "response.created") {
|
|
1054
|
+
return {
|
|
1055
|
+
...event,
|
|
1056
|
+
response: {
|
|
1057
|
+
...event.response,
|
|
1058
|
+
output: validateResponseOutputItems(event.response.output ?? [], validateItem, `${event.type}.response.output`),
|
|
1059
|
+
},
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return event;
|
|
1063
|
+
}
|
|
1064
|
+
function isSupplementalResponseItem(item) {
|
|
1065
|
+
return item.type === "reasoning" || !isCoreResponseItemType(item.type);
|
|
1066
|
+
}
|
|
1067
|
+
function supplementalResponseItemSignature(item) {
|
|
1068
|
+
if (item.type === "reasoning") {
|
|
1069
|
+
const content = (item.content ?? []).map((part) => `${part.type}:${part.text}`).join("|");
|
|
1070
|
+
const summary = item.summary.map((part) => `${part.type}:${part.text}`)
|
|
1071
|
+
.join("|");
|
|
1072
|
+
return [
|
|
1073
|
+
"reasoning",
|
|
1074
|
+
item.id ?? "",
|
|
1075
|
+
content,
|
|
1076
|
+
summary,
|
|
1077
|
+
item.encrypted_content ?? "",
|
|
1078
|
+
].join(":");
|
|
1079
|
+
}
|
|
1080
|
+
if (isResponseExtensionItem(item)) {
|
|
1081
|
+
return [
|
|
1082
|
+
item.type,
|
|
1083
|
+
item.id ?? "",
|
|
1084
|
+
JSON.stringify(item.data),
|
|
1085
|
+
].join(":");
|
|
1086
|
+
}
|
|
1087
|
+
return "";
|
|
1088
|
+
}
|
|
1089
|
+
function mergeSupplementalResponseItems(prior, latest) {
|
|
1090
|
+
const out = [];
|
|
1091
|
+
const seen = new Set();
|
|
1092
|
+
for (const item of [...prior, ...latest]) {
|
|
1093
|
+
if (!isSupplementalResponseItem(item))
|
|
1094
|
+
continue;
|
|
1095
|
+
const signature = supplementalResponseItemSignature(item);
|
|
1096
|
+
if (!signature || seen.has(signature))
|
|
1097
|
+
continue;
|
|
1098
|
+
seen.add(signature);
|
|
1099
|
+
out.push(item);
|
|
1100
|
+
}
|
|
1101
|
+
return out;
|
|
1102
|
+
}
|
|
864
1103
|
function safeJsonArgs(value) {
|
|
865
1104
|
if (!value)
|
|
866
1105
|
return {};
|
|
@@ -882,13 +1121,62 @@ function asToolKind(value, fallback) {
|
|
|
882
1121
|
}
|
|
883
1122
|
return fallback;
|
|
884
1123
|
}
|
|
1124
|
+
function normalizeStreamToolEventType(type) {
|
|
1125
|
+
if (type === "tool.call" || type === "gambit:tool.call")
|
|
1126
|
+
return "tool.call";
|
|
1127
|
+
if (type === "tool.result" || type === "gambit:tool.result") {
|
|
1128
|
+
return "tool.result";
|
|
1129
|
+
}
|
|
1130
|
+
return undefined;
|
|
1131
|
+
}
|
|
1132
|
+
function isTerminalResponseEventType(type) {
|
|
1133
|
+
return type === "response.completed" || type === "response.failed";
|
|
1134
|
+
}
|
|
1135
|
+
function normalizeSequenceNumber(value) {
|
|
1136
|
+
if (typeof value !== "number")
|
|
1137
|
+
return undefined;
|
|
1138
|
+
if (!Number.isInteger(value) || value < 0)
|
|
1139
|
+
return undefined;
|
|
1140
|
+
return value;
|
|
1141
|
+
}
|
|
1142
|
+
function createCanonicalStreamEventController() {
|
|
1143
|
+
let nextSequenceNumber = 0;
|
|
1144
|
+
let terminalStateReached = false;
|
|
1145
|
+
return (streamEvent) => {
|
|
1146
|
+
const type = typeof streamEvent.type === "string" ? streamEvent.type : "";
|
|
1147
|
+
if (!type)
|
|
1148
|
+
return streamEvent;
|
|
1149
|
+
const isResponseEvent = type.startsWith("response.");
|
|
1150
|
+
const isToolEvent = normalizeStreamToolEventType(type) !== undefined;
|
|
1151
|
+
if (!isResponseEvent && !isToolEvent)
|
|
1152
|
+
return streamEvent;
|
|
1153
|
+
if (terminalStateReached)
|
|
1154
|
+
return null;
|
|
1155
|
+
if (!isResponseEvent)
|
|
1156
|
+
return streamEvent;
|
|
1157
|
+
const nextEvent = { ...streamEvent, type };
|
|
1158
|
+
const existingSequenceNumber = normalizeSequenceNumber(streamEvent.sequence_number);
|
|
1159
|
+
if (existingSequenceNumber === undefined) {
|
|
1160
|
+
nextSequenceNumber += 1;
|
|
1161
|
+
nextEvent.sequence_number = nextSequenceNumber;
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
nextSequenceNumber = Math.max(nextSequenceNumber, existingSequenceNumber);
|
|
1165
|
+
}
|
|
1166
|
+
if (isTerminalResponseEventType(type)) {
|
|
1167
|
+
terminalStateReached = true;
|
|
1168
|
+
}
|
|
1169
|
+
return nextEvent;
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
885
1172
|
function projectStreamToolTraceEvents(input) {
|
|
886
1173
|
if (!input.trace)
|
|
887
1174
|
return;
|
|
888
|
-
const
|
|
1175
|
+
const rawType = typeof input.streamEvent.type === "string"
|
|
889
1176
|
? input.streamEvent.type
|
|
890
1177
|
: "";
|
|
891
|
-
|
|
1178
|
+
const type = normalizeStreamToolEventType(rawType);
|
|
1179
|
+
if (!type)
|
|
892
1180
|
return;
|
|
893
1181
|
const actionCallId = typeof input.streamEvent.actionCallId === "string"
|
|
894
1182
|
? input.streamEvent.actionCallId
|
|
@@ -962,6 +1250,682 @@ function traceOpenResponsesStreamEvent(input) {
|
|
|
962
1250
|
});
|
|
963
1251
|
return true;
|
|
964
1252
|
}
|
|
1253
|
+
function createIntermediateOutputDisallowedError(input) {
|
|
1254
|
+
const action = typeof input.context?.actionName === "string" &&
|
|
1255
|
+
input.context.actionName.trim().length > 0
|
|
1256
|
+
? `action "${input.context.actionName.trim()}"`
|
|
1257
|
+
: "action";
|
|
1258
|
+
const deckSuffix = typeof input.context?.parentDeckPath === "string" &&
|
|
1259
|
+
input.context.parentDeckPath.trim().length > 0
|
|
1260
|
+
? ` in ${input.context.parentDeckPath.trim()}`
|
|
1261
|
+
: "";
|
|
1262
|
+
const error = new Error(`[gambit] ${action}${deckSuffix} disallows intermediate output emission (${input.source}). Set action.intermediateOutput.emit = "allow" to opt in.`);
|
|
1263
|
+
error.code = INTERMEDIATE_OUTPUT_DISALLOWED_CODE;
|
|
1264
|
+
return error;
|
|
1265
|
+
}
|
|
1266
|
+
function createIntermediateChildResponseEmitter(input) {
|
|
1267
|
+
const responseId = `${input.actionCallId}:intermediate`;
|
|
1268
|
+
const canonicalizeStreamEvent = createCanonicalStreamEventController();
|
|
1269
|
+
const emittedItems = [];
|
|
1270
|
+
let nextOutputIndex = 0;
|
|
1271
|
+
let created = false;
|
|
1272
|
+
let terminal = false;
|
|
1273
|
+
const emitStreamEvent = (event) => {
|
|
1274
|
+
const canonical = canonicalizeStreamEvent(event);
|
|
1275
|
+
if (!canonical)
|
|
1276
|
+
return;
|
|
1277
|
+
traceOpenResponsesStreamEvent({
|
|
1278
|
+
streamEvent: canonical,
|
|
1279
|
+
runId: input.runId,
|
|
1280
|
+
actionCallId: input.actionCallId,
|
|
1281
|
+
deckPath: input.deckPath,
|
|
1282
|
+
parentActionCallId: input.parentActionCallId,
|
|
1283
|
+
trace: input.trace,
|
|
1284
|
+
});
|
|
1285
|
+
};
|
|
1286
|
+
const ensureCreated = () => {
|
|
1287
|
+
if (created)
|
|
1288
|
+
return;
|
|
1289
|
+
created = true;
|
|
1290
|
+
emitStreamEvent({
|
|
1291
|
+
type: "response.created",
|
|
1292
|
+
response: {
|
|
1293
|
+
id: responseId,
|
|
1294
|
+
object: "response",
|
|
1295
|
+
status: "in_progress",
|
|
1296
|
+
output: [],
|
|
1297
|
+
},
|
|
1298
|
+
});
|
|
1299
|
+
};
|
|
1300
|
+
return {
|
|
1301
|
+
emitOutputItem: (item, source) => {
|
|
1302
|
+
input.ensureActive();
|
|
1303
|
+
if (input.allowEmission === false) {
|
|
1304
|
+
throw createIntermediateOutputDisallowedError({
|
|
1305
|
+
source,
|
|
1306
|
+
context: input.errorContext,
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
if (terminal)
|
|
1310
|
+
return;
|
|
1311
|
+
const validated = input.validateItem(item, source);
|
|
1312
|
+
ensureCreated();
|
|
1313
|
+
const outputIndex = nextOutputIndex++;
|
|
1314
|
+
emittedItems.push(validated);
|
|
1315
|
+
emitStreamEvent({
|
|
1316
|
+
type: "response.output_item.added",
|
|
1317
|
+
response_id: responseId,
|
|
1318
|
+
output_index: outputIndex,
|
|
1319
|
+
item: validated,
|
|
1320
|
+
});
|
|
1321
|
+
emitStreamEvent({
|
|
1322
|
+
type: "response.output_item.done",
|
|
1323
|
+
response_id: responseId,
|
|
1324
|
+
output_index: outputIndex,
|
|
1325
|
+
item: validated,
|
|
1326
|
+
});
|
|
1327
|
+
input.onEmit?.({
|
|
1328
|
+
responseId,
|
|
1329
|
+
actionCallId: input.actionCallId,
|
|
1330
|
+
parentActionCallId: input.parentActionCallId,
|
|
1331
|
+
deckPath: input.deckPath,
|
|
1332
|
+
outputIndex,
|
|
1333
|
+
item: validated,
|
|
1334
|
+
});
|
|
1335
|
+
},
|
|
1336
|
+
complete: () => {
|
|
1337
|
+
input.ensureActive();
|
|
1338
|
+
if (terminal || !created) {
|
|
1339
|
+
terminal = true;
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
emitStreamEvent({
|
|
1343
|
+
type: "response.completed",
|
|
1344
|
+
response: {
|
|
1345
|
+
id: responseId,
|
|
1346
|
+
object: "response",
|
|
1347
|
+
status: "completed",
|
|
1348
|
+
output: emittedItems,
|
|
1349
|
+
},
|
|
1350
|
+
});
|
|
1351
|
+
terminal = true;
|
|
1352
|
+
},
|
|
1353
|
+
fail: (err) => {
|
|
1354
|
+
if (terminal || !created) {
|
|
1355
|
+
terminal = true;
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const code = typeof err?.code === "string"
|
|
1359
|
+
? err.code
|
|
1360
|
+
: undefined;
|
|
1361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1362
|
+
emitStreamEvent({
|
|
1363
|
+
type: "response.failed",
|
|
1364
|
+
response_id: responseId,
|
|
1365
|
+
error: {
|
|
1366
|
+
...(code ? { code } : {}),
|
|
1367
|
+
message,
|
|
1368
|
+
},
|
|
1369
|
+
});
|
|
1370
|
+
terminal = true;
|
|
1371
|
+
},
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function parseIntermediateOutputEmissionFromTraceEvent(input) {
|
|
1375
|
+
if (input.event.type !== "response.output_item.done")
|
|
1376
|
+
return undefined;
|
|
1377
|
+
const eventRecord = input.event;
|
|
1378
|
+
const responseId = typeof eventRecord.response_id === "string"
|
|
1379
|
+
? eventRecord.response_id
|
|
1380
|
+
: undefined;
|
|
1381
|
+
if (!responseId || !responseId.endsWith(":intermediate"))
|
|
1382
|
+
return undefined;
|
|
1383
|
+
const outputIndex = eventRecord.output_index;
|
|
1384
|
+
if (!Number.isInteger(outputIndex) || Number(outputIndex) < 0) {
|
|
1385
|
+
return undefined;
|
|
1386
|
+
}
|
|
1387
|
+
const item = eventRecord.item;
|
|
1388
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
1389
|
+
return undefined;
|
|
1390
|
+
}
|
|
1391
|
+
const gambitMeta = eventRecord._gambit && typeof eventRecord._gambit ===
|
|
1392
|
+
"object" &&
|
|
1393
|
+
!Array.isArray(eventRecord._gambit)
|
|
1394
|
+
? eventRecord._gambit
|
|
1395
|
+
: {};
|
|
1396
|
+
const actionCallId = typeof gambitMeta.action_call_id === "string"
|
|
1397
|
+
? gambitMeta.action_call_id
|
|
1398
|
+
: undefined;
|
|
1399
|
+
if (!actionCallId)
|
|
1400
|
+
return undefined;
|
|
1401
|
+
const parentActionCallId = typeof gambitMeta.parent_action_call_id ===
|
|
1402
|
+
"string"
|
|
1403
|
+
? gambitMeta.parent_action_call_id
|
|
1404
|
+
: undefined;
|
|
1405
|
+
if (input.expectedParentActionCallId !== undefined &&
|
|
1406
|
+
parentActionCallId !== input.expectedParentActionCallId) {
|
|
1407
|
+
return undefined;
|
|
1408
|
+
}
|
|
1409
|
+
const deckPath = typeof gambitMeta.deck_path === "string"
|
|
1410
|
+
? gambitMeta.deck_path
|
|
1411
|
+
: "";
|
|
1412
|
+
if (!deckPath)
|
|
1413
|
+
return undefined;
|
|
1414
|
+
return {
|
|
1415
|
+
responseId,
|
|
1416
|
+
actionCallId,
|
|
1417
|
+
parentActionCallId,
|
|
1418
|
+
deckPath,
|
|
1419
|
+
outputIndex: Number(outputIndex),
|
|
1420
|
+
item: item,
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
function cloneAsyncActionTerminalResult(input) {
|
|
1424
|
+
return {
|
|
1425
|
+
state: input.state,
|
|
1426
|
+
status: input.status,
|
|
1427
|
+
payload: input.payload,
|
|
1428
|
+
message: input.message,
|
|
1429
|
+
code: input.code,
|
|
1430
|
+
meta: input.meta ? { ...input.meta } : undefined,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function cloneIntermediateEmission(emission) {
|
|
1434
|
+
return {
|
|
1435
|
+
responseId: emission.responseId,
|
|
1436
|
+
actionCallId: emission.actionCallId,
|
|
1437
|
+
parentActionCallId: emission.parentActionCallId,
|
|
1438
|
+
deckPath: emission.deckPath,
|
|
1439
|
+
outputIndex: emission.outputIndex,
|
|
1440
|
+
item: emission.item,
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
function cloneAsyncActionJobEvent(event) {
|
|
1444
|
+
if (event.type === "intermediate_output") {
|
|
1445
|
+
return {
|
|
1446
|
+
cursor: event.cursor,
|
|
1447
|
+
type: "intermediate_output",
|
|
1448
|
+
emission: cloneIntermediateEmission(event.emission),
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
cursor: event.cursor,
|
|
1453
|
+
type: "terminal",
|
|
1454
|
+
terminal: cloneAsyncActionTerminalResult(event.terminal),
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function parsePersistedAsyncActionJobs(state) {
|
|
1458
|
+
const meta = state?.meta;
|
|
1459
|
+
const raw = meta?.[ASYNC_ACTION_JOBS_META_KEY];
|
|
1460
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1461
|
+
return undefined;
|
|
1462
|
+
const asRecord = raw;
|
|
1463
|
+
const version = Number.isInteger(asRecord.version)
|
|
1464
|
+
? Number(asRecord.version)
|
|
1465
|
+
: undefined;
|
|
1466
|
+
if (version !== ASYNC_ACTION_JOBS_META_VERSION)
|
|
1467
|
+
return undefined;
|
|
1468
|
+
const rawJobs = asRecord.jobs;
|
|
1469
|
+
if (!Array.isArray(rawJobs))
|
|
1470
|
+
return undefined;
|
|
1471
|
+
const jobs = [];
|
|
1472
|
+
for (const rawJob of rawJobs) {
|
|
1473
|
+
if (!rawJob || typeof rawJob !== "object" || Array.isArray(rawJob)) {
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
const rec = rawJob;
|
|
1477
|
+
const jobId = typeof rec.jobId === "string" ? rec.jobId : "";
|
|
1478
|
+
const actionName = typeof rec.actionName === "string" ? rec.actionName : "";
|
|
1479
|
+
const actionPath = typeof rec.actionPath === "string" ? rec.actionPath : "";
|
|
1480
|
+
const stateValue = typeof rec.state === "string" ? rec.state : "";
|
|
1481
|
+
if (!jobId || !actionName || !actionPath)
|
|
1482
|
+
continue;
|
|
1483
|
+
if (stateValue !== "running" && stateValue !== "completed" &&
|
|
1484
|
+
stateValue !== "failed" && stateValue !== "canceled") {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const parsedState = stateValue;
|
|
1488
|
+
const cursorBase = Number.isInteger(rec.cursorBase)
|
|
1489
|
+
? Math.max(0, Number(rec.cursorBase))
|
|
1490
|
+
: 0;
|
|
1491
|
+
const nextCursor = Number.isInteger(rec.nextCursor)
|
|
1492
|
+
? Math.max(cursorBase, Number(rec.nextCursor))
|
|
1493
|
+
: cursorBase;
|
|
1494
|
+
const expectedCursor = Number.isInteger(rec.expectedCursor)
|
|
1495
|
+
? Math.max(cursorBase, Math.min(nextCursor, Number(rec.expectedCursor)))
|
|
1496
|
+
: cursorBase;
|
|
1497
|
+
const rawEvents = Array.isArray(rec.events) ? rec.events : [];
|
|
1498
|
+
const events = [];
|
|
1499
|
+
for (const rawEvent of rawEvents) {
|
|
1500
|
+
if (!rawEvent || typeof rawEvent !== "object" || Array.isArray(rawEvent)) {
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
const eventRec = rawEvent;
|
|
1504
|
+
const cursor = Number.isInteger(eventRec.cursor)
|
|
1505
|
+
? Number(eventRec.cursor)
|
|
1506
|
+
: NaN;
|
|
1507
|
+
if (!Number.isInteger(cursor) || cursor < cursorBase || cursor >= nextCursor) {
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
const eventType = typeof eventRec.type === "string" ? eventRec.type : "";
|
|
1511
|
+
if (eventType === "intermediate_output") {
|
|
1512
|
+
const emission = eventRec.emission;
|
|
1513
|
+
if (!emission || typeof emission !== "object" || Array.isArray(emission)) {
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const emissionRec = emission;
|
|
1517
|
+
const outputIndex = Number.isInteger(emissionRec.outputIndex)
|
|
1518
|
+
? Number(emissionRec.outputIndex)
|
|
1519
|
+
: NaN;
|
|
1520
|
+
if (!Number.isInteger(outputIndex) || outputIndex < 0)
|
|
1521
|
+
continue;
|
|
1522
|
+
const responseId = typeof emissionRec.responseId === "string"
|
|
1523
|
+
? emissionRec.responseId
|
|
1524
|
+
: "";
|
|
1525
|
+
const actionCallId = typeof emissionRec.actionCallId === "string"
|
|
1526
|
+
? emissionRec.actionCallId
|
|
1527
|
+
: "";
|
|
1528
|
+
const deckPath = typeof emissionRec.deckPath === "string"
|
|
1529
|
+
? emissionRec.deckPath
|
|
1530
|
+
: "";
|
|
1531
|
+
const item = emissionRec.item;
|
|
1532
|
+
if (!responseId || !actionCallId || !deckPath || !item)
|
|
1533
|
+
continue;
|
|
1534
|
+
events.push({
|
|
1535
|
+
cursor,
|
|
1536
|
+
type: "intermediate_output",
|
|
1537
|
+
emission: {
|
|
1538
|
+
responseId,
|
|
1539
|
+
actionCallId,
|
|
1540
|
+
parentActionCallId: typeof emissionRec.parentActionCallId === "string"
|
|
1541
|
+
? emissionRec.parentActionCallId
|
|
1542
|
+
: undefined,
|
|
1543
|
+
deckPath,
|
|
1544
|
+
outputIndex,
|
|
1545
|
+
item: item,
|
|
1546
|
+
},
|
|
1547
|
+
});
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (eventType === "terminal") {
|
|
1551
|
+
const terminal = eventRec.terminal;
|
|
1552
|
+
if (!terminal || typeof terminal !== "object" || Array.isArray(terminal)) {
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
const terminalRec = terminal;
|
|
1556
|
+
const terminalState = terminalRec.state;
|
|
1557
|
+
if (terminalState !== "completed" && terminalState !== "failed" &&
|
|
1558
|
+
terminalState !== "canceled") {
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
events.push({
|
|
1562
|
+
cursor,
|
|
1563
|
+
type: "terminal",
|
|
1564
|
+
terminal: {
|
|
1565
|
+
state: terminalState,
|
|
1566
|
+
status: typeof terminalRec.status === "number"
|
|
1567
|
+
? terminalRec.status
|
|
1568
|
+
: undefined,
|
|
1569
|
+
payload: terminalRec.payload,
|
|
1570
|
+
message: typeof terminalRec.message === "string"
|
|
1571
|
+
? terminalRec.message
|
|
1572
|
+
: undefined,
|
|
1573
|
+
code: typeof terminalRec.code === "string"
|
|
1574
|
+
? terminalRec.code
|
|
1575
|
+
: undefined,
|
|
1576
|
+
meta: terminalRec.meta && typeof terminalRec.meta === "object" &&
|
|
1577
|
+
!Array.isArray(terminalRec.meta)
|
|
1578
|
+
? terminalRec.meta
|
|
1579
|
+
: undefined,
|
|
1580
|
+
},
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
events.sort((a, b) => a.cursor - b.cursor);
|
|
1585
|
+
const terminal = rec.terminal && typeof rec.terminal === "object" &&
|
|
1586
|
+
!Array.isArray(rec.terminal)
|
|
1587
|
+
? (() => {
|
|
1588
|
+
const terminalRec = rec.terminal;
|
|
1589
|
+
const terminalState = terminalRec.state;
|
|
1590
|
+
if (terminalState !== "completed" && terminalState !== "failed" &&
|
|
1591
|
+
terminalState !== "canceled") {
|
|
1592
|
+
return undefined;
|
|
1593
|
+
}
|
|
1594
|
+
return {
|
|
1595
|
+
state: terminalState,
|
|
1596
|
+
status: typeof terminalRec.status === "number"
|
|
1597
|
+
? terminalRec.status
|
|
1598
|
+
: undefined,
|
|
1599
|
+
payload: terminalRec.payload,
|
|
1600
|
+
message: typeof terminalRec.message === "string"
|
|
1601
|
+
? terminalRec.message
|
|
1602
|
+
: undefined,
|
|
1603
|
+
code: typeof terminalRec.code === "string"
|
|
1604
|
+
? terminalRec.code
|
|
1605
|
+
: undefined,
|
|
1606
|
+
meta: terminalRec.meta && typeof terminalRec.meta === "object" &&
|
|
1607
|
+
!Array.isArray(terminalRec.meta)
|
|
1608
|
+
? terminalRec.meta
|
|
1609
|
+
: undefined,
|
|
1610
|
+
};
|
|
1611
|
+
})()
|
|
1612
|
+
: undefined;
|
|
1613
|
+
jobs.push({
|
|
1614
|
+
jobId,
|
|
1615
|
+
actionName,
|
|
1616
|
+
actionPath,
|
|
1617
|
+
state: parsedState,
|
|
1618
|
+
cursorBase,
|
|
1619
|
+
nextCursor,
|
|
1620
|
+
expectedCursor,
|
|
1621
|
+
events,
|
|
1622
|
+
terminal,
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
return { version, jobs };
|
|
1626
|
+
}
|
|
1627
|
+
function createAsyncActionJobController(input) {
|
|
1628
|
+
const jobs = new Map();
|
|
1629
|
+
const notifyChange = () => input.onChange?.();
|
|
1630
|
+
const notifyWaiters = (record) => {
|
|
1631
|
+
if (record.waiters.size === 0)
|
|
1632
|
+
return;
|
|
1633
|
+
for (const waiter of record.waiters) {
|
|
1634
|
+
waiter();
|
|
1635
|
+
}
|
|
1636
|
+
record.waiters.clear();
|
|
1637
|
+
};
|
|
1638
|
+
const appendEvent = (record, event) => {
|
|
1639
|
+
const withCursor = {
|
|
1640
|
+
...event,
|
|
1641
|
+
cursor: record.nextCursor,
|
|
1642
|
+
};
|
|
1643
|
+
record.events.push(withCursor);
|
|
1644
|
+
record.nextCursor += 1;
|
|
1645
|
+
while (record.events.length > ASYNC_ACTION_JOB_MAX_EVENTS) {
|
|
1646
|
+
record.events.shift();
|
|
1647
|
+
record.cursorBase += 1;
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
const completeRecord = (record, terminal) => {
|
|
1651
|
+
if (record.state !== "running")
|
|
1652
|
+
return;
|
|
1653
|
+
record.state = terminal.state;
|
|
1654
|
+
record.terminal = cloneAsyncActionTerminalResult(terminal);
|
|
1655
|
+
appendEvent(record, {
|
|
1656
|
+
type: "terminal",
|
|
1657
|
+
terminal: cloneAsyncActionTerminalResult(terminal),
|
|
1658
|
+
});
|
|
1659
|
+
notifyWaiters(record);
|
|
1660
|
+
notifyChange();
|
|
1661
|
+
};
|
|
1662
|
+
const persisted = parsePersistedAsyncActionJobs(input.state);
|
|
1663
|
+
if (persisted) {
|
|
1664
|
+
let normalizedPersisted = false;
|
|
1665
|
+
for (const entry of persisted.jobs) {
|
|
1666
|
+
const record = {
|
|
1667
|
+
...entry,
|
|
1668
|
+
events: entry.events.map((event) => cloneAsyncActionJobEvent(event)),
|
|
1669
|
+
terminal: entry.terminal
|
|
1670
|
+
? cloneAsyncActionTerminalResult(entry.terminal)
|
|
1671
|
+
: undefined,
|
|
1672
|
+
waiters: new Set(),
|
|
1673
|
+
};
|
|
1674
|
+
if (record.expectedCursor < record.cursorBase) {
|
|
1675
|
+
record.expectedCursor = record.cursorBase;
|
|
1676
|
+
normalizedPersisted = true;
|
|
1677
|
+
}
|
|
1678
|
+
if (record.expectedCursor > record.nextCursor) {
|
|
1679
|
+
record.expectedCursor = record.nextCursor;
|
|
1680
|
+
normalizedPersisted = true;
|
|
1681
|
+
}
|
|
1682
|
+
if (record.state === "running") {
|
|
1683
|
+
normalizedPersisted = true;
|
|
1684
|
+
completeRecord(record, {
|
|
1685
|
+
state: "canceled",
|
|
1686
|
+
status: 499,
|
|
1687
|
+
code: ASYNC_ACTION_JOB_INTERRUPTED_CODE,
|
|
1688
|
+
message: "Async action job was interrupted before the run resumed.",
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
else if (!record.terminal) {
|
|
1692
|
+
const terminalEvent = [...record.events].reverse().find((event) => event.type === "terminal");
|
|
1693
|
+
if (terminalEvent?.type === "terminal") {
|
|
1694
|
+
record.terminal = cloneAsyncActionTerminalResult(terminalEvent.terminal);
|
|
1695
|
+
normalizedPersisted = true;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
jobs.set(record.jobId, record);
|
|
1699
|
+
}
|
|
1700
|
+
if (normalizedPersisted) {
|
|
1701
|
+
notifyChange();
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
const waitForJobChange = async (record, waitMs, signal) => {
|
|
1705
|
+
if (waitMs <= 0)
|
|
1706
|
+
return;
|
|
1707
|
+
await new Promise((resolve) => {
|
|
1708
|
+
const timeoutId = setTimeout(() => {
|
|
1709
|
+
cleanup();
|
|
1710
|
+
resolve();
|
|
1711
|
+
}, waitMs);
|
|
1712
|
+
const onAbort = () => {
|
|
1713
|
+
cleanup();
|
|
1714
|
+
resolve();
|
|
1715
|
+
};
|
|
1716
|
+
const waiter = () => {
|
|
1717
|
+
cleanup();
|
|
1718
|
+
resolve();
|
|
1719
|
+
};
|
|
1720
|
+
const cleanup = () => {
|
|
1721
|
+
clearTimeout(timeoutId);
|
|
1722
|
+
record.waiters.delete(waiter);
|
|
1723
|
+
if (signal)
|
|
1724
|
+
signal.removeEventListener("abort", onAbort);
|
|
1725
|
+
};
|
|
1726
|
+
record.waiters.add(waiter);
|
|
1727
|
+
if (signal)
|
|
1728
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1729
|
+
if (signal?.aborted) {
|
|
1730
|
+
cleanup();
|
|
1731
|
+
resolve();
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1734
|
+
};
|
|
1735
|
+
return {
|
|
1736
|
+
hasConfiguredAsyncActions: input.hasConfiguredAsyncActions,
|
|
1737
|
+
hasLiveJobs: () => Array.from(jobs.values()).some((job) => job.state === "running"),
|
|
1738
|
+
startJob: ({ actionName, actionPath }) => {
|
|
1739
|
+
const jobId = randomId("job");
|
|
1740
|
+
const record = {
|
|
1741
|
+
jobId,
|
|
1742
|
+
actionName,
|
|
1743
|
+
actionPath,
|
|
1744
|
+
state: "running",
|
|
1745
|
+
cursorBase: 0,
|
|
1746
|
+
nextCursor: 0,
|
|
1747
|
+
expectedCursor: 0,
|
|
1748
|
+
events: [],
|
|
1749
|
+
waiters: new Set(),
|
|
1750
|
+
};
|
|
1751
|
+
jobs.set(jobId, record);
|
|
1752
|
+
notifyChange();
|
|
1753
|
+
return {
|
|
1754
|
+
handle: {
|
|
1755
|
+
jobId,
|
|
1756
|
+
actionName,
|
|
1757
|
+
state: "running",
|
|
1758
|
+
cursor: {
|
|
1759
|
+
min: record.cursorBase,
|
|
1760
|
+
next: record.nextCursor,
|
|
1761
|
+
},
|
|
1762
|
+
},
|
|
1763
|
+
appendIntermediateOutput: (emission) => {
|
|
1764
|
+
if (record.state !== "running")
|
|
1765
|
+
return;
|
|
1766
|
+
appendEvent(record, {
|
|
1767
|
+
type: "intermediate_output",
|
|
1768
|
+
emission: cloneIntermediateEmission(emission),
|
|
1769
|
+
});
|
|
1770
|
+
notifyWaiters(record);
|
|
1771
|
+
notifyChange();
|
|
1772
|
+
},
|
|
1773
|
+
complete: (terminalResult) => {
|
|
1774
|
+
completeRecord(record, {
|
|
1775
|
+
state: "completed",
|
|
1776
|
+
status: terminalResult.status,
|
|
1777
|
+
payload: terminalResult.payload,
|
|
1778
|
+
message: terminalResult.message,
|
|
1779
|
+
code: terminalResult.code,
|
|
1780
|
+
meta: terminalResult.meta,
|
|
1781
|
+
});
|
|
1782
|
+
},
|
|
1783
|
+
fail: (terminalResult) => {
|
|
1784
|
+
completeRecord(record, {
|
|
1785
|
+
state: terminalResult.state,
|
|
1786
|
+
status: terminalResult.status,
|
|
1787
|
+
payload: terminalResult.payload,
|
|
1788
|
+
message: terminalResult.message,
|
|
1789
|
+
code: terminalResult.code,
|
|
1790
|
+
meta: terminalResult.meta,
|
|
1791
|
+
});
|
|
1792
|
+
},
|
|
1793
|
+
bindLiveRun: (runPromise, controller) => {
|
|
1794
|
+
record.controller = controller;
|
|
1795
|
+
record.runPromise = runPromise.finally(() => {
|
|
1796
|
+
record.runPromise = undefined;
|
|
1797
|
+
record.controller = undefined;
|
|
1798
|
+
});
|
|
1799
|
+
},
|
|
1800
|
+
};
|
|
1801
|
+
},
|
|
1802
|
+
consume: async (consumeInput) => {
|
|
1803
|
+
ensureRunActive(consumeInput.runDeadlineMs, consumeInput.signal);
|
|
1804
|
+
const record = jobs.get(consumeInput.jobId);
|
|
1805
|
+
if (!record) {
|
|
1806
|
+
return {
|
|
1807
|
+
ok: false,
|
|
1808
|
+
status: 404,
|
|
1809
|
+
code: "async_job_not_found",
|
|
1810
|
+
message: `Unknown async action job: ${consumeInput.jobId}`,
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
let cursor = consumeInput.cursor;
|
|
1814
|
+
if (!Number.isInteger(cursor) || cursor < 0) {
|
|
1815
|
+
return {
|
|
1816
|
+
ok: false,
|
|
1817
|
+
status: 400,
|
|
1818
|
+
code: "invalid_cursor",
|
|
1819
|
+
message: "cursor must be a non-negative integer",
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
if (cursor < record.cursorBase) {
|
|
1823
|
+
return {
|
|
1824
|
+
ok: false,
|
|
1825
|
+
status: 409,
|
|
1826
|
+
code: "cursor_expired",
|
|
1827
|
+
message: `cursor ${cursor} is behind retained minimum cursor ${record.cursorBase}`,
|
|
1828
|
+
payload: { minCursor: record.cursorBase },
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
if (cursor > record.nextCursor) {
|
|
1832
|
+
return {
|
|
1833
|
+
ok: false,
|
|
1834
|
+
status: 400,
|
|
1835
|
+
code: "invalid_cursor",
|
|
1836
|
+
message: `cursor ${cursor} is beyond current cursor ${record.nextCursor}`,
|
|
1837
|
+
payload: { maxCursor: record.nextCursor },
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
if (cursor !== record.expectedCursor) {
|
|
1841
|
+
return {
|
|
1842
|
+
ok: false,
|
|
1843
|
+
status: 409,
|
|
1844
|
+
code: "cursor_mismatch",
|
|
1845
|
+
message: `cursor ${cursor} does not match expected cursor ${record.expectedCursor}`,
|
|
1846
|
+
payload: { expectedCursor: record.expectedCursor },
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
const boundedWaitMs = Math.min(ASYNC_ACTION_JOB_MAX_WAIT_MS, Math.max(0, consumeInput.waitMs));
|
|
1850
|
+
const remainingMs = Math.max(0, Math.floor(consumeInput.runDeadlineMs - performance.now()));
|
|
1851
|
+
const waitMs = Math.min(boundedWaitMs, remainingMs);
|
|
1852
|
+
if (waitMs > 0 &&
|
|
1853
|
+
record.state === "running" &&
|
|
1854
|
+
cursor >= record.nextCursor) {
|
|
1855
|
+
await waitForJobChange(record, waitMs, consumeInput.signal);
|
|
1856
|
+
}
|
|
1857
|
+
ensureRunActive(consumeInput.runDeadlineMs, consumeInput.signal);
|
|
1858
|
+
cursor = consumeInput.cursor;
|
|
1859
|
+
if (cursor < record.cursorBase) {
|
|
1860
|
+
return {
|
|
1861
|
+
ok: false,
|
|
1862
|
+
status: 409,
|
|
1863
|
+
code: "cursor_expired",
|
|
1864
|
+
message: `cursor ${cursor} is behind retained minimum cursor ${record.cursorBase}`,
|
|
1865
|
+
payload: { minCursor: record.cursorBase },
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
const startIndex = Math.max(0, cursor - record.cursorBase);
|
|
1869
|
+
const events = record.events.slice(startIndex, startIndex + consumeInput.limit).map((event) => cloneAsyncActionJobEvent(event));
|
|
1870
|
+
const nextCursor = cursor + events.length;
|
|
1871
|
+
if (record.expectedCursor !== nextCursor) {
|
|
1872
|
+
record.expectedCursor = nextCursor;
|
|
1873
|
+
notifyChange();
|
|
1874
|
+
}
|
|
1875
|
+
return {
|
|
1876
|
+
ok: true,
|
|
1877
|
+
payload: {
|
|
1878
|
+
jobId: record.jobId,
|
|
1879
|
+
actionName: record.actionName,
|
|
1880
|
+
state: record.state,
|
|
1881
|
+
cursor: {
|
|
1882
|
+
requested: consumeInput.cursor,
|
|
1883
|
+
min: record.cursorBase,
|
|
1884
|
+
next: nextCursor,
|
|
1885
|
+
expected: record.expectedCursor,
|
|
1886
|
+
},
|
|
1887
|
+
events,
|
|
1888
|
+
terminal: record.terminal
|
|
1889
|
+
? cloneAsyncActionTerminalResult(record.terminal)
|
|
1890
|
+
: undefined,
|
|
1891
|
+
},
|
|
1892
|
+
};
|
|
1893
|
+
},
|
|
1894
|
+
snapshot: () => {
|
|
1895
|
+
if (jobs.size === 0)
|
|
1896
|
+
return undefined;
|
|
1897
|
+
return {
|
|
1898
|
+
version: ASYNC_ACTION_JOBS_META_VERSION,
|
|
1899
|
+
jobs: Array.from(jobs.values()).map((job) => ({
|
|
1900
|
+
jobId: job.jobId,
|
|
1901
|
+
actionName: job.actionName,
|
|
1902
|
+
actionPath: job.actionPath,
|
|
1903
|
+
state: job.state,
|
|
1904
|
+
cursorBase: job.cursorBase,
|
|
1905
|
+
nextCursor: job.nextCursor,
|
|
1906
|
+
expectedCursor: job.expectedCursor,
|
|
1907
|
+
events: job.events.map((event) => cloneAsyncActionJobEvent(event)),
|
|
1908
|
+
terminal: job.terminal
|
|
1909
|
+
? cloneAsyncActionTerminalResult(job.terminal)
|
|
1910
|
+
: undefined,
|
|
1911
|
+
})),
|
|
1912
|
+
};
|
|
1913
|
+
},
|
|
1914
|
+
cancelLiveJobs: ({ message, code }) => {
|
|
1915
|
+
for (const record of jobs.values()) {
|
|
1916
|
+
if (record.state !== "running")
|
|
1917
|
+
continue;
|
|
1918
|
+
completeRecord(record, {
|
|
1919
|
+
state: "canceled",
|
|
1920
|
+
status: 499,
|
|
1921
|
+
code,
|
|
1922
|
+
message,
|
|
1923
|
+
});
|
|
1924
|
+
record.controller?.abort();
|
|
1925
|
+
}
|
|
1926
|
+
},
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
965
1929
|
function mapResponseOutput(output) {
|
|
966
1930
|
const toolCalls = [];
|
|
967
1931
|
const textParts = [];
|
|
@@ -1015,7 +1979,22 @@ function validateInput(deck, input, isRoot, allowRootStringInput) {
|
|
|
1015
1979
|
function validateOutput(deck, output, isRoot) {
|
|
1016
1980
|
const responseSchema = resolveResponseSchema(deck);
|
|
1017
1981
|
if (responseSchema) {
|
|
1018
|
-
|
|
1982
|
+
try {
|
|
1983
|
+
return validateWithSchema(responseSchema, output);
|
|
1984
|
+
}
|
|
1985
|
+
catch (directErr) {
|
|
1986
|
+
if (typeof output !== "string")
|
|
1987
|
+
throw directErr;
|
|
1988
|
+
try {
|
|
1989
|
+
const parsed = JSON.parse(output);
|
|
1990
|
+
return validateWithSchema(responseSchema, parsed);
|
|
1991
|
+
}
|
|
1992
|
+
catch (parsedErr) {
|
|
1993
|
+
if (parsedErr instanceof SyntaxError)
|
|
1994
|
+
throw directErr;
|
|
1995
|
+
throw parsedErr;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1019
1998
|
}
|
|
1020
1999
|
if (isRoot) {
|
|
1021
2000
|
if (typeof output === "string")
|
|
@@ -1045,6 +2024,9 @@ async function runComputeDeck(ctx) {
|
|
|
1045
2024
|
responsesMode: ctx.responsesMode,
|
|
1046
2025
|
permissions: ctx.permissions,
|
|
1047
2026
|
permissionsTrace: ctx.permissionsTrace,
|
|
2027
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
2028
|
+
onIntermediateOutputItem: ctx.onIntermediateOutputItem,
|
|
2029
|
+
intermediateOutputErrorContext: ctx.intermediateOutputErrorContext,
|
|
1048
2030
|
workspacePermissions: ctx.workspacePermissions,
|
|
1049
2031
|
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
1050
2032
|
sessionPermissions: ctx.sessionPermissions,
|
|
@@ -1417,14 +2399,30 @@ function collectLocalImportGraph(entryPath) {
|
|
|
1417
2399
|
}
|
|
1418
2400
|
return visited;
|
|
1419
2401
|
}
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
2402
|
+
const moduleBaseFilePath = (() => {
|
|
2403
|
+
try {
|
|
2404
|
+
return path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url);
|
|
2405
|
+
}
|
|
2406
|
+
catch {
|
|
2407
|
+
return undefined;
|
|
2408
|
+
}
|
|
2409
|
+
})();
|
|
2410
|
+
const WORKER_ENTRY_PATHS = moduleBaseFilePath
|
|
2411
|
+
? [
|
|
2412
|
+
"./runtime_worker.ts",
|
|
2413
|
+
"./runtime_orchestration_worker.ts",
|
|
2414
|
+
].map((relative) => path.fromFileUrl(new URL(relative, globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url)))
|
|
2415
|
+
: [];
|
|
2416
|
+
const BUILTIN_SCHEMAS_DIR = moduleBaseFilePath
|
|
2417
|
+
? path.resolve(path.dirname(moduleBaseFilePath), "../schemas")
|
|
2418
|
+
: undefined;
|
|
2419
|
+
const BUILTIN_SNIPPETS_DIR = moduleBaseFilePath
|
|
2420
|
+
? path.resolve(path.dirname(moduleBaseFilePath), "../snippets")
|
|
2421
|
+
: undefined;
|
|
1426
2422
|
let builtinSchemaBootstrapCache;
|
|
1427
2423
|
function builtinSchemaBootstrapReads() {
|
|
2424
|
+
if (!BUILTIN_SCHEMAS_DIR)
|
|
2425
|
+
return [];
|
|
1428
2426
|
if (builtinSchemaBootstrapCache)
|
|
1429
2427
|
return builtinSchemaBootstrapCache;
|
|
1430
2428
|
const schemaModules = [];
|
|
@@ -1457,6 +2455,8 @@ function builtinSchemaBootstrapReads() {
|
|
|
1457
2455
|
}
|
|
1458
2456
|
let builtinSnippetBootstrapCache;
|
|
1459
2457
|
function builtinSnippetBootstrapReads() {
|
|
2458
|
+
if (!BUILTIN_SNIPPETS_DIR)
|
|
2459
|
+
return [];
|
|
1460
2460
|
if (builtinSnippetBootstrapCache)
|
|
1461
2461
|
return builtinSnippetBootstrapCache;
|
|
1462
2462
|
const snippetFiles = [];
|
|
@@ -1499,6 +2499,10 @@ let trustedWorkerBootstrapCache;
|
|
|
1499
2499
|
function trustedWorkerBootstrapReads() {
|
|
1500
2500
|
if (trustedWorkerBootstrapCache)
|
|
1501
2501
|
return trustedWorkerBootstrapCache;
|
|
2502
|
+
if (!moduleBaseFilePath) {
|
|
2503
|
+
trustedWorkerBootstrapCache = [];
|
|
2504
|
+
return trustedWorkerBootstrapCache;
|
|
2505
|
+
}
|
|
1502
2506
|
const definitionsPath = path.fromFileUrl(new URL("./definitions.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
|
|
1503
2507
|
const modPath = path.fromFileUrl(new URL("../mod.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
|
|
1504
2508
|
trustedWorkerBootstrapCache = Array.from(new Set([
|
|
@@ -1688,6 +2692,7 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1688
2692
|
const bridgeSession = randomId("bridge");
|
|
1689
2693
|
const completionNonce = randomId("done");
|
|
1690
2694
|
const worker = createWorkerSandboxBridge(new URL("./runtime_orchestration_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
|
|
2695
|
+
const modelRequestControllers = new Map();
|
|
1691
2696
|
let settled = false;
|
|
1692
2697
|
const clearAndTerminate = () => {
|
|
1693
2698
|
try {
|
|
@@ -1739,6 +2744,15 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1739
2744
|
return;
|
|
1740
2745
|
}
|
|
1741
2746
|
if (msg.type === "trace.event") {
|
|
2747
|
+
const emission = ctx.onIntermediateOutputItem
|
|
2748
|
+
? parseIntermediateOutputEmissionFromTraceEvent({
|
|
2749
|
+
event: msg.event,
|
|
2750
|
+
expectedParentActionCallId: ctx.parentActionCallId,
|
|
2751
|
+
})
|
|
2752
|
+
: undefined;
|
|
2753
|
+
if (emission) {
|
|
2754
|
+
ctx.onIntermediateOutputItem?.(emission);
|
|
2755
|
+
}
|
|
1742
2756
|
ctx.trace?.(msg.event);
|
|
1743
2757
|
return;
|
|
1744
2758
|
}
|
|
@@ -1752,10 +2766,12 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1752
2766
|
}
|
|
1753
2767
|
if (msg.type === "model.chat.request") {
|
|
1754
2768
|
(async () => {
|
|
2769
|
+
const controller = new AbortController();
|
|
2770
|
+
modelRequestControllers.set(msg.requestId, controller);
|
|
1755
2771
|
try {
|
|
1756
2772
|
const result = await ctx.modelProvider.chat({
|
|
1757
2773
|
...msg.input,
|
|
1758
|
-
signal: ctx.signal,
|
|
2774
|
+
signal: mergeAbortSignals(ctx.signal, controller.signal),
|
|
1759
2775
|
onStreamText: (chunk) => {
|
|
1760
2776
|
worker.postMessage({
|
|
1761
2777
|
type: "model.chat.stream",
|
|
@@ -1782,18 +2798,23 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1782
2798
|
},
|
|
1783
2799
|
});
|
|
1784
2800
|
}
|
|
2801
|
+
finally {
|
|
2802
|
+
modelRequestControllers.delete(msg.requestId);
|
|
2803
|
+
}
|
|
1785
2804
|
})();
|
|
1786
2805
|
return;
|
|
1787
2806
|
}
|
|
1788
2807
|
if (msg.type === "model.responses.request") {
|
|
1789
2808
|
(async () => {
|
|
2809
|
+
const controller = new AbortController();
|
|
2810
|
+
modelRequestControllers.set(msg.requestId, controller);
|
|
1790
2811
|
try {
|
|
1791
2812
|
if (!ctx.modelProvider.responses) {
|
|
1792
2813
|
throw new Error("Responses API unavailable for current model provider");
|
|
1793
2814
|
}
|
|
1794
2815
|
const result = await ctx.modelProvider.responses({
|
|
1795
2816
|
...msg.input,
|
|
1796
|
-
signal: ctx.signal,
|
|
2817
|
+
signal: mergeAbortSignals(ctx.signal, controller.signal),
|
|
1797
2818
|
onStreamEvent: (streamEvent) => {
|
|
1798
2819
|
worker.postMessage({
|
|
1799
2820
|
type: "model.responses.event",
|
|
@@ -1820,9 +2841,16 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1820
2841
|
},
|
|
1821
2842
|
});
|
|
1822
2843
|
}
|
|
2844
|
+
finally {
|
|
2845
|
+
modelRequestControllers.delete(msg.requestId);
|
|
2846
|
+
}
|
|
1823
2847
|
})();
|
|
1824
2848
|
return;
|
|
1825
2849
|
}
|
|
2850
|
+
if (msg.type === "model.request.cancel") {
|
|
2851
|
+
modelRequestControllers.get(msg.requestId)?.abort();
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
1826
2854
|
if (msg.type === "model.resolveModel.request") {
|
|
1827
2855
|
(async () => {
|
|
1828
2856
|
try {
|
|
@@ -1893,6 +2921,8 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1893
2921
|
state: ctx.state,
|
|
1894
2922
|
responsesMode: ctx.responsesMode,
|
|
1895
2923
|
allowRootStringInput: ctx.allowRootStringInput,
|
|
2924
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
2925
|
+
intermediateOutputErrorContext: ctx.intermediateOutputErrorContext,
|
|
1896
2926
|
runDeadlineMs: ctx.runDeadlineMs,
|
|
1897
2927
|
},
|
|
1898
2928
|
permissionCeiling: toWirePermissionSet(ctx.permissions),
|
|
@@ -1901,6 +2931,9 @@ async function runLlmDeckInWorker(ctx) {
|
|
|
1901
2931
|
return await outcome;
|
|
1902
2932
|
}
|
|
1903
2933
|
finally {
|
|
2934
|
+
for (const controller of modelRequestControllers.values()) {
|
|
2935
|
+
controller.abort();
|
|
2936
|
+
}
|
|
1904
2937
|
if (timeoutId !== undefined)
|
|
1905
2938
|
clearTimeout(timeoutId);
|
|
1906
2939
|
clearAndTerminate();
|
|
@@ -1925,6 +2958,18 @@ async function runComputeDeckInWorker(ctx) {
|
|
|
1925
2958
|
let timeoutId;
|
|
1926
2959
|
const activeSpawnRequests = new Set();
|
|
1927
2960
|
let currentState = ctx.state;
|
|
2961
|
+
const intermediateEmitter = createIntermediateChildResponseEmitter({
|
|
2962
|
+
runId,
|
|
2963
|
+
actionCallId,
|
|
2964
|
+
deckPath: ctx.deckPath,
|
|
2965
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2966
|
+
trace: ctx.trace,
|
|
2967
|
+
validateItem: (item) => item,
|
|
2968
|
+
ensureActive: () => ensureRunActive(ctx.runDeadlineMs, ctx.signal),
|
|
2969
|
+
allowEmission: ctx.intermediateOutputAllow,
|
|
2970
|
+
errorContext: ctx.intermediateOutputErrorContext,
|
|
2971
|
+
onEmit: ctx.onIntermediateOutputItem,
|
|
2972
|
+
});
|
|
1928
2973
|
const outcome = new Promise((resolve, reject) => {
|
|
1929
2974
|
const finishResolve = (value) => {
|
|
1930
2975
|
if (settled)
|
|
@@ -2038,6 +3083,9 @@ async function runComputeDeckInWorker(ctx) {
|
|
|
2038
3083
|
responsesMode: ctx.responsesMode,
|
|
2039
3084
|
initialUserMessage: req.payload.initialUserMessage,
|
|
2040
3085
|
inputProvided: true,
|
|
3086
|
+
intermediateOutputAllow: req.payload.intermediateOutputAllow,
|
|
3087
|
+
onIntermediateOutputItem: ctx.onIntermediateOutputItem,
|
|
3088
|
+
intermediateOutputErrorContext: req.payload.intermediateOutputErrorContext,
|
|
2041
3089
|
parentPermissions: bridgedParent,
|
|
2042
3090
|
workspacePermissions: req.payload.workspacePermissions,
|
|
2043
3091
|
workspacePermissionsBaseDir: req.payload.workspacePermissionsBaseDir,
|
|
@@ -2082,12 +3130,34 @@ async function runComputeDeckInWorker(ctx) {
|
|
|
2082
3130
|
ctx.onStateUpdate?.(nextState);
|
|
2083
3131
|
return;
|
|
2084
3132
|
}
|
|
3133
|
+
if (type === "response.item") {
|
|
3134
|
+
const item = msg.item;
|
|
3135
|
+
if (!item || typeof item !== "object")
|
|
3136
|
+
return;
|
|
3137
|
+
try {
|
|
3138
|
+
intermediateEmitter.emitOutputItem(item, "worker.emitOutputItem");
|
|
3139
|
+
}
|
|
3140
|
+
catch (err) {
|
|
3141
|
+
intermediateEmitter.fail(err);
|
|
3142
|
+
finishReject(err);
|
|
3143
|
+
clearAndTerminate();
|
|
3144
|
+
}
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
2085
3147
|
if (type === "run.result") {
|
|
2086
3148
|
if (msg.completionNonce !==
|
|
2087
3149
|
completionNonce) {
|
|
2088
3150
|
logger.warn(`[gambit] rejected compute-worker run.result with invalid completion nonce`);
|
|
2089
3151
|
return;
|
|
2090
3152
|
}
|
|
3153
|
+
try {
|
|
3154
|
+
intermediateEmitter.complete();
|
|
3155
|
+
}
|
|
3156
|
+
catch (err) {
|
|
3157
|
+
intermediateEmitter.fail(err);
|
|
3158
|
+
finishReject(err);
|
|
3159
|
+
return;
|
|
3160
|
+
}
|
|
2091
3161
|
finishResolve(msg.result);
|
|
2092
3162
|
return;
|
|
2093
3163
|
}
|
|
@@ -2097,7 +3167,9 @@ async function runComputeDeckInWorker(ctx) {
|
|
|
2097
3167
|
logger.warn(`[gambit] rejected compute-worker run.error with invalid completion nonce`);
|
|
2098
3168
|
return;
|
|
2099
3169
|
}
|
|
2100
|
-
|
|
3170
|
+
const normalizedError = normalizeWorkerError(msg.error);
|
|
3171
|
+
intermediateEmitter.fail(normalizedError);
|
|
3172
|
+
finishReject(normalizedError);
|
|
2101
3173
|
}
|
|
2102
3174
|
});
|
|
2103
3175
|
});
|
|
@@ -2114,6 +3186,8 @@ async function runComputeDeckInWorker(ctx) {
|
|
|
2114
3186
|
initialUserMessage: ctx.initialUserMessage,
|
|
2115
3187
|
depth: ctx.depth,
|
|
2116
3188
|
parentActionCallId: ctx.parentActionCallId,
|
|
3189
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
3190
|
+
intermediateOutputErrorContext: ctx.intermediateOutputErrorContext,
|
|
2117
3191
|
permissions: toWirePermissionSet(ctx.permissions),
|
|
2118
3192
|
workspacePermissions: ctx.workspacePermissions,
|
|
2119
3193
|
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
@@ -2136,6 +3210,7 @@ async function runComputeDeckInWorker(ctx) {
|
|
|
2136
3210
|
async function runComputeDeckInProcess(ctx) {
|
|
2137
3211
|
const { deck, runId } = ctx;
|
|
2138
3212
|
const actionCallId = randomId("action");
|
|
3213
|
+
const validateResponseItemEmission = createResponseItemEmissionValidator(deck);
|
|
2139
3214
|
let computeState = ctx.state
|
|
2140
3215
|
? {
|
|
2141
3216
|
...ctx.state,
|
|
@@ -2171,6 +3246,18 @@ async function runComputeDeckInProcess(ctx) {
|
|
|
2171
3246
|
: undefined,
|
|
2172
3247
|
});
|
|
2173
3248
|
};
|
|
3249
|
+
const intermediateEmitter = createIntermediateChildResponseEmitter({
|
|
3250
|
+
runId,
|
|
3251
|
+
actionCallId,
|
|
3252
|
+
deckPath: deck.path,
|
|
3253
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3254
|
+
trace: ctx.trace,
|
|
3255
|
+
validateItem: validateResponseItemEmission,
|
|
3256
|
+
ensureActive: () => ensureRunActive(ctx.runDeadlineMs, ctx.signal),
|
|
3257
|
+
allowEmission: ctx.intermediateOutputAllow,
|
|
3258
|
+
errorContext: ctx.intermediateOutputErrorContext,
|
|
3259
|
+
onEmit: ctx.onIntermediateOutputItem,
|
|
3260
|
+
});
|
|
2174
3261
|
const execContext = {
|
|
2175
3262
|
runId,
|
|
2176
3263
|
actionCallId,
|
|
@@ -2213,6 +3300,10 @@ async function runComputeDeckInProcess(ctx) {
|
|
|
2213
3300
|
state.messageRefs = refs;
|
|
2214
3301
|
publishComputeState();
|
|
2215
3302
|
},
|
|
3303
|
+
emitOutputItem: (item) => {
|
|
3304
|
+
intermediateEmitter.emitOutputItem(item, "executionContext.emitOutputItem");
|
|
3305
|
+
return Promise.resolve();
|
|
3306
|
+
},
|
|
2216
3307
|
label: deck.label,
|
|
2217
3308
|
log: (entry) => {
|
|
2218
3309
|
if (!ctx.trace)
|
|
@@ -2281,6 +3372,9 @@ async function runComputeDeckInProcess(ctx) {
|
|
|
2281
3372
|
responsesMode: ctx.responsesMode,
|
|
2282
3373
|
initialUserMessage: childInitialUserMessage,
|
|
2283
3374
|
inputProvided: true,
|
|
3375
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
3376
|
+
onIntermediateOutputItem: ctx.onIntermediateOutputItem,
|
|
3377
|
+
intermediateOutputErrorContext: ctx.intermediateOutputErrorContext,
|
|
2284
3378
|
parentPermissions: ctx.permissions,
|
|
2285
3379
|
workspacePermissions: ctx.workspacePermissions,
|
|
2286
3380
|
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
@@ -2297,18 +3391,40 @@ async function runComputeDeckInProcess(ctx) {
|
|
|
2297
3391
|
},
|
|
2298
3392
|
return: (payload) => Promise.resolve(payload),
|
|
2299
3393
|
};
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
3394
|
+
try {
|
|
3395
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
3396
|
+
const raw = await deck.executor(execContext);
|
|
3397
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
3398
|
+
const validated = validateOutput(deck, raw, ctx.depth === 0);
|
|
3399
|
+
intermediateEmitter.complete();
|
|
3400
|
+
return validated;
|
|
3401
|
+
}
|
|
3402
|
+
catch (err) {
|
|
3403
|
+
intermediateEmitter.fail(err);
|
|
3404
|
+
throw err;
|
|
3405
|
+
}
|
|
2304
3406
|
}
|
|
2305
3407
|
async function runLlmDeck(ctx) {
|
|
2306
3408
|
const { deck, guardrails, depth, modelProvider, input, runId, inputProvided, initialUserMessage, } = ctx;
|
|
2307
3409
|
const actionCallId = randomId("action");
|
|
2308
3410
|
const start = performance.now();
|
|
2309
|
-
const respondEnabled = Boolean(deck.respond);
|
|
2310
3411
|
const useResponses = Boolean(ctx.responsesMode) ||
|
|
2311
3412
|
ctx.state?.format === "responses";
|
|
3413
|
+
const validateResponseItemEmission = createResponseItemEmissionValidator(deck);
|
|
3414
|
+
const intermediateEmitter = ctx.parentActionCallId !== undefined
|
|
3415
|
+
? createIntermediateChildResponseEmitter({
|
|
3416
|
+
runId,
|
|
3417
|
+
actionCallId,
|
|
3418
|
+
deckPath: deck.path,
|
|
3419
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3420
|
+
trace: ctx.trace,
|
|
3421
|
+
validateItem: validateResponseItemEmission,
|
|
3422
|
+
ensureActive: () => ensureRunActive(ctx.runDeadlineMs, ctx.signal),
|
|
3423
|
+
allowEmission: ctx.intermediateOutputAllow,
|
|
3424
|
+
errorContext: ctx.intermediateOutputErrorContext,
|
|
3425
|
+
onEmit: ctx.onIntermediateOutputItem,
|
|
3426
|
+
})
|
|
3427
|
+
: undefined;
|
|
2312
3428
|
const systemPrompt = buildSystemPrompt(deck);
|
|
2313
3429
|
const refToolCallId = randomId("call");
|
|
2314
3430
|
const messages = ctx.state?.messages?.length
|
|
@@ -2333,6 +3449,9 @@ async function runLlmDeck(ctx) {
|
|
|
2333
3449
|
onStreamText: ctx.onStreamText,
|
|
2334
3450
|
pushMessages: (msgs) => messages.push(...msgs.map(sanitizeMessage)),
|
|
2335
3451
|
responsesMode: ctx.responsesMode,
|
|
3452
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
3453
|
+
onIntermediateOutputItem: ctx.onIntermediateOutputItem,
|
|
3454
|
+
intermediateOutputErrorContext: ctx.intermediateOutputErrorContext,
|
|
2336
3455
|
permissions: ctx.permissions,
|
|
2337
3456
|
workspacePermissions: ctx.workspacePermissions,
|
|
2338
3457
|
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
@@ -2343,6 +3462,10 @@ async function runLlmDeck(ctx) {
|
|
|
2343
3462
|
signal: ctx.signal,
|
|
2344
3463
|
onTool: ctx.onTool,
|
|
2345
3464
|
});
|
|
3465
|
+
const asyncActionJobs = createAsyncActionJobController({
|
|
3466
|
+
state: ctx.state,
|
|
3467
|
+
hasConfiguredAsyncActions: deck.actionDecks.some((action) => action.asyncStart?.mode === "allow"),
|
|
3468
|
+
});
|
|
2346
3469
|
let streamingBuffer = "";
|
|
2347
3470
|
let streamingCommitted = false;
|
|
2348
3471
|
const wrappedOnStreamText = (chunk) => {
|
|
@@ -2408,7 +3531,14 @@ async function runLlmDeck(ctx) {
|
|
|
2408
3531
|
});
|
|
2409
3532
|
}
|
|
2410
3533
|
idleController.touch();
|
|
2411
|
-
const tools = await buildToolDefs(deck, ctx.permissions
|
|
3534
|
+
const tools = await buildToolDefs(deck, ctx.permissions, {
|
|
3535
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3536
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
3537
|
+
asyncActionJobs,
|
|
3538
|
+
});
|
|
3539
|
+
const responseTextConfig = useResponses
|
|
3540
|
+
? toResponseTextConfig(deck)
|
|
3541
|
+
: undefined;
|
|
2412
3542
|
ctx.trace?.({
|
|
2413
3543
|
type: "deck.start",
|
|
2414
3544
|
runId,
|
|
@@ -2462,6 +3592,7 @@ async function runLlmDeck(ctx) {
|
|
|
2462
3592
|
const projectedToolCalls = new Set();
|
|
2463
3593
|
const projectedToolResults = new Set();
|
|
2464
3594
|
const projectedToolNames = new Map();
|
|
3595
|
+
const canonicalizeStreamEvent = createCanonicalStreamEventController();
|
|
2465
3596
|
const result = (useResponses && responses)
|
|
2466
3597
|
? await (async () => {
|
|
2467
3598
|
const responseItems = responseItemsFromMessages(messages);
|
|
@@ -2471,6 +3602,7 @@ async function runLlmDeck(ctx) {
|
|
|
2471
3602
|
model,
|
|
2472
3603
|
input: responseItems,
|
|
2473
3604
|
tools: tools,
|
|
3605
|
+
text: responseTextConfig,
|
|
2474
3606
|
stream: ctx.stream,
|
|
2475
3607
|
params: providerParams,
|
|
2476
3608
|
},
|
|
@@ -2479,8 +3611,11 @@ async function runLlmDeck(ctx) {
|
|
|
2479
3611
|
signal: ctx.signal,
|
|
2480
3612
|
onStreamEvent: (ctx.trace || ctx.onStreamText || deck.handlers?.onIdle)
|
|
2481
3613
|
? (event) => {
|
|
3614
|
+
const normalizedEvent = validateResponseEventItems(event, validateResponseItemEmission);
|
|
3615
|
+
const streamEvent = canonicalizeStreamEvent(normalizedEvent);
|
|
3616
|
+
if (!streamEvent)
|
|
3617
|
+
return;
|
|
2482
3618
|
if (ctx.trace) {
|
|
2483
|
-
const streamEvent = event;
|
|
2484
3619
|
const handledAsResponse = traceOpenResponsesStreamEvent({
|
|
2485
3620
|
streamEvent,
|
|
2486
3621
|
runId,
|
|
@@ -2511,17 +3646,28 @@ async function runLlmDeck(ctx) {
|
|
|
2511
3646
|
toolNames: projectedToolNames,
|
|
2512
3647
|
});
|
|
2513
3648
|
}
|
|
2514
|
-
|
|
3649
|
+
const eventType = typeof streamEvent.type === "string"
|
|
3650
|
+
? streamEvent.type
|
|
3651
|
+
: "";
|
|
3652
|
+
if (eventType === "response.output_text.delta") {
|
|
2515
3653
|
sawDelta = true;
|
|
2516
|
-
|
|
3654
|
+
const delta = typeof streamEvent.delta === "string"
|
|
3655
|
+
? streamEvent.delta
|
|
3656
|
+
: "";
|
|
3657
|
+
if (delta)
|
|
3658
|
+
wrappedOnStreamText(delta);
|
|
2517
3659
|
}
|
|
2518
|
-
else if (
|
|
2519
|
-
|
|
3660
|
+
else if (eventType === "response.output_text.done" && !sawDelta) {
|
|
3661
|
+
const text = typeof streamEvent.text === "string"
|
|
3662
|
+
? streamEvent.text
|
|
3663
|
+
: "";
|
|
3664
|
+
if (text)
|
|
3665
|
+
wrappedOnStreamText(text);
|
|
2520
3666
|
}
|
|
2521
3667
|
}
|
|
2522
3668
|
: undefined,
|
|
2523
3669
|
});
|
|
2524
|
-
responseOutputItems = response.output ?? [];
|
|
3670
|
+
responseOutputItems = validateResponseOutputItems(response.output ?? [], validateResponseItemEmission, "response.output");
|
|
2525
3671
|
const mapped = mapResponseOutput(responseOutputItems);
|
|
2526
3672
|
return {
|
|
2527
3673
|
message: mapped.message,
|
|
@@ -2545,8 +3691,11 @@ async function runLlmDeck(ctx) {
|
|
|
2545
3691
|
: undefined,
|
|
2546
3692
|
onStreamEvent: ctx.trace
|
|
2547
3693
|
? (event) => {
|
|
3694
|
+
const streamEvent = canonicalizeStreamEvent(event);
|
|
3695
|
+
if (!streamEvent)
|
|
3696
|
+
return;
|
|
2548
3697
|
const handledAsResponse = traceOpenResponsesStreamEvent({
|
|
2549
|
-
streamEvent
|
|
3698
|
+
streamEvent,
|
|
2550
3699
|
runId,
|
|
2551
3700
|
actionCallId,
|
|
2552
3701
|
deckPath: deck.path,
|
|
@@ -2561,12 +3710,12 @@ async function runLlmDeck(ctx) {
|
|
|
2561
3710
|
actionCallId,
|
|
2562
3711
|
deckPath: deck.path,
|
|
2563
3712
|
model,
|
|
2564
|
-
event,
|
|
3713
|
+
event: streamEvent,
|
|
2565
3714
|
parentActionCallId: ctx.parentActionCallId,
|
|
2566
3715
|
});
|
|
2567
3716
|
}
|
|
2568
3717
|
projectStreamToolTraceEvents({
|
|
2569
|
-
streamEvent
|
|
3718
|
+
streamEvent,
|
|
2570
3719
|
runId,
|
|
2571
3720
|
parentActionCallId: actionCallId,
|
|
2572
3721
|
trace: ctx.trace,
|
|
@@ -2600,14 +3749,36 @@ async function runLlmDeck(ctx) {
|
|
|
2600
3749
|
const mergedMessages = base.messages && base.messages.length > 0
|
|
2601
3750
|
? base.messages.map(sanitizeMessage)
|
|
2602
3751
|
: messages.map(sanitizeMessage);
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
3752
|
+
let responseItems;
|
|
3753
|
+
if (useResponses) {
|
|
3754
|
+
const conversationalItems = responseItemsFromMessages(mergedMessages);
|
|
3755
|
+
const priorSupplemental = (updated?.items ?? ctx.state?.items ?? [])
|
|
3756
|
+
.filter((item) => isSupplementalResponseItem(item));
|
|
3757
|
+
const latestSupplemental = (responseOutputItems ?? [])
|
|
3758
|
+
.filter((item) => isSupplementalResponseItem(item));
|
|
3759
|
+
const supplementalItems = mergeSupplementalResponseItems(priorSupplemental, latestSupplemental);
|
|
3760
|
+
responseItems = [
|
|
3761
|
+
...conversationalItems,
|
|
3762
|
+
...supplementalItems,
|
|
3763
|
+
];
|
|
3764
|
+
}
|
|
3765
|
+
else {
|
|
3766
|
+
responseItems = updated?.items ?? ctx.state?.items;
|
|
3767
|
+
}
|
|
2606
3768
|
const priorRefs = updated?.messageRefs ?? ctx.state?.messageRefs ?? [];
|
|
2607
3769
|
const messageRefs = mergedMessages.map((m, idx) => priorRefs[idx] ?? { id: randomId("msg"), role: m.role });
|
|
2608
3770
|
const feedback = updated?.feedback ?? ctx.state?.feedback;
|
|
2609
3771
|
const traces = updated?.traces ?? ctx.state?.traces;
|
|
2610
|
-
const
|
|
3772
|
+
const sourceMeta = updated?.meta ?? ctx.state?.meta;
|
|
3773
|
+
const nextMeta = sourceMeta ? { ...sourceMeta } : {};
|
|
3774
|
+
const asyncActionSnapshot = asyncActionJobs.snapshot();
|
|
3775
|
+
if (asyncActionSnapshot) {
|
|
3776
|
+
nextMeta[ASYNC_ACTION_JOBS_META_KEY] = asyncActionSnapshot;
|
|
3777
|
+
}
|
|
3778
|
+
else {
|
|
3779
|
+
delete nextMeta[ASYNC_ACTION_JOBS_META_KEY];
|
|
3780
|
+
}
|
|
3781
|
+
const meta = Object.keys(nextMeta).length > 0 ? nextMeta : undefined;
|
|
2611
3782
|
const notes = updated?.notes ?? ctx.state?.notes;
|
|
2612
3783
|
const conversationScore = updated?.conversationScore ??
|
|
2613
3784
|
ctx.state?.conversationScore;
|
|
@@ -2628,9 +3799,6 @@ async function runLlmDeck(ctx) {
|
|
|
2628
3799
|
};
|
|
2629
3800
|
};
|
|
2630
3801
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
2631
|
-
let responded = false;
|
|
2632
|
-
let respondValue;
|
|
2633
|
-
let endSignal;
|
|
2634
3802
|
const appendedMessages = [];
|
|
2635
3803
|
const toolCallText = streamingBuffer ||
|
|
2636
3804
|
(typeof message.content === "string" ? message.content : "");
|
|
@@ -2639,141 +3807,11 @@ async function runLlmDeck(ctx) {
|
|
|
2639
3807
|
streamingCommitted = true;
|
|
2640
3808
|
}
|
|
2641
3809
|
for (const call of result.toolCalls) {
|
|
2642
|
-
if (
|
|
2643
|
-
|
|
2644
|
-
? call.args.status
|
|
2645
|
-
: undefined;
|
|
2646
|
-
const message = typeof call.args?.message === "string"
|
|
2647
|
-
? call.args.message
|
|
2648
|
-
: undefined;
|
|
2649
|
-
const code = typeof call.args?.code === "string"
|
|
2650
|
-
? call.args.code
|
|
2651
|
-
: undefined;
|
|
2652
|
-
const meta = (call.args?.meta &&
|
|
2653
|
-
typeof call.args.meta === "object" &&
|
|
2654
|
-
call.args.meta !== null)
|
|
2655
|
-
? call.args.meta
|
|
2656
|
-
: undefined;
|
|
2657
|
-
const rawPayload = call.args?.payload ?? call.args;
|
|
2658
|
-
const validatedPayload = validateOutput(deck, rawPayload, depth === 0);
|
|
2659
|
-
const respondEnvelope = {
|
|
2660
|
-
payload: validatedPayload,
|
|
2661
|
-
};
|
|
2662
|
-
if (status !== undefined)
|
|
2663
|
-
respondEnvelope.status = status;
|
|
2664
|
-
if (message !== undefined)
|
|
2665
|
-
respondEnvelope.message = message;
|
|
2666
|
-
if (code !== undefined)
|
|
2667
|
-
respondEnvelope.code = code;
|
|
2668
|
-
if (meta !== undefined)
|
|
2669
|
-
respondEnvelope.meta = meta;
|
|
2670
|
-
ctx.trace?.({
|
|
2671
|
-
type: "tool.call",
|
|
2672
|
-
runId,
|
|
2673
|
-
actionCallId: call.id,
|
|
2674
|
-
name: call.name,
|
|
2675
|
-
args: call.args,
|
|
2676
|
-
toolKind: "internal",
|
|
2677
|
-
parentActionCallId: actionCallId,
|
|
2678
|
-
});
|
|
2679
|
-
const toolContent = JSON.stringify(call.args ?? {});
|
|
2680
|
-
appendedMessages.push({
|
|
2681
|
-
role: "assistant",
|
|
2682
|
-
content: null,
|
|
2683
|
-
tool_calls: [{
|
|
2684
|
-
id: call.id,
|
|
2685
|
-
type: "function",
|
|
2686
|
-
function: {
|
|
2687
|
-
name: call.name,
|
|
2688
|
-
arguments: JSON.stringify(call.args ?? {}),
|
|
2689
|
-
},
|
|
2690
|
-
}],
|
|
2691
|
-
});
|
|
2692
|
-
appendedMessages.push({
|
|
2693
|
-
role: "tool",
|
|
2694
|
-
tool_call_id: call.id,
|
|
2695
|
-
name: call.name,
|
|
2696
|
-
content: toolContent,
|
|
2697
|
-
});
|
|
2698
|
-
respondValue = respondEnvelope;
|
|
2699
|
-
responded = true;
|
|
2700
|
-
ctx.trace?.({
|
|
2701
|
-
type: "tool.result",
|
|
2702
|
-
runId,
|
|
2703
|
-
actionCallId: call.id,
|
|
2704
|
-
name: call.name,
|
|
2705
|
-
result: respondEnvelope,
|
|
2706
|
-
toolKind: "internal",
|
|
2707
|
-
parentActionCallId: actionCallId,
|
|
2708
|
-
});
|
|
2709
|
-
continue;
|
|
3810
|
+
if (REMOVED_LEGACY_RESPONSE_TOOL_NAMES.has(call.name)) {
|
|
3811
|
+
throw legacyResponseToolMigrationError(`Model emitted removed synthetic tool "${call.name}" for deck ${deck.path}.`);
|
|
2710
3812
|
}
|
|
2711
|
-
if (
|
|
2712
|
-
|
|
2713
|
-
? call.args.status
|
|
2714
|
-
: undefined;
|
|
2715
|
-
const messageText = typeof call.args?.message === "string"
|
|
2716
|
-
? call.args.message
|
|
2717
|
-
: undefined;
|
|
2718
|
-
const code = typeof call.args?.code === "string"
|
|
2719
|
-
? call.args.code
|
|
2720
|
-
: undefined;
|
|
2721
|
-
const meta = (call.args?.meta &&
|
|
2722
|
-
typeof call.args.meta === "object" &&
|
|
2723
|
-
call.args.meta !== null)
|
|
2724
|
-
? call.args.meta
|
|
2725
|
-
: undefined;
|
|
2726
|
-
const payload = call.args?.payload;
|
|
2727
|
-
ctx.trace?.({
|
|
2728
|
-
type: "tool.call",
|
|
2729
|
-
runId,
|
|
2730
|
-
actionCallId: call.id,
|
|
2731
|
-
name: call.name,
|
|
2732
|
-
args: call.args,
|
|
2733
|
-
toolKind: "internal",
|
|
2734
|
-
parentActionCallId: actionCallId,
|
|
2735
|
-
});
|
|
2736
|
-
const toolContent = JSON.stringify(call.args ?? {});
|
|
2737
|
-
appendedMessages.push({
|
|
2738
|
-
role: "assistant",
|
|
2739
|
-
content: null,
|
|
2740
|
-
tool_calls: [{
|
|
2741
|
-
id: call.id,
|
|
2742
|
-
type: "function",
|
|
2743
|
-
function: {
|
|
2744
|
-
name: call.name,
|
|
2745
|
-
arguments: JSON.stringify(call.args ?? {}),
|
|
2746
|
-
},
|
|
2747
|
-
}],
|
|
2748
|
-
});
|
|
2749
|
-
appendedMessages.push({
|
|
2750
|
-
role: "tool",
|
|
2751
|
-
tool_call_id: call.id,
|
|
2752
|
-
name: call.name,
|
|
2753
|
-
content: toolContent,
|
|
2754
|
-
});
|
|
2755
|
-
const signal = { __gambitEnd: true };
|
|
2756
|
-
if (status !== undefined)
|
|
2757
|
-
signal.status = status;
|
|
2758
|
-
if (messageText !== undefined)
|
|
2759
|
-
signal.message = messageText;
|
|
2760
|
-
if (code !== undefined)
|
|
2761
|
-
signal.code = code;
|
|
2762
|
-
if (meta !== undefined)
|
|
2763
|
-
signal.meta = meta;
|
|
2764
|
-
if (payload !== undefined)
|
|
2765
|
-
signal.payload = payload;
|
|
2766
|
-
endSignal = signal;
|
|
2767
|
-
ctx.trace?.({
|
|
2768
|
-
type: "tool.result",
|
|
2769
|
-
runId,
|
|
2770
|
-
actionCallId: call.id,
|
|
2771
|
-
name: call.name,
|
|
2772
|
-
result: signal,
|
|
2773
|
-
toolKind: "internal",
|
|
2774
|
-
parentActionCallId: actionCallId,
|
|
2775
|
-
});
|
|
2776
|
-
continue;
|
|
3813
|
+
if (isContextToolName(call.name)) {
|
|
3814
|
+
throw syntheticContextToolInvocationError(deck.path);
|
|
2777
3815
|
}
|
|
2778
3816
|
const actionRef = deck.actionDecks.find((a) => a.name === call.name);
|
|
2779
3817
|
const toolKind = actionRef ? "action" : "external";
|
|
@@ -2815,6 +3853,7 @@ async function runLlmDeck(ctx) {
|
|
|
2815
3853
|
defaultModel: ctx.defaultModel,
|
|
2816
3854
|
modelOverride: ctx.modelOverride,
|
|
2817
3855
|
trace: ctx.trace,
|
|
3856
|
+
stream: ctx.stream,
|
|
2818
3857
|
onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
|
|
2819
3858
|
? wrappedOnStreamText
|
|
2820
3859
|
: undefined,
|
|
@@ -2830,7 +3869,14 @@ async function runLlmDeck(ctx) {
|
|
|
2830
3869
|
runDeadlineMs: ctx.runDeadlineMs,
|
|
2831
3870
|
workerSandbox: ctx.workerSandbox,
|
|
2832
3871
|
signal: ctx.signal,
|
|
3872
|
+
intermediateOutputAllow: ctx.intermediateOutputAllow,
|
|
3873
|
+
onIntermediateOutputItem: ctx.onIntermediateOutputItem,
|
|
3874
|
+
intermediateOutputErrorContext: ctx.intermediateOutputErrorContext,
|
|
2833
3875
|
onTool: ctx.onTool,
|
|
3876
|
+
emitIntermediateOutputItem: intermediateEmitter
|
|
3877
|
+
? (item, source) => intermediateEmitter.emitOutputItem(item, source)
|
|
3878
|
+
: undefined,
|
|
3879
|
+
asyncActionJobs,
|
|
2834
3880
|
});
|
|
2835
3881
|
ctx.trace?.({
|
|
2836
3882
|
type: "tool.result",
|
|
@@ -2880,30 +3926,9 @@ async function runLlmDeck(ctx) {
|
|
|
2880
3926
|
const state = computeState(result.updatedState);
|
|
2881
3927
|
ctx.onStateUpdate(state);
|
|
2882
3928
|
}
|
|
2883
|
-
if (endSignal) {
|
|
2884
|
-
ctx.trace?.({
|
|
2885
|
-
type: "deck.end",
|
|
2886
|
-
runId,
|
|
2887
|
-
deckPath: deck.path,
|
|
2888
|
-
actionCallId,
|
|
2889
|
-
parentActionCallId: ctx.parentActionCallId,
|
|
2890
|
-
});
|
|
2891
|
-
return endSignal;
|
|
2892
|
-
}
|
|
2893
|
-
if (responded) {
|
|
2894
|
-
ctx.trace?.({
|
|
2895
|
-
type: "deck.end",
|
|
2896
|
-
runId,
|
|
2897
|
-
deckPath: deck.path,
|
|
2898
|
-
actionCallId,
|
|
2899
|
-
parentActionCallId: ctx.parentActionCallId,
|
|
2900
|
-
});
|
|
2901
|
-
return respondValue;
|
|
2902
|
-
}
|
|
2903
3929
|
continue;
|
|
2904
3930
|
}
|
|
2905
|
-
if (
|
|
2906
|
-
result.finishReason === "stop" &&
|
|
3931
|
+
if (result.finishReason === "stop" &&
|
|
2907
3932
|
(message.content === null || message.content === undefined) &&
|
|
2908
3933
|
(!result.toolCalls || result.toolCalls.length === 0)) {
|
|
2909
3934
|
message = { ...message, content: "" };
|
|
@@ -2934,30 +3959,34 @@ async function runLlmDeck(ctx) {
|
|
|
2934
3959
|
.content,
|
|
2935
3960
|
});
|
|
2936
3961
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
if (respondEnabled && result.finishReason === "stop") {
|
|
2950
|
-
continue;
|
|
3962
|
+
const validated = validateOutput(deck, message.content, depth === 0);
|
|
3963
|
+
intermediateEmitter?.complete();
|
|
3964
|
+
ctx.trace?.({
|
|
3965
|
+
type: "deck.end",
|
|
3966
|
+
runId,
|
|
3967
|
+
deckPath: deck.path,
|
|
3968
|
+
actionCallId,
|
|
3969
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3970
|
+
});
|
|
3971
|
+
return validated;
|
|
2951
3972
|
}
|
|
2952
3973
|
if (passes >= guardrails.maxPasses) {
|
|
2953
3974
|
throw new Error("Max passes exceeded without completing");
|
|
2954
3975
|
}
|
|
2955
3976
|
}
|
|
3977
|
+
throw new Error("Model did not complete within guardrails");
|
|
3978
|
+
}
|
|
3979
|
+
catch (err) {
|
|
3980
|
+
intermediateEmitter?.fail(err);
|
|
3981
|
+
throw err;
|
|
2956
3982
|
}
|
|
2957
3983
|
finally {
|
|
3984
|
+
asyncActionJobs.cancelLiveJobs({
|
|
3985
|
+
code: "async_job_parent_ended",
|
|
3986
|
+
message: "Parent run ended before async action job reached terminal state.",
|
|
3987
|
+
});
|
|
2958
3988
|
idleController.stop();
|
|
2959
3989
|
}
|
|
2960
|
-
throw new Error("Model did not complete within guardrails");
|
|
2961
3990
|
}
|
|
2962
3991
|
async function handleToolCall(call, ctx) {
|
|
2963
3992
|
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
@@ -2965,6 +3994,7 @@ async function handleToolCall(call, ctx) {
|
|
|
2965
3994
|
deckPath: ctx.parentDeck.path,
|
|
2966
3995
|
actionName: call.name,
|
|
2967
3996
|
};
|
|
3997
|
+
let intermediateOutputView;
|
|
2968
3998
|
const baseComplete = (payload) => JSON.stringify({
|
|
2969
3999
|
runId: ctx.runId,
|
|
2970
4000
|
actionCallId: call.id,
|
|
@@ -2975,6 +4005,9 @@ async function handleToolCall(call, ctx) {
|
|
|
2975
4005
|
message: payload.message,
|
|
2976
4006
|
code: payload.code,
|
|
2977
4007
|
meta: payload.meta,
|
|
4008
|
+
...(intermediateOutputView
|
|
4009
|
+
? { intermediateOutput: intermediateOutputView }
|
|
4010
|
+
: {}),
|
|
2978
4011
|
});
|
|
2979
4012
|
const extraMessages = [];
|
|
2980
4013
|
const started = performance.now();
|
|
@@ -2988,6 +4021,98 @@ async function handleToolCall(call, ctx) {
|
|
|
2988
4021
|
message,
|
|
2989
4022
|
}),
|
|
2990
4023
|
});
|
|
4024
|
+
if (call.name === BUILTIN_TOOL_EMIT_OUTPUT_ITEM) {
|
|
4025
|
+
if (!ctx.emitIntermediateOutputItem) {
|
|
4026
|
+
return {
|
|
4027
|
+
toolContent: baseComplete({
|
|
4028
|
+
status: 400,
|
|
4029
|
+
code: "invalid_tool_context",
|
|
4030
|
+
message: `${BUILTIN_TOOL_EMIT_OUTPUT_ITEM} is only available inside child LLM action decks`,
|
|
4031
|
+
}),
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
let item;
|
|
4035
|
+
try {
|
|
4036
|
+
item = parseEmitOutputItemArgs(call.args);
|
|
4037
|
+
}
|
|
4038
|
+
catch (err) {
|
|
4039
|
+
return {
|
|
4040
|
+
toolContent: baseComplete({
|
|
4041
|
+
status: 400,
|
|
4042
|
+
code: "invalid_input",
|
|
4043
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4044
|
+
}),
|
|
4045
|
+
};
|
|
4046
|
+
}
|
|
4047
|
+
try {
|
|
4048
|
+
ctx.emitIntermediateOutputItem(item, `tool.${BUILTIN_TOOL_EMIT_OUTPUT_ITEM}`);
|
|
4049
|
+
}
|
|
4050
|
+
catch (err) {
|
|
4051
|
+
const errorCode = typeof err?.code === "string"
|
|
4052
|
+
? err.code
|
|
4053
|
+
: "invalid_input";
|
|
4054
|
+
return {
|
|
4055
|
+
toolContent: baseComplete({
|
|
4056
|
+
status: errorCode === INTERMEDIATE_OUTPUT_DISALLOWED_CODE
|
|
4057
|
+
? 403
|
|
4058
|
+
: 400,
|
|
4059
|
+
code: errorCode,
|
|
4060
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4061
|
+
}),
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
return {
|
|
4065
|
+
toolContent: baseComplete({
|
|
4066
|
+
status: 200,
|
|
4067
|
+
payload: { emitted: true, type: item.type },
|
|
4068
|
+
}),
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
if (call.name === BUILTIN_TOOL_CONSUME_ASYNC_ACTION) {
|
|
4072
|
+
if (!ctx.asyncActionJobs) {
|
|
4073
|
+
return {
|
|
4074
|
+
toolContent: baseComplete({
|
|
4075
|
+
status: 400,
|
|
4076
|
+
code: "invalid_tool_context",
|
|
4077
|
+
message: `${BUILTIN_TOOL_CONSUME_ASYNC_ACTION} is unavailable for this deck`,
|
|
4078
|
+
}),
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
let parsedArgs;
|
|
4082
|
+
try {
|
|
4083
|
+
parsedArgs = parseConsumeAsyncActionArgs(call.args);
|
|
4084
|
+
}
|
|
4085
|
+
catch (err) {
|
|
4086
|
+
return {
|
|
4087
|
+
toolContent: baseComplete({
|
|
4088
|
+
status: 400,
|
|
4089
|
+
code: "invalid_input",
|
|
4090
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4091
|
+
}),
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
const consumed = await ctx.asyncActionJobs.consume({
|
|
4095
|
+
...parsedArgs,
|
|
4096
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
4097
|
+
signal: ctx.signal,
|
|
4098
|
+
});
|
|
4099
|
+
if (!consumed.ok) {
|
|
4100
|
+
return {
|
|
4101
|
+
toolContent: baseComplete({
|
|
4102
|
+
status: consumed.status,
|
|
4103
|
+
code: consumed.code,
|
|
4104
|
+
message: consumed.message,
|
|
4105
|
+
payload: consumed.payload,
|
|
4106
|
+
}),
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
return {
|
|
4110
|
+
toolContent: baseComplete({
|
|
4111
|
+
status: 200,
|
|
4112
|
+
payload: consumed.payload,
|
|
4113
|
+
}),
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
2991
4116
|
if (call.name === BUILTIN_TOOL_READ_FILE) {
|
|
2992
4117
|
let targetPath;
|
|
2993
4118
|
try {
|
|
@@ -3452,6 +4577,139 @@ async function handleToolCall(call, ctx) {
|
|
|
3452
4577
|
};
|
|
3453
4578
|
}
|
|
3454
4579
|
}
|
|
4580
|
+
const inheritedIntermediateOutputAllow = ctx.intermediateOutputAllow ?? true;
|
|
4581
|
+
const actionDeclaresIntermediateOutput = action.intermediateOutput?.emit === "allow";
|
|
4582
|
+
const actionIntermediateOutputAllow = inheritedIntermediateOutputAllow &&
|
|
4583
|
+
actionDeclaresIntermediateOutput;
|
|
4584
|
+
const actionIntermediateOutputItems = [];
|
|
4585
|
+
const actionIntermediateOutputErrorContext = inheritedIntermediateOutputAllow
|
|
4586
|
+
? {
|
|
4587
|
+
actionName: action.name,
|
|
4588
|
+
parentDeckPath: ctx.parentDeck.path,
|
|
4589
|
+
}
|
|
4590
|
+
: (ctx.intermediateOutputErrorContext ?? {
|
|
4591
|
+
actionName: action.name,
|
|
4592
|
+
parentDeckPath: ctx.parentDeck.path,
|
|
4593
|
+
});
|
|
4594
|
+
const actionIntermediateOutputCapture = actionIntermediateOutputAllow
|
|
4595
|
+
? (emission) => {
|
|
4596
|
+
actionIntermediateOutputItems.push(emission);
|
|
4597
|
+
ctx.onIntermediateOutputItem?.(emission);
|
|
4598
|
+
}
|
|
4599
|
+
: undefined;
|
|
4600
|
+
const actionAsyncStartAllowed = action.asyncStart?.mode === "allow";
|
|
4601
|
+
if (actionAsyncStartAllowed) {
|
|
4602
|
+
if (!ctx.asyncActionJobs) {
|
|
4603
|
+
return {
|
|
4604
|
+
toolContent: baseComplete({
|
|
4605
|
+
status: 500,
|
|
4606
|
+
code: "async_job_unavailable",
|
|
4607
|
+
message: "Async action start is configured but async job controller is unavailable",
|
|
4608
|
+
}),
|
|
4609
|
+
};
|
|
4610
|
+
}
|
|
4611
|
+
const job = ctx.asyncActionJobs.startJob({
|
|
4612
|
+
actionName: action.name,
|
|
4613
|
+
actionPath: action.path,
|
|
4614
|
+
});
|
|
4615
|
+
const controller = new AbortController();
|
|
4616
|
+
const childSignal = mergeAbortSignals(ctx.signal, controller.signal);
|
|
4617
|
+
const asyncIntermediateOutputCapture = actionIntermediateOutputAllow
|
|
4618
|
+
? (emission) => {
|
|
4619
|
+
job.appendIntermediateOutput(emission);
|
|
4620
|
+
ctx.onIntermediateOutputItem?.(emission);
|
|
4621
|
+
}
|
|
4622
|
+
: undefined;
|
|
4623
|
+
const runPromise = (async () => {
|
|
4624
|
+
try {
|
|
4625
|
+
const result = await runDeck({
|
|
4626
|
+
path: action.path,
|
|
4627
|
+
input: actionInput,
|
|
4628
|
+
modelProvider: ctx.modelProvider,
|
|
4629
|
+
isRoot: false,
|
|
4630
|
+
guardrails: ctx.guardrails,
|
|
4631
|
+
depth: ctx.depth + 1,
|
|
4632
|
+
parentActionCallId: call.id,
|
|
4633
|
+
runId: ctx.runId,
|
|
4634
|
+
defaultModel: ctx.defaultModel,
|
|
4635
|
+
modelOverride: ctx.modelOverride,
|
|
4636
|
+
trace: ctx.trace,
|
|
4637
|
+
stream: ctx.stream,
|
|
4638
|
+
onStreamText: ctx.onStreamText,
|
|
4639
|
+
responsesMode: ctx.responsesMode,
|
|
4640
|
+
initialUserMessage: undefined,
|
|
4641
|
+
intermediateOutputAllow: actionIntermediateOutputAllow,
|
|
4642
|
+
onIntermediateOutputItem: asyncIntermediateOutputCapture,
|
|
4643
|
+
intermediateOutputErrorContext: actionIntermediateOutputErrorContext,
|
|
4644
|
+
parentPermissions: ctx.permissions,
|
|
4645
|
+
referencePermissions: action.permissions,
|
|
4646
|
+
referencePermissionsBaseDir: path.dirname(ctx.parentDeck.path),
|
|
4647
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
4648
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
4649
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
4650
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
4651
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
4652
|
+
workerSandbox: ctx.workerSandbox,
|
|
4653
|
+
signal: childSignal,
|
|
4654
|
+
onTool: ctx.onTool,
|
|
4655
|
+
});
|
|
4656
|
+
const normalized = normalizeChildResult(result);
|
|
4657
|
+
if (action.responseSchema) {
|
|
4658
|
+
normalized.payload = validateWithSchema(action.responseSchema, normalized.payload);
|
|
4659
|
+
}
|
|
4660
|
+
job.complete(normalized);
|
|
4661
|
+
}
|
|
4662
|
+
catch (err) {
|
|
4663
|
+
const handled = await maybeHandleError({
|
|
4664
|
+
err,
|
|
4665
|
+
call,
|
|
4666
|
+
ctx,
|
|
4667
|
+
action,
|
|
4668
|
+
});
|
|
4669
|
+
if (handled) {
|
|
4670
|
+
const handledEnvelope = parseToolContentEnvelope(handled.toolContent);
|
|
4671
|
+
job.fail({
|
|
4672
|
+
state: "failed",
|
|
4673
|
+
status: handledEnvelope?.status,
|
|
4674
|
+
payload: handledEnvelope?.payload,
|
|
4675
|
+
message: handledEnvelope?.message ??
|
|
4676
|
+
(err instanceof Error ? err.message : String(err)),
|
|
4677
|
+
code: handledEnvelope?.code,
|
|
4678
|
+
meta: handledEnvelope?.meta,
|
|
4679
|
+
});
|
|
4680
|
+
return;
|
|
4681
|
+
}
|
|
4682
|
+
if (isRunCanceledError(err)) {
|
|
4683
|
+
job.fail({
|
|
4684
|
+
state: "canceled",
|
|
4685
|
+
status: 499,
|
|
4686
|
+
code: typeof err?.code === "string"
|
|
4687
|
+
? err.code
|
|
4688
|
+
: "run_canceled",
|
|
4689
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4690
|
+
});
|
|
4691
|
+
return;
|
|
4692
|
+
}
|
|
4693
|
+
job.fail({
|
|
4694
|
+
state: "failed",
|
|
4695
|
+
code: typeof err?.code === "string"
|
|
4696
|
+
? err.code
|
|
4697
|
+
: undefined,
|
|
4698
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4699
|
+
});
|
|
4700
|
+
}
|
|
4701
|
+
})();
|
|
4702
|
+
job.bindLiveRun(runPromise, controller);
|
|
4703
|
+
return {
|
|
4704
|
+
toolContent: baseComplete({
|
|
4705
|
+
status: 202,
|
|
4706
|
+
code: "async_action_started",
|
|
4707
|
+
payload: {
|
|
4708
|
+
job: job.handle,
|
|
4709
|
+
},
|
|
4710
|
+
}),
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
3455
4713
|
const busyCfg = ctx.parentDeck.handlers?.onBusy ??
|
|
3456
4714
|
ctx.parentDeck.handlers?.onInterval;
|
|
3457
4715
|
const busyDelay = busyCfg?.delayMs ?? DEFAULT_STATUS_DELAY_MS;
|
|
@@ -3479,6 +4737,9 @@ async function handleToolCall(call, ctx) {
|
|
|
3479
4737
|
onStreamText: ctx.onStreamText,
|
|
3480
4738
|
responsesMode: ctx.responsesMode,
|
|
3481
4739
|
initialUserMessage: undefined,
|
|
4740
|
+
intermediateOutputAllow: actionIntermediateOutputAllow,
|
|
4741
|
+
onIntermediateOutputItem: actionIntermediateOutputCapture,
|
|
4742
|
+
intermediateOutputErrorContext: actionIntermediateOutputErrorContext,
|
|
3482
4743
|
parentPermissions: ctx.permissions,
|
|
3483
4744
|
referencePermissions: action.permissions,
|
|
3484
4745
|
referencePermissionsBaseDir: path.dirname(ctx.parentDeck.path),
|
|
@@ -3523,6 +4784,9 @@ async function handleToolCall(call, ctx) {
|
|
|
3523
4784
|
onStreamText: ctx.onStreamText,
|
|
3524
4785
|
responsesMode: ctx.responsesMode,
|
|
3525
4786
|
initialUserMessage: undefined,
|
|
4787
|
+
intermediateOutputAllow: actionIntermediateOutputAllow,
|
|
4788
|
+
onIntermediateOutputItem: actionIntermediateOutputCapture,
|
|
4789
|
+
intermediateOutputErrorContext: actionIntermediateOutputErrorContext,
|
|
3526
4790
|
permissions: ctx.permissions,
|
|
3527
4791
|
workspacePermissions: ctx.workspacePermissions,
|
|
3528
4792
|
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
@@ -3592,6 +4856,12 @@ async function handleToolCall(call, ctx) {
|
|
|
3592
4856
|
if (action.responseSchema) {
|
|
3593
4857
|
normalized.payload = validateWithSchema(action.responseSchema, normalized.payload);
|
|
3594
4858
|
}
|
|
4859
|
+
if (actionIntermediateOutputAllow) {
|
|
4860
|
+
intermediateOutputView = {
|
|
4861
|
+
policy: "allow",
|
|
4862
|
+
items: actionIntermediateOutputItems,
|
|
4863
|
+
};
|
|
4864
|
+
}
|
|
3595
4865
|
const toolContent = baseComplete(normalized);
|
|
3596
4866
|
if (busyCfg?.path) {
|
|
3597
4867
|
const elapsedFromAction = performance.now() - started;
|
|
@@ -3615,6 +4885,9 @@ async function handleToolCall(call, ctx) {
|
|
|
3615
4885
|
onStreamText: ctx.onStreamText,
|
|
3616
4886
|
responsesMode: ctx.responsesMode,
|
|
3617
4887
|
initialUserMessage: undefined,
|
|
4888
|
+
intermediateOutputAllow: actionIntermediateOutputAllow,
|
|
4889
|
+
onIntermediateOutputItem: actionIntermediateOutputCapture,
|
|
4890
|
+
intermediateOutputErrorContext: actionIntermediateOutputErrorContext,
|
|
3618
4891
|
permissions: ctx.permissions,
|
|
3619
4892
|
workspacePermissions: ctx.workspacePermissions,
|
|
3620
4893
|
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
@@ -3635,24 +4908,6 @@ async function handleToolCall(call, ctx) {
|
|
|
3635
4908
|
}
|
|
3636
4909
|
}
|
|
3637
4910
|
}
|
|
3638
|
-
const completeEventId = randomId("event");
|
|
3639
|
-
extraMessages.push({
|
|
3640
|
-
role: "assistant",
|
|
3641
|
-
content: null,
|
|
3642
|
-
tool_calls: [{
|
|
3643
|
-
id: completeEventId,
|
|
3644
|
-
type: "function",
|
|
3645
|
-
function: {
|
|
3646
|
-
name: GAMBIT_TOOL_COMPLETE,
|
|
3647
|
-
arguments: toolContent,
|
|
3648
|
-
},
|
|
3649
|
-
}],
|
|
3650
|
-
}, {
|
|
3651
|
-
role: "tool",
|
|
3652
|
-
tool_call_id: completeEventId,
|
|
3653
|
-
name: GAMBIT_TOOL_COMPLETE,
|
|
3654
|
-
content: toolContent,
|
|
3655
|
-
});
|
|
3656
4911
|
stopBusy();
|
|
3657
4912
|
ctx.idle?.touch();
|
|
3658
4913
|
return { toolContent, extraMessages };
|
|
@@ -3671,6 +4926,27 @@ function normalizeChildResult(result) {
|
|
|
3671
4926
|
}
|
|
3672
4927
|
return { payload: result };
|
|
3673
4928
|
}
|
|
4929
|
+
function parseToolContentEnvelope(content) {
|
|
4930
|
+
try {
|
|
4931
|
+
const parsed = JSON.parse(content);
|
|
4932
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4933
|
+
return undefined;
|
|
4934
|
+
}
|
|
4935
|
+
const rec = parsed;
|
|
4936
|
+
return {
|
|
4937
|
+
status: typeof rec.status === "number" ? rec.status : undefined,
|
|
4938
|
+
payload: rec.payload,
|
|
4939
|
+
message: typeof rec.message === "string" ? rec.message : undefined,
|
|
4940
|
+
code: typeof rec.code === "string" ? rec.code : undefined,
|
|
4941
|
+
meta: rec.meta && typeof rec.meta === "object" && !Array.isArray(rec.meta)
|
|
4942
|
+
? rec.meta
|
|
4943
|
+
: undefined,
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
4946
|
+
catch {
|
|
4947
|
+
return undefined;
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
3674
4950
|
async function runBusyHandler(args) {
|
|
3675
4951
|
try {
|
|
3676
4952
|
ensureRunActive(args.runDeadlineMs, args.signal);
|
|
@@ -3701,6 +4977,9 @@ async function runBusyHandler(args) {
|
|
|
3701
4977
|
responsesMode: args.responsesMode,
|
|
3702
4978
|
initialUserMessage: args.initialUserMessage,
|
|
3703
4979
|
inputProvided: true,
|
|
4980
|
+
intermediateOutputAllow: args.intermediateOutputAllow,
|
|
4981
|
+
onIntermediateOutputItem: args.onIntermediateOutputItem,
|
|
4982
|
+
intermediateOutputErrorContext: args.intermediateOutputErrorContext,
|
|
3704
4983
|
parentPermissions: args.permissions,
|
|
3705
4984
|
workspacePermissions: args.workspacePermissions,
|
|
3706
4985
|
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
@@ -3787,6 +5066,9 @@ function createIdleController(args) {
|
|
|
3787
5066
|
stream: args.stream,
|
|
3788
5067
|
onStreamText: args.onStreamText,
|
|
3789
5068
|
responsesMode: args.responsesMode,
|
|
5069
|
+
intermediateOutputAllow: args.intermediateOutputAllow,
|
|
5070
|
+
onIntermediateOutputItem: args.onIntermediateOutputItem,
|
|
5071
|
+
intermediateOutputErrorContext: args.intermediateOutputErrorContext,
|
|
3790
5072
|
permissions: args.permissions,
|
|
3791
5073
|
workspacePermissions: args.workspacePermissions,
|
|
3792
5074
|
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
@@ -3862,6 +5144,9 @@ async function runIdleHandler(args) {
|
|
|
3862
5144
|
responsesMode: args.responsesMode,
|
|
3863
5145
|
initialUserMessage: undefined,
|
|
3864
5146
|
inputProvided: true,
|
|
5147
|
+
intermediateOutputAllow: args.intermediateOutputAllow,
|
|
5148
|
+
onIntermediateOutputItem: args.onIntermediateOutputItem,
|
|
5149
|
+
intermediateOutputErrorContext: args.intermediateOutputErrorContext,
|
|
3865
5150
|
parentPermissions: args.permissions,
|
|
3866
5151
|
workspacePermissions: args.workspacePermissions,
|
|
3867
5152
|
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
@@ -3938,6 +5223,9 @@ async function maybeHandleError(args) {
|
|
|
3938
5223
|
responsesMode: args.ctx.responsesMode,
|
|
3939
5224
|
initialUserMessage: undefined,
|
|
3940
5225
|
inputProvided: true,
|
|
5226
|
+
intermediateOutputAllow: args.ctx.intermediateOutputAllow,
|
|
5227
|
+
onIntermediateOutputItem: args.ctx.onIntermediateOutputItem,
|
|
5228
|
+
intermediateOutputErrorContext: args.ctx.intermediateOutputErrorContext,
|
|
3941
5229
|
parentPermissions: args.ctx.permissions,
|
|
3942
5230
|
workspacePermissions: args.ctx.workspacePermissions,
|
|
3943
5231
|
workspacePermissionsBaseDir: args.ctx.workspacePermissionsBaseDir,
|
|
@@ -3974,28 +5262,7 @@ async function maybeHandleError(args) {
|
|
|
3974
5262
|
code,
|
|
3975
5263
|
meta,
|
|
3976
5264
|
});
|
|
3977
|
-
|
|
3978
|
-
const extraMessages = [
|
|
3979
|
-
{
|
|
3980
|
-
role: "assistant",
|
|
3981
|
-
content: null,
|
|
3982
|
-
tool_calls: [{
|
|
3983
|
-
id: callId,
|
|
3984
|
-
type: "function",
|
|
3985
|
-
function: {
|
|
3986
|
-
name: GAMBIT_TOOL_COMPLETE,
|
|
3987
|
-
arguments: content,
|
|
3988
|
-
},
|
|
3989
|
-
}],
|
|
3990
|
-
},
|
|
3991
|
-
{
|
|
3992
|
-
role: "tool",
|
|
3993
|
-
tool_call_id: callId,
|
|
3994
|
-
name: GAMBIT_TOOL_COMPLETE,
|
|
3995
|
-
content,
|
|
3996
|
-
},
|
|
3997
|
-
];
|
|
3998
|
-
return { toolContent: content, extraMessages };
|
|
5265
|
+
return { toolContent: content };
|
|
3999
5266
|
}
|
|
4000
5267
|
catch {
|
|
4001
5268
|
// Fallback when the handler itself fails: still return a structured error envelope
|
|
@@ -4017,28 +5284,7 @@ async function maybeHandleError(args) {
|
|
|
4017
5284
|
code,
|
|
4018
5285
|
meta: { handlerFailed: true },
|
|
4019
5286
|
});
|
|
4020
|
-
|
|
4021
|
-
const extraMessages = [
|
|
4022
|
-
{
|
|
4023
|
-
role: "assistant",
|
|
4024
|
-
content: null,
|
|
4025
|
-
tool_calls: [{
|
|
4026
|
-
id: callId,
|
|
4027
|
-
type: "function",
|
|
4028
|
-
function: {
|
|
4029
|
-
name: GAMBIT_TOOL_COMPLETE,
|
|
4030
|
-
arguments: content,
|
|
4031
|
-
},
|
|
4032
|
-
}],
|
|
4033
|
-
},
|
|
4034
|
-
{
|
|
4035
|
-
role: "tool",
|
|
4036
|
-
tool_call_id: callId,
|
|
4037
|
-
name: GAMBIT_TOOL_COMPLETE,
|
|
4038
|
-
content,
|
|
4039
|
-
},
|
|
4040
|
-
];
|
|
4041
|
-
return { toolContent: content, extraMessages };
|
|
5287
|
+
return { toolContent: content };
|
|
4042
5288
|
}
|
|
4043
5289
|
}
|
|
4044
5290
|
function buildSystemPrompt(deck) {
|
|
@@ -4090,6 +5336,53 @@ function parseToolLimit(value, fallback, max) {
|
|
|
4090
5336
|
return fallback;
|
|
4091
5337
|
return Math.min(max, Math.max(1, Number(value)));
|
|
4092
5338
|
}
|
|
5339
|
+
function parseAsyncActionWaitMs(value) {
|
|
5340
|
+
if (value === undefined || value === null)
|
|
5341
|
+
return 0;
|
|
5342
|
+
if (!Number.isInteger(value)) {
|
|
5343
|
+
throw new Error("wait_ms must be a non-negative integer");
|
|
5344
|
+
}
|
|
5345
|
+
return Math.min(ASYNC_ACTION_JOB_MAX_WAIT_MS, Math.max(0, Number(value)));
|
|
5346
|
+
}
|
|
5347
|
+
function parseConsumeAsyncActionArgs(args) {
|
|
5348
|
+
const rawJobId = typeof args.job_id === "string"
|
|
5349
|
+
? args.job_id
|
|
5350
|
+
: typeof args.jobId === "string"
|
|
5351
|
+
? args.jobId
|
|
5352
|
+
: "";
|
|
5353
|
+
const jobId = rawJobId.trim();
|
|
5354
|
+
if (!jobId) {
|
|
5355
|
+
throw new Error("job_id is required");
|
|
5356
|
+
}
|
|
5357
|
+
const cursor = args.cursor === undefined ? 0 : args.cursor;
|
|
5358
|
+
if (!Number.isInteger(cursor) || Number(cursor) < 0) {
|
|
5359
|
+
throw new Error("cursor must be a non-negative integer");
|
|
5360
|
+
}
|
|
5361
|
+
const limit = parseToolLimit(args.limit, 32, 256);
|
|
5362
|
+
const waitMs = parseAsyncActionWaitMs(Object.hasOwn(args, "wait_ms") ? args.wait_ms : args.waitMs);
|
|
5363
|
+
return {
|
|
5364
|
+
jobId,
|
|
5365
|
+
cursor: Number(cursor),
|
|
5366
|
+
limit,
|
|
5367
|
+
waitMs,
|
|
5368
|
+
};
|
|
5369
|
+
}
|
|
5370
|
+
function parseEmitOutputItemArgs(args) {
|
|
5371
|
+
const rawItem = Object.hasOwn(args, "item") ? args.item : args;
|
|
5372
|
+
if (!rawItem || typeof rawItem !== "object" || Array.isArray(rawItem)) {
|
|
5373
|
+
throw new Error("item must be an object matching the OpenResponses response item shape");
|
|
5374
|
+
}
|
|
5375
|
+
const canonicalItem = canonicalizeJsonValue(rawItem);
|
|
5376
|
+
if (!canonicalItem || typeof canonicalItem !== "object" ||
|
|
5377
|
+
Array.isArray(canonicalItem)) {
|
|
5378
|
+
throw new Error("item must be an object matching the OpenResponses response item shape");
|
|
5379
|
+
}
|
|
5380
|
+
const type = canonicalItem.type;
|
|
5381
|
+
if (typeof type !== "string" || !type.trim()) {
|
|
5382
|
+
throw new Error("item.type must be a non-empty string");
|
|
5383
|
+
}
|
|
5384
|
+
return canonicalItem;
|
|
5385
|
+
}
|
|
4093
5386
|
function toStringArray(value) {
|
|
4094
5387
|
if (!Array.isArray(value))
|
|
4095
5388
|
return [];
|
|
@@ -4127,7 +5420,7 @@ function applySimplePatch(content, edits) {
|
|
|
4127
5420
|
}
|
|
4128
5421
|
return { next, applied };
|
|
4129
5422
|
}
|
|
4130
|
-
async function buildToolDefs(deck, permissions) {
|
|
5423
|
+
async function buildToolDefs(deck, permissions, options) {
|
|
4131
5424
|
const defs = [];
|
|
4132
5425
|
const addBuiltinTools = () => {
|
|
4133
5426
|
if (hasAnyScope(permissions.read)) {
|
|
@@ -4235,48 +5528,51 @@ async function buildToolDefs(deck, permissions) {
|
|
|
4235
5528
|
},
|
|
4236
5529
|
});
|
|
4237
5530
|
}
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
5531
|
+
if (options?.parentActionCallId !== undefined &&
|
|
5532
|
+
(options.intermediateOutputAllow ?? true)) {
|
|
5533
|
+
defs.push({
|
|
5534
|
+
type: "function",
|
|
5535
|
+
function: {
|
|
5536
|
+
name: BUILTIN_TOOL_EMIT_OUTPUT_ITEM,
|
|
5537
|
+
description: "Emit a supplemental output item to the parent before action completion.",
|
|
5538
|
+
parameters: {
|
|
5539
|
+
type: "object",
|
|
5540
|
+
properties: {
|
|
5541
|
+
item: {
|
|
5542
|
+
type: "object",
|
|
5543
|
+
additionalProperties: true,
|
|
5544
|
+
},
|
|
5545
|
+
},
|
|
5546
|
+
required: ["item"],
|
|
5547
|
+
additionalProperties: false,
|
|
4254
5548
|
},
|
|
4255
|
-
additionalProperties: true,
|
|
4256
5549
|
},
|
|
4257
|
-
}
|
|
4258
|
-
}
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
5550
|
+
});
|
|
5551
|
+
}
|
|
5552
|
+
if (options?.asyncActionJobs &&
|
|
5553
|
+
(options.asyncActionJobs.hasConfiguredAsyncActions ||
|
|
5554
|
+
options.asyncActionJobs.hasLiveJobs())) {
|
|
5555
|
+
defs.push({
|
|
5556
|
+
type: "function",
|
|
5557
|
+
function: {
|
|
5558
|
+
name: BUILTIN_TOOL_CONSUME_ASYNC_ACTION,
|
|
5559
|
+
description: "Consume ordered async action job events/results by cursor.",
|
|
5560
|
+
parameters: {
|
|
5561
|
+
type: "object",
|
|
5562
|
+
properties: {
|
|
5563
|
+
job_id: { type: "string" },
|
|
5564
|
+
cursor: { type: "number" },
|
|
5565
|
+
limit: { type: "number" },
|
|
5566
|
+
wait_ms: { type: "number" },
|
|
5567
|
+
},
|
|
5568
|
+
required: ["job_id"],
|
|
5569
|
+
additionalProperties: false,
|
|
4274
5570
|
},
|
|
4275
|
-
additionalProperties: true,
|
|
4276
5571
|
},
|
|
4277
|
-
}
|
|
4278
|
-
}
|
|
4279
|
-
}
|
|
5572
|
+
});
|
|
5573
|
+
}
|
|
5574
|
+
};
|
|
5575
|
+
addBuiltinTools();
|
|
4280
5576
|
for (const action of deck.actionDecks) {
|
|
4281
5577
|
if (isBuiltinTool(action.name)) {
|
|
4282
5578
|
throw new Error(`Action name ${action.name} conflicts with a built-in tool name`);
|