@bolt-foundry/gambit-core 0.8.1 → 0.8.5-rc.3
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 +60 -34
- package/cards/context.card.md +5 -5
- package/cards/generate-test-input.card.md +12 -0
- package/{script/deps/jsr.io/@std/collections/1.1.4 → esm/deps/jsr.io/@std/collections/1.1.5}/deep_merge.d.ts +2 -2
- package/esm/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.d.ts.map +1 -1
- package/esm/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.js +29 -19
- package/esm/deps/jsr.io/@std/toml/1.0.11/_parser.js +1 -1
- package/esm/mod.d.ts +20 -10
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +11 -5
- package/esm/schemas/graders/contexts/conversation.d.ts +22 -0
- package/esm/schemas/graders/contexts/conversation.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation.js +17 -0
- package/esm/schemas/graders/contexts/conversation.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/conversation.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation.zod.js +2 -0
- package/esm/schemas/graders/contexts/conversation_tools.d.ts +31 -0
- package/esm/schemas/graders/contexts/conversation_tools.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation_tools.js +25 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.js +2 -0
- package/esm/schemas/graders/contexts/tools.d.ts +4 -0
- package/esm/schemas/graders/contexts/tools.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/tools.js +3 -0
- package/esm/schemas/graders/contexts/tools.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/tools.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/tools.zod.js +2 -0
- package/esm/schemas/graders/contexts/turn.d.ts +10 -0
- package/esm/schemas/graders/contexts/turn.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn.js +8 -0
- package/esm/schemas/graders/contexts/turn.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/turn.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn.zod.js +2 -0
- package/esm/schemas/graders/contexts/turn_tools.d.ts +32 -0
- package/esm/schemas/graders/contexts/turn_tools.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn_tools.js +28 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.js +2 -0
- package/esm/schemas/graders/grader_output.d.ts +10 -0
- package/esm/schemas/graders/grader_output.d.ts.map +1 -0
- package/esm/schemas/graders/grader_output.js +8 -0
- package/esm/schemas/graders/grader_output.zod.d.ts +3 -0
- package/esm/schemas/graders/grader_output.zod.d.ts.map +1 -0
- package/esm/schemas/graders/grader_output.zod.js +2 -0
- package/esm/schemas/graders/respond.d.ts +12 -0
- package/esm/schemas/graders/respond.d.ts.map +1 -0
- package/esm/schemas/graders/respond.js +10 -0
- package/esm/schemas/graders/respond.zod.d.ts +3 -0
- package/esm/schemas/graders/respond.zod.d.ts.map +1 -0
- package/esm/schemas/graders/respond.zod.js +2 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.d.ts +5 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.js +5 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.d.ts +3 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.js +2 -0
- package/esm/schemas/scenarios/plain_chat_output.d.ts +5 -0
- package/esm/schemas/scenarios/plain_chat_output.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_output.js +4 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.d.ts +3 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.js +2 -0
- package/esm/src/builtins.d.ts +2 -0
- package/esm/src/builtins.d.ts.map +1 -1
- package/esm/src/builtins.js +45 -1
- package/esm/src/constants.d.ts +4 -0
- package/esm/src/constants.d.ts.map +1 -1
- package/esm/src/constants.js +5 -0
- package/esm/src/definitions.d.ts +5 -1
- package/esm/src/definitions.d.ts.map +1 -1
- package/esm/src/loader.d.ts.map +1 -1
- package/esm/src/loader.js +119 -13
- package/esm/src/markdown.d.ts.map +1 -1
- package/esm/src/markdown.js +222 -53
- package/esm/src/permissions.d.ts +143 -0
- package/esm/src/permissions.d.ts.map +1 -0
- package/esm/src/permissions.js +406 -0
- package/esm/src/render.d.ts.map +1 -1
- package/esm/src/render.js +22 -8
- package/esm/src/runtime.d.ts +28 -2
- package/esm/src/runtime.d.ts.map +1 -1
- package/esm/src/runtime.js +3051 -100
- package/esm/src/runtime_exec_host.d.ts +6 -0
- package/esm/src/runtime_exec_host.d.ts.map +1 -0
- package/esm/src/runtime_exec_host.js +17 -0
- package/esm/src/runtime_exec_host_contract.d.ts +23 -0
- package/esm/src/runtime_exec_host_contract.d.ts.map +1 -0
- package/esm/src/runtime_exec_host_contract.js +14 -0
- package/esm/src/runtime_exec_host_deno.d.ts +3 -0
- package/esm/src/runtime_exec_host_deno.d.ts.map +1 -0
- package/esm/src/runtime_exec_host_deno.js +35 -0
- package/esm/src/runtime_exec_host_unsupported.d.ts +3 -0
- package/esm/src/runtime_exec_host_unsupported.d.ts.map +1 -0
- package/esm/src/runtime_exec_host_unsupported.js +8 -0
- package/esm/src/runtime_worker_host.d.ts +6 -0
- package/esm/src/runtime_worker_host.d.ts.map +1 -0
- package/esm/src/runtime_worker_host.js +17 -0
- package/esm/src/runtime_worker_host_contract.d.ts +33 -0
- package/esm/src/runtime_worker_host_contract.d.ts.map +1 -0
- package/esm/src/runtime_worker_host_contract.js +14 -0
- package/esm/src/runtime_worker_host_deno.d.ts +3 -0
- package/esm/src/runtime_worker_host_deno.d.ts.map +1 -0
- package/esm/src/runtime_worker_host_deno.js +26 -0
- package/esm/src/runtime_worker_host_unsupported.d.ts +3 -0
- package/esm/src/runtime_worker_host_unsupported.d.ts.map +1 -0
- package/esm/src/runtime_worker_host_unsupported.js +8 -0
- package/esm/src/state.d.ts +4 -1
- package/esm/src/state.d.ts.map +1 -1
- package/esm/src/state.js +48 -2
- package/esm/src/types.d.ts +381 -1
- package/esm/src/types.d.ts.map +1 -1
- package/esm/src/types.js +102 -1
- package/package.json +73 -2
- package/schemas/graders/contexts/conversation.ts +32 -9
- package/schemas/graders/contexts/conversation.zod.ts +1 -0
- package/schemas/graders/contexts/conversation_tools.ts +63 -0
- package/schemas/graders/contexts/conversation_tools.zod.ts +1 -0
- package/schemas/graders/contexts/tools.ts +5 -0
- package/schemas/graders/contexts/tools.zod.ts +1 -0
- package/schemas/graders/contexts/turn.ts +8 -1
- package/schemas/graders/contexts/turn.zod.ts +1 -0
- package/schemas/graders/contexts/turn_tools.ts +63 -0
- package/schemas/graders/contexts/turn_tools.zod.ts +1 -0
- package/schemas/graders/grader_output.ts +15 -0
- package/schemas/graders/grader_output.zod.ts +1 -0
- package/schemas/graders/respond.ts +13 -3
- package/schemas/graders/respond.zod.ts +1 -0
- package/schemas/scenarios/plain_chat_input_optional.ts +6 -0
- package/schemas/scenarios/plain_chat_input_optional.zod.ts +1 -0
- package/schemas/scenarios/plain_chat_output.ts +5 -0
- package/schemas/scenarios/plain_chat_output.zod.ts +1 -0
- package/{esm/deps/jsr.io/@std/collections/1.1.4 → script/deps/jsr.io/@std/collections/1.1.5}/deep_merge.d.ts +2 -2
- package/script/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.d.ts.map +1 -1
- package/script/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.js +29 -19
- package/script/deps/jsr.io/@std/toml/1.0.11/_parser.js +1 -1
- package/script/mod.d.ts +20 -10
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +25 -9
- package/script/schemas/graders/contexts/conversation.d.ts +22 -0
- package/script/schemas/graders/contexts/conversation.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation.js +20 -0
- package/script/schemas/graders/contexts/conversation.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/conversation.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation.zod.js +9 -0
- package/script/schemas/graders/contexts/conversation_tools.d.ts +31 -0
- package/script/schemas/graders/contexts/conversation_tools.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation_tools.js +28 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.js +9 -0
- package/script/schemas/graders/contexts/tools.d.ts +4 -0
- package/script/schemas/graders/contexts/tools.d.ts.map +1 -0
- package/script/schemas/graders/contexts/tools.js +12 -0
- package/script/schemas/graders/contexts/tools.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/tools.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/tools.zod.js +9 -0
- package/script/schemas/graders/contexts/turn.d.ts +10 -0
- package/script/schemas/graders/contexts/turn.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn.js +10 -0
- package/script/schemas/graders/contexts/turn.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/turn.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn.zod.js +9 -0
- package/script/schemas/graders/contexts/turn_tools.d.ts +32 -0
- package/script/schemas/graders/contexts/turn_tools.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn_tools.js +31 -0
- package/script/schemas/graders/contexts/turn_tools.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/turn_tools.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn_tools.zod.js +9 -0
- package/script/schemas/graders/grader_output.d.ts +10 -0
- package/script/schemas/graders/grader_output.d.ts.map +1 -0
- package/script/schemas/graders/grader_output.js +10 -0
- package/script/schemas/graders/grader_output.zod.d.ts +3 -0
- package/script/schemas/graders/grader_output.zod.d.ts.map +1 -0
- package/script/schemas/graders/grader_output.zod.js +9 -0
- package/script/schemas/graders/respond.d.ts +12 -0
- package/script/schemas/graders/respond.d.ts.map +1 -0
- package/script/schemas/graders/respond.js +12 -0
- package/script/schemas/graders/respond.zod.d.ts +3 -0
- package/script/schemas/graders/respond.zod.d.ts.map +1 -0
- package/script/schemas/graders/respond.zod.js +9 -0
- package/script/schemas/scenarios/plain_chat_input_optional.d.ts +5 -0
- package/script/schemas/scenarios/plain_chat_input_optional.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_input_optional.js +7 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.d.ts +3 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.js +9 -0
- package/script/schemas/scenarios/plain_chat_output.d.ts +5 -0
- package/script/schemas/scenarios/plain_chat_output.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_output.js +6 -0
- package/script/schemas/scenarios/plain_chat_output.zod.d.ts +3 -0
- package/script/schemas/scenarios/plain_chat_output.zod.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_output.zod.js +9 -0
- package/script/src/builtins.d.ts +2 -0
- package/script/src/builtins.d.ts.map +1 -1
- package/script/src/builtins.js +47 -1
- package/script/src/constants.d.ts +4 -0
- package/script/src/constants.d.ts.map +1 -1
- package/script/src/constants.js +6 -1
- package/script/src/definitions.d.ts +5 -1
- package/script/src/definitions.d.ts.map +1 -1
- package/script/src/loader.d.ts.map +1 -1
- package/script/src/loader.js +118 -12
- package/script/src/markdown.d.ts.map +1 -1
- package/script/src/markdown.js +221 -52
- package/script/src/permissions.d.ts +143 -0
- package/script/src/permissions.d.ts.map +1 -0
- package/script/src/permissions.js +453 -0
- package/script/src/render.d.ts.map +1 -1
- package/script/src/render.js +22 -8
- package/script/src/runtime.d.ts +28 -2
- package/script/src/runtime.d.ts.map +1 -1
- package/script/src/runtime.js +3053 -99
- package/script/src/runtime_exec_host.d.ts +6 -0
- package/script/src/runtime_exec_host.d.ts.map +1 -0
- package/script/src/runtime_exec_host.js +56 -0
- package/script/src/runtime_exec_host_contract.d.ts +23 -0
- package/script/src/runtime_exec_host_contract.d.ts.map +1 -0
- package/script/src/runtime_exec_host_contract.js +18 -0
- package/script/src/runtime_exec_host_deno.d.ts +3 -0
- package/script/src/runtime_exec_host_deno.d.ts.map +1 -0
- package/script/src/runtime_exec_host_deno.js +71 -0
- package/script/src/runtime_exec_host_unsupported.d.ts +3 -0
- package/script/src/runtime_exec_host_unsupported.d.ts.map +1 -0
- package/script/src/runtime_exec_host_unsupported.js +11 -0
- package/script/src/runtime_worker_host.d.ts +6 -0
- package/script/src/runtime_worker_host.d.ts.map +1 -0
- package/script/src/runtime_worker_host.js +56 -0
- package/script/src/runtime_worker_host_contract.d.ts +33 -0
- package/script/src/runtime_worker_host_contract.d.ts.map +1 -0
- package/script/src/runtime_worker_host_contract.js +18 -0
- package/script/src/runtime_worker_host_deno.d.ts +3 -0
- package/script/src/runtime_worker_host_deno.d.ts.map +1 -0
- package/script/src/runtime_worker_host_deno.js +62 -0
- package/script/src/runtime_worker_host_unsupported.d.ts +3 -0
- package/script/src/runtime_worker_host_unsupported.d.ts.map +1 -0
- package/script/src/runtime_worker_host_unsupported.js +11 -0
- package/script/src/state.d.ts +4 -1
- package/script/src/state.d.ts.map +1 -1
- package/script/src/state.js +48 -2
- package/script/src/types.d.ts +381 -1
- package/script/src/types.d.ts.map +1 -1
- package/script/src/types.js +103 -0
- package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts +0 -6
- package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.js +0 -18
- package/esm/src/openai_compat.d.ts +0 -63
- package/esm/src/openai_compat.d.ts.map +0 -1
- package/esm/src/openai_compat.js +0 -272
- package/esm/src/providers/openrouter.d.ts +0 -8
- package/esm/src/providers/openrouter.d.ts.map +0 -1
- package/esm/src/providers/openrouter.js +0 -168
- package/script/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts +0 -6
- package/script/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/collections/1.1.4/_utils.js +0 -21
- package/script/src/openai_compat.d.ts +0 -63
- package/script/src/openai_compat.d.ts.map +0 -1
- package/script/src/openai_compat.js +0 -276
- package/script/src/providers/openrouter.d.ts +0 -8
- package/script/src/providers/openrouter.d.ts.map +0 -1
- package/script/src/providers/openrouter.js +0 -207
package/script/src/runtime.js
CHANGED
|
@@ -33,11 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RunCanceledError = void 0;
|
|
36
37
|
exports.isGambitEndSignal = isGambitEndSignal;
|
|
38
|
+
exports.isRunCanceledError = isRunCanceledError;
|
|
37
39
|
exports.runDeck = runDeck;
|
|
40
|
+
const dntShim = __importStar(require("../_dnt.shims.js"));
|
|
38
41
|
const path = __importStar(require("../deps/jsr.io/@std/path/1.1.4/mod.js"));
|
|
39
42
|
const constants_js_1 = require("./constants.js");
|
|
40
43
|
const loader_js_1 = require("./loader.js");
|
|
44
|
+
const permissions_js_1 = require("./permissions.js");
|
|
45
|
+
const runtime_exec_host_js_1 = require("./runtime_exec_host.js");
|
|
46
|
+
const runtime_worker_host_js_1 = require("./runtime_worker_host.js");
|
|
41
47
|
const schema_js_1 = require("./schema.js");
|
|
42
48
|
function isGambitEndSignal(value) {
|
|
43
49
|
return Boolean(value &&
|
|
@@ -50,6 +56,301 @@ function randomId(prefix) {
|
|
|
50
56
|
// Keep IDs short enough for OpenAI/OpenRouter tool_call id limits (~40 chars).
|
|
51
57
|
return `${prefix}-${suffix}`;
|
|
52
58
|
}
|
|
59
|
+
const WORKER_SANDBOX_ENV = "GAMBIT_DECK_WORKER_SANDBOX";
|
|
60
|
+
const WORKER_TIMEOUT_MESSAGE = "Timeout exceeded";
|
|
61
|
+
const RUN_CANCELED_MESSAGE = "Run canceled";
|
|
62
|
+
const WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE = "workerSandbox is unsupported when `signal` is provided.";
|
|
63
|
+
const INSPECT_WORKER_TIMEOUT_MS = 1_500;
|
|
64
|
+
const INSPECT_WORKER_TIMEOUT_MESSAGE = "Deck inspection timed out";
|
|
65
|
+
const BUILTIN_TOOL_READ_FILE = "read_file";
|
|
66
|
+
const BUILTIN_TOOL_LIST_DIR = "list_dir";
|
|
67
|
+
const BUILTIN_TOOL_GREP_FILES = "grep_files";
|
|
68
|
+
const BUILTIN_TOOL_APPLY_PATCH = "apply_patch";
|
|
69
|
+
const BUILTIN_TOOL_EXEC = "exec";
|
|
70
|
+
const BUILTIN_TOOL_NAMES = new Set([
|
|
71
|
+
BUILTIN_TOOL_READ_FILE,
|
|
72
|
+
BUILTIN_TOOL_LIST_DIR,
|
|
73
|
+
BUILTIN_TOOL_GREP_FILES,
|
|
74
|
+
BUILTIN_TOOL_APPLY_PATCH,
|
|
75
|
+
BUILTIN_TOOL_EXEC,
|
|
76
|
+
]);
|
|
77
|
+
const TRUSTED_SCHEMA_IMPORT_PREFIXES = [
|
|
78
|
+
"@bolt-foundry/gambit-core/schemas",
|
|
79
|
+
"gambit://schemas",
|
|
80
|
+
];
|
|
81
|
+
class RunCanceledError extends Error {
|
|
82
|
+
constructor(message = RUN_CANCELED_MESSAGE) {
|
|
83
|
+
super(message);
|
|
84
|
+
Object.defineProperty(this, "code", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: "run_canceled"
|
|
89
|
+
});
|
|
90
|
+
this.name = "RunCanceledError";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.RunCanceledError = RunCanceledError;
|
|
94
|
+
class WorkerSandboxSignalUnsupportedError extends Error {
|
|
95
|
+
constructor(message = WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE) {
|
|
96
|
+
super(message);
|
|
97
|
+
Object.defineProperty(this, "code", {
|
|
98
|
+
enumerable: true,
|
|
99
|
+
configurable: true,
|
|
100
|
+
writable: true,
|
|
101
|
+
value: "worker_sandbox_signal_unsupported"
|
|
102
|
+
});
|
|
103
|
+
this.name = "WorkerSandboxSignalUnsupportedError";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isRunCanceledError(err) {
|
|
107
|
+
if (!err || typeof err !== "object")
|
|
108
|
+
return false;
|
|
109
|
+
const name = err.name;
|
|
110
|
+
const code = err.code;
|
|
111
|
+
if (name === "RunCanceledError" || code === "run_canceled")
|
|
112
|
+
return true;
|
|
113
|
+
if (name === "AbortError")
|
|
114
|
+
return true;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function shouldUseWorkerSandbox() {
|
|
118
|
+
let raw;
|
|
119
|
+
try {
|
|
120
|
+
raw = dntShim.Deno.env.get(WORKER_SANDBOX_ENV);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
raw = raw?.trim().toLowerCase();
|
|
126
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
127
|
+
}
|
|
128
|
+
function normalizedScopeToWire(scope) {
|
|
129
|
+
if (scope.all)
|
|
130
|
+
return true;
|
|
131
|
+
if (scope.values.size === 0)
|
|
132
|
+
return false;
|
|
133
|
+
return Array.from(scope.values).sort();
|
|
134
|
+
}
|
|
135
|
+
function normalizedRunToWire(scope) {
|
|
136
|
+
if (scope.all)
|
|
137
|
+
return true;
|
|
138
|
+
if (scope.paths.size === 0 && scope.commands.size === 0)
|
|
139
|
+
return false;
|
|
140
|
+
return {
|
|
141
|
+
paths: Array.from(scope.paths).sort(),
|
|
142
|
+
commands: Array.from(scope.commands).sort(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function toWirePermissionSet(set) {
|
|
146
|
+
return {
|
|
147
|
+
baseDir: set.baseDir,
|
|
148
|
+
read: normalizedScopeToWire(set.read),
|
|
149
|
+
write: normalizedScopeToWire(set.write),
|
|
150
|
+
run: normalizedRunToWire(set.run),
|
|
151
|
+
net: normalizedScopeToWire(set.net),
|
|
152
|
+
env: normalizedScopeToWire(set.env),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function wireScopeToNormalized(scope) {
|
|
156
|
+
if (scope === true)
|
|
157
|
+
return { all: true, values: new Set() };
|
|
158
|
+
if (scope === false)
|
|
159
|
+
return { all: false, values: new Set() };
|
|
160
|
+
return { all: false, values: new Set(scope) };
|
|
161
|
+
}
|
|
162
|
+
function wireRunToNormalized(scope) {
|
|
163
|
+
if (scope === true) {
|
|
164
|
+
return {
|
|
165
|
+
all: true,
|
|
166
|
+
paths: new Set(),
|
|
167
|
+
commands: new Set(),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (scope === false) {
|
|
171
|
+
return {
|
|
172
|
+
all: false,
|
|
173
|
+
paths: new Set(),
|
|
174
|
+
commands: new Set(),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
all: false,
|
|
179
|
+
paths: new Set(scope.paths),
|
|
180
|
+
commands: new Set(scope.commands),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function fromWirePermissionSet(set) {
|
|
184
|
+
return {
|
|
185
|
+
baseDir: set.baseDir,
|
|
186
|
+
read: wireScopeToNormalized(set.read),
|
|
187
|
+
write: wireScopeToNormalized(set.write),
|
|
188
|
+
run: wireRunToNormalized(set.run),
|
|
189
|
+
net: wireScopeToNormalized(set.net),
|
|
190
|
+
env: wireScopeToNormalized(set.env),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function normalizePermissionBaseDir(set, baseDir) {
|
|
194
|
+
return {
|
|
195
|
+
...set,
|
|
196
|
+
baseDir,
|
|
197
|
+
read: { all: set.read.all, values: new Set(set.read.values) },
|
|
198
|
+
write: { all: set.write.all, values: new Set(set.write.values) },
|
|
199
|
+
run: {
|
|
200
|
+
all: set.run.all,
|
|
201
|
+
paths: new Set(set.run.paths),
|
|
202
|
+
commands: new Set(set.run.commands),
|
|
203
|
+
},
|
|
204
|
+
net: { all: set.net.all, values: new Set(set.net.values) },
|
|
205
|
+
env: { all: set.env.all, values: new Set(set.env.values) },
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function deadlineForRun(guardrails, existing) {
|
|
209
|
+
const timeoutDeadline = performance.now() + guardrails.timeoutMs;
|
|
210
|
+
if (typeof existing === "number" && Number.isFinite(existing)) {
|
|
211
|
+
return Math.min(existing, timeoutDeadline);
|
|
212
|
+
}
|
|
213
|
+
return timeoutDeadline;
|
|
214
|
+
}
|
|
215
|
+
function ensureNotExpired(deadlineMs) {
|
|
216
|
+
if (performance.now() > deadlineMs) {
|
|
217
|
+
throw new Error(WORKER_TIMEOUT_MESSAGE);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function throwIfCanceled(signal) {
|
|
221
|
+
if (!signal?.aborted)
|
|
222
|
+
return;
|
|
223
|
+
const reason = signal.reason;
|
|
224
|
+
if (typeof reason === "string" && reason.trim().length > 0) {
|
|
225
|
+
throw new RunCanceledError(reason);
|
|
226
|
+
}
|
|
227
|
+
if (reason instanceof Error && reason.message.trim().length > 0) {
|
|
228
|
+
throw new RunCanceledError(reason.message);
|
|
229
|
+
}
|
|
230
|
+
throw new RunCanceledError();
|
|
231
|
+
}
|
|
232
|
+
function ensureRunActive(deadlineMs, signal) {
|
|
233
|
+
throwIfCanceled(signal);
|
|
234
|
+
ensureNotExpired(deadlineMs);
|
|
235
|
+
}
|
|
236
|
+
function isTrustedSchemaImportKey(key) {
|
|
237
|
+
const normalized = key.trim();
|
|
238
|
+
if (!normalized)
|
|
239
|
+
return false;
|
|
240
|
+
return TRUSTED_SCHEMA_IMPORT_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
|
|
241
|
+
}
|
|
242
|
+
function tryReadWorkspaceConfigPath(deckPath) {
|
|
243
|
+
const startDir = path.dirname(path.resolve(deckPath));
|
|
244
|
+
let current = startDir;
|
|
245
|
+
while (true) {
|
|
246
|
+
const denoJson = path.join(current, "deno.json");
|
|
247
|
+
const denoJsonc = path.join(current, "deno.jsonc");
|
|
248
|
+
try {
|
|
249
|
+
if (dntShim.Deno.statSync(denoJson).isFile)
|
|
250
|
+
return denoJson;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// continue search
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
if (dntShim.Deno.statSync(denoJsonc).isFile)
|
|
257
|
+
return denoJsonc;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// continue search
|
|
261
|
+
}
|
|
262
|
+
const parent = path.dirname(current);
|
|
263
|
+
if (parent === current)
|
|
264
|
+
break;
|
|
265
|
+
current = parent;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
function readWorkspaceImportMapKeys(configPath) {
|
|
270
|
+
const text = dntShim.Deno.readTextFileSync(configPath);
|
|
271
|
+
const parsed = parseWorkspaceConfig(text);
|
|
272
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) ||
|
|
273
|
+
!parsed.imports || typeof parsed.imports !== "object" ||
|
|
274
|
+
Array.isArray(parsed.imports)) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
return Object.keys(parsed.imports);
|
|
278
|
+
}
|
|
279
|
+
function parseWorkspaceConfig(text) {
|
|
280
|
+
try {
|
|
281
|
+
return JSON.parse(text);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
const stripped = stripJsonComments(text);
|
|
285
|
+
return JSON.parse(stripped);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function stripJsonComments(text) {
|
|
289
|
+
let out = "";
|
|
290
|
+
let inString = false;
|
|
291
|
+
let escapeNext = false;
|
|
292
|
+
let inLineComment = false;
|
|
293
|
+
let inBlockComment = false;
|
|
294
|
+
for (let i = 0; i < text.length; i++) {
|
|
295
|
+
const ch = text[i];
|
|
296
|
+
const next = text[i + 1];
|
|
297
|
+
if (inLineComment) {
|
|
298
|
+
if (ch === "\n") {
|
|
299
|
+
inLineComment = false;
|
|
300
|
+
out += ch;
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (inBlockComment) {
|
|
305
|
+
if (ch === "*" && next === "/") {
|
|
306
|
+
inBlockComment = false;
|
|
307
|
+
i++;
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (inString) {
|
|
312
|
+
out += ch;
|
|
313
|
+
if (escapeNext) {
|
|
314
|
+
escapeNext = false;
|
|
315
|
+
}
|
|
316
|
+
else if (ch === "\\") {
|
|
317
|
+
escapeNext = true;
|
|
318
|
+
}
|
|
319
|
+
else if (ch === '"') {
|
|
320
|
+
inString = false;
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (ch === '"') {
|
|
325
|
+
inString = true;
|
|
326
|
+
out += ch;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (ch === "/" && next === "/") {
|
|
330
|
+
inLineComment = true;
|
|
331
|
+
i++;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (ch === "/" && next === "*") {
|
|
335
|
+
inBlockComment = true;
|
|
336
|
+
i++;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
out += ch;
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
function enforceTrustedSchemaImportMapPolicy(deckPath) {
|
|
344
|
+
if (deckPath.startsWith("gambit://"))
|
|
345
|
+
return;
|
|
346
|
+
const configPath = tryReadWorkspaceConfigPath(deckPath);
|
|
347
|
+
if (!configPath)
|
|
348
|
+
return;
|
|
349
|
+
const violations = readWorkspaceImportMapKeys(configPath).filter((key) => isTrustedSchemaImportKey(key));
|
|
350
|
+
if (violations.length === 0)
|
|
351
|
+
return;
|
|
352
|
+
throw new Error(`[gambit] trust-boundary violation: workspace import map at ${configPath} remaps trusted schema namespace (${violations.join(", ")})`);
|
|
353
|
+
}
|
|
53
354
|
async function runDeck(opts) {
|
|
54
355
|
const guardrails = {
|
|
55
356
|
...constants_js_1.DEFAULT_GUARDRAILS,
|
|
@@ -62,34 +363,241 @@ async function runDeck(opts) {
|
|
|
62
363
|
throw new Error(`Max depth ${guardrails.maxDepth} exceeded`);
|
|
63
364
|
}
|
|
64
365
|
const runId = opts.runId ?? opts.state?.runId ?? randomId("run");
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
366
|
+
enforceTrustedSchemaImportMapPolicy(opts.path);
|
|
367
|
+
const workerSandboxRequested = opts.workerSandbox ??
|
|
368
|
+
shouldUseWorkerSandbox();
|
|
369
|
+
if (workerSandboxRequested && !(0, runtime_worker_host_js_1.isWorkerSandboxHostSupported)()) {
|
|
370
|
+
throw new runtime_worker_host_js_1.WorkerSandboxUnsupportedHostError();
|
|
371
|
+
}
|
|
372
|
+
if (workerSandboxRequested && opts.signal) {
|
|
373
|
+
throw new WorkerSandboxSignalUnsupportedError();
|
|
374
|
+
}
|
|
375
|
+
const workerSandbox = workerSandboxRequested;
|
|
71
376
|
const isRoot = Boolean(inferredRoot);
|
|
72
|
-
ensureSchemaPresence(deck, isRoot);
|
|
73
|
-
const resolvedInput = resolveInput({
|
|
74
|
-
deck,
|
|
75
|
-
input: opts.input,
|
|
76
|
-
state: opts.state,
|
|
77
|
-
isRoot,
|
|
78
|
-
initialUserMessage: opts.initialUserMessage,
|
|
79
|
-
});
|
|
80
|
-
const validatedInput = validateInput(deck, resolvedInput, isRoot, opts.allowRootStringInput ?? false);
|
|
81
377
|
const shouldEmitRun = opts.depth === undefined || opts.depth === 0;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
378
|
+
let canceled = false;
|
|
379
|
+
let cancelHandled = false;
|
|
380
|
+
const handleCancel = async () => {
|
|
381
|
+
if (cancelHandled)
|
|
382
|
+
return;
|
|
383
|
+
cancelHandled = true;
|
|
384
|
+
if (!opts.onCancel)
|
|
385
|
+
return;
|
|
386
|
+
try {
|
|
387
|
+
await opts.onCancel();
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
logger.warn(`[gambit] runDeck onCancel callback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
92
393
|
try {
|
|
394
|
+
throwIfCanceled(opts.signal);
|
|
395
|
+
if (workerSandbox) {
|
|
396
|
+
const preInspectRunDeadlineMs = deadlineForRun(guardrails, opts.runDeadlineMs);
|
|
397
|
+
ensureRunActive(preInspectRunDeadlineMs, opts.signal);
|
|
398
|
+
const inspectedDeck = await inspectDeckInWorker(opts.path, preInspectRunDeadlineMs);
|
|
399
|
+
const deckDir = path.dirname(inspectedDeck.deckPath);
|
|
400
|
+
const permissions = (0, permissions_js_1.resolveEffectivePermissions)({
|
|
401
|
+
baseDir: deckDir,
|
|
402
|
+
parent: opts.parentPermissions,
|
|
403
|
+
workspace: opts.workspacePermissions
|
|
404
|
+
? {
|
|
405
|
+
baseDir: opts.workspacePermissionsBaseDir ?? deckDir,
|
|
406
|
+
permissions: opts.workspacePermissions,
|
|
407
|
+
}
|
|
408
|
+
: undefined,
|
|
409
|
+
declaration: inspectedDeck.permissions
|
|
410
|
+
? { baseDir: deckDir, permissions: inspectedDeck.permissions }
|
|
411
|
+
: undefined,
|
|
412
|
+
reference: opts.referencePermissions
|
|
413
|
+
? {
|
|
414
|
+
baseDir: opts.referencePermissionsBaseDir ?? deckDir,
|
|
415
|
+
permissions: opts.referencePermissions,
|
|
416
|
+
}
|
|
417
|
+
: undefined,
|
|
418
|
+
session: opts.sessionPermissions
|
|
419
|
+
? {
|
|
420
|
+
baseDir: opts.sessionPermissionsBaseDir ?? dntShim.Deno.cwd(),
|
|
421
|
+
permissions: opts.sessionPermissions,
|
|
422
|
+
}
|
|
423
|
+
: undefined,
|
|
424
|
+
});
|
|
425
|
+
const effectiveGuardrails = {
|
|
426
|
+
...guardrails,
|
|
427
|
+
...(inspectedDeck.guardrails ?? {}),
|
|
428
|
+
};
|
|
429
|
+
const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
|
|
430
|
+
ensureRunActive(runDeadlineMs, opts.signal);
|
|
431
|
+
const resolvedInput = resolveInputWithoutDeck({
|
|
432
|
+
input: opts.input,
|
|
433
|
+
state: opts.state,
|
|
434
|
+
isRoot,
|
|
435
|
+
initialUserMessage: opts.initialUserMessage,
|
|
436
|
+
});
|
|
437
|
+
if (!inspectedDeck.hasModelParams) {
|
|
438
|
+
if (shouldEmitRun) {
|
|
439
|
+
opts.trace?.({
|
|
440
|
+
type: "run.start",
|
|
441
|
+
runId,
|
|
442
|
+
deckPath: inspectedDeck.deckPath,
|
|
443
|
+
input: resolvedInput,
|
|
444
|
+
initialUserMessage: opts
|
|
445
|
+
.initialUserMessage,
|
|
446
|
+
permissions: permissions.trace,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return await runComputeDeckInWorker({
|
|
450
|
+
deckPath: inspectedDeck.deckPath,
|
|
451
|
+
guardrails: effectiveGuardrails,
|
|
452
|
+
depth,
|
|
453
|
+
runId,
|
|
454
|
+
initialUserMessage: opts.initialUserMessage,
|
|
455
|
+
parentActionCallId: opts.parentActionCallId,
|
|
456
|
+
modelProvider: opts.modelProvider,
|
|
457
|
+
input: resolvedInput,
|
|
458
|
+
defaultModel: opts.defaultModel,
|
|
459
|
+
modelOverride: opts.modelOverride,
|
|
460
|
+
trace: opts.trace,
|
|
461
|
+
stream: opts.stream,
|
|
462
|
+
state: opts.state,
|
|
463
|
+
onStateUpdate: opts.onStateUpdate,
|
|
464
|
+
onStreamText: opts.onStreamText,
|
|
465
|
+
responsesMode: opts.responsesMode,
|
|
466
|
+
permissions: permissions.effective,
|
|
467
|
+
permissionsTrace: permissions.trace,
|
|
468
|
+
workspacePermissions: opts.workspacePermissions,
|
|
469
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
470
|
+
sessionPermissions: opts.sessionPermissions,
|
|
471
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
472
|
+
runDeadlineMs,
|
|
473
|
+
isRoot,
|
|
474
|
+
allowRootStringInput: opts.allowRootStringInput ?? false,
|
|
475
|
+
signal: opts.signal,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
if (!opts.inOrchestrationWorker) {
|
|
479
|
+
return await runLlmDeckInWorker({
|
|
480
|
+
deckPath: inspectedDeck.deckPath,
|
|
481
|
+
guardrails: effectiveGuardrails,
|
|
482
|
+
depth,
|
|
483
|
+
runId,
|
|
484
|
+
parentActionCallId: opts.parentActionCallId,
|
|
485
|
+
modelProvider: opts.modelProvider,
|
|
486
|
+
input: resolvedInput,
|
|
487
|
+
inputProvided: opts.inputProvided ?? true,
|
|
488
|
+
initialUserMessage: opts.initialUserMessage,
|
|
489
|
+
defaultModel: opts.defaultModel,
|
|
490
|
+
modelOverride: opts.modelOverride,
|
|
491
|
+
trace: opts.trace,
|
|
492
|
+
stream: opts.stream,
|
|
493
|
+
state: opts.state,
|
|
494
|
+
onStateUpdate: opts.onStateUpdate,
|
|
495
|
+
onStreamText: opts.onStreamText,
|
|
496
|
+
responsesMode: opts.responsesMode,
|
|
497
|
+
permissions: permissions.effective,
|
|
498
|
+
permissionsTrace: permissions.trace,
|
|
499
|
+
workspacePermissions: opts.workspacePermissions,
|
|
500
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
501
|
+
sessionPermissions: opts.sessionPermissions,
|
|
502
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
503
|
+
runDeadlineMs,
|
|
504
|
+
workerSandbox,
|
|
505
|
+
allowRootStringInput: opts.allowRootStringInput,
|
|
506
|
+
isRoot,
|
|
507
|
+
signal: opts.signal,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const deck = await (0, loader_js_1.loadDeck)(opts.path);
|
|
512
|
+
const permissions = (0, permissions_js_1.resolveEffectivePermissions)({
|
|
513
|
+
baseDir: path.dirname(deck.path),
|
|
514
|
+
parent: opts.parentPermissions,
|
|
515
|
+
workspace: opts.workspacePermissions
|
|
516
|
+
? {
|
|
517
|
+
baseDir: opts.workspacePermissionsBaseDir ?? path.dirname(deck.path),
|
|
518
|
+
permissions: opts.workspacePermissions,
|
|
519
|
+
}
|
|
520
|
+
: undefined,
|
|
521
|
+
declaration: deck.permissions
|
|
522
|
+
? { baseDir: path.dirname(deck.path), permissions: deck.permissions }
|
|
523
|
+
: undefined,
|
|
524
|
+
reference: opts.referencePermissions
|
|
525
|
+
? {
|
|
526
|
+
baseDir: opts.referencePermissionsBaseDir ?? path.dirname(deck.path),
|
|
527
|
+
permissions: opts.referencePermissions,
|
|
528
|
+
}
|
|
529
|
+
: undefined,
|
|
530
|
+
session: opts.sessionPermissions
|
|
531
|
+
? {
|
|
532
|
+
baseDir: opts.sessionPermissionsBaseDir ?? dntShim.Deno.cwd(),
|
|
533
|
+
permissions: opts.sessionPermissions,
|
|
534
|
+
}
|
|
535
|
+
: undefined,
|
|
536
|
+
});
|
|
537
|
+
const deckGuardrails = deck.guardrails ?? {};
|
|
538
|
+
const effectiveGuardrails = {
|
|
539
|
+
...guardrails,
|
|
540
|
+
...deckGuardrails,
|
|
541
|
+
};
|
|
542
|
+
const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
|
|
543
|
+
ensureRunActive(runDeadlineMs, opts.signal);
|
|
544
|
+
ensureSchemaPresence(deck, isRoot);
|
|
545
|
+
const resolvedInput = resolveInput({
|
|
546
|
+
deck,
|
|
547
|
+
input: opts.input,
|
|
548
|
+
state: opts.state,
|
|
549
|
+
isRoot,
|
|
550
|
+
initialUserMessage: opts.initialUserMessage,
|
|
551
|
+
});
|
|
552
|
+
const validatedInput = validateInput(deck, resolvedInput, isRoot, opts.allowRootStringInput ?? false);
|
|
553
|
+
const useOrchestrationWorker = workerSandbox &&
|
|
554
|
+
!opts.inOrchestrationWorker &&
|
|
555
|
+
isRoot &&
|
|
556
|
+
!opts.onTool &&
|
|
557
|
+
Boolean(deck.modelParams?.model || deck.modelParams?.temperature !== undefined);
|
|
558
|
+
if (useOrchestrationWorker) {
|
|
559
|
+
return await runLlmDeckInWorker({
|
|
560
|
+
deckPath: deck.path,
|
|
561
|
+
guardrails: effectiveGuardrails,
|
|
562
|
+
depth,
|
|
563
|
+
runId,
|
|
564
|
+
parentActionCallId: opts.parentActionCallId,
|
|
565
|
+
modelProvider: opts.modelProvider,
|
|
566
|
+
input: validatedInput,
|
|
567
|
+
inputProvided: opts.inputProvided ?? true,
|
|
568
|
+
initialUserMessage: opts.initialUserMessage,
|
|
569
|
+
defaultModel: opts.defaultModel,
|
|
570
|
+
modelOverride: opts.modelOverride,
|
|
571
|
+
trace: opts.trace,
|
|
572
|
+
stream: opts.stream,
|
|
573
|
+
state: opts.state,
|
|
574
|
+
onStateUpdate: opts.onStateUpdate,
|
|
575
|
+
onStreamText: opts.onStreamText,
|
|
576
|
+
responsesMode: opts.responsesMode,
|
|
577
|
+
permissions: permissions.effective,
|
|
578
|
+
permissionsTrace: permissions.trace,
|
|
579
|
+
workspacePermissions: opts.workspacePermissions,
|
|
580
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
581
|
+
sessionPermissions: opts.sessionPermissions,
|
|
582
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
583
|
+
runDeadlineMs,
|
|
584
|
+
workerSandbox,
|
|
585
|
+
allowRootStringInput: opts.allowRootStringInput,
|
|
586
|
+
isRoot,
|
|
587
|
+
signal: opts.signal,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (shouldEmitRun) {
|
|
591
|
+
opts.trace?.({
|
|
592
|
+
type: "run.start",
|
|
593
|
+
runId,
|
|
594
|
+
deckPath: deck.path,
|
|
595
|
+
input: validatedInput,
|
|
596
|
+
initialUserMessage: opts
|
|
597
|
+
.initialUserMessage,
|
|
598
|
+
permissions: permissions.trace,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
93
601
|
if (deck.modelParams?.model || deck.modelParams?.temperature !== undefined) {
|
|
94
602
|
return await runLlmDeck({
|
|
95
603
|
deck,
|
|
@@ -108,6 +616,17 @@ async function runDeck(opts) {
|
|
|
108
616
|
state: opts.state,
|
|
109
617
|
onStateUpdate: opts.onStateUpdate,
|
|
110
618
|
onStreamText: opts.onStreamText,
|
|
619
|
+
responsesMode: opts.responsesMode,
|
|
620
|
+
permissions: permissions.effective,
|
|
621
|
+
permissionsTrace: permissions.trace,
|
|
622
|
+
workspacePermissions: opts.workspacePermissions,
|
|
623
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
624
|
+
sessionPermissions: opts.sessionPermissions,
|
|
625
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
626
|
+
runDeadlineMs,
|
|
627
|
+
workerSandbox,
|
|
628
|
+
onTool: opts.onTool,
|
|
629
|
+
signal: opts.signal,
|
|
111
630
|
});
|
|
112
631
|
}
|
|
113
632
|
if (!deck.executor) {
|
|
@@ -118,6 +637,7 @@ async function runDeck(opts) {
|
|
|
118
637
|
guardrails: effectiveGuardrails,
|
|
119
638
|
depth,
|
|
120
639
|
runId,
|
|
640
|
+
initialUserMessage: opts.initialUserMessage,
|
|
121
641
|
parentActionCallId: opts.parentActionCallId,
|
|
122
642
|
modelProvider: opts.modelProvider,
|
|
123
643
|
input: validatedInput,
|
|
@@ -125,19 +645,42 @@ async function runDeck(opts) {
|
|
|
125
645
|
modelOverride: opts.modelOverride,
|
|
126
646
|
trace: opts.trace,
|
|
127
647
|
stream: opts.stream,
|
|
648
|
+
state: opts.state,
|
|
649
|
+
onStateUpdate: opts.onStateUpdate,
|
|
128
650
|
onStreamText: opts.onStreamText,
|
|
651
|
+
responsesMode: opts.responsesMode,
|
|
652
|
+
permissions: permissions.effective,
|
|
653
|
+
permissionsTrace: permissions.trace,
|
|
654
|
+
workspacePermissions: opts.workspacePermissions,
|
|
655
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
656
|
+
sessionPermissions: opts.sessionPermissions,
|
|
657
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
658
|
+
runDeadlineMs,
|
|
659
|
+
workerSandbox,
|
|
660
|
+
onTool: opts.onTool,
|
|
661
|
+
signal: opts.signal,
|
|
129
662
|
});
|
|
130
663
|
}
|
|
664
|
+
catch (err) {
|
|
665
|
+
if (isRunCanceledError(err)) {
|
|
666
|
+
canceled = true;
|
|
667
|
+
await handleCancel();
|
|
668
|
+
}
|
|
669
|
+
throw err;
|
|
670
|
+
}
|
|
131
671
|
finally {
|
|
132
672
|
if (shouldEmitRun) {
|
|
133
673
|
opts.trace?.({ type: "run.end", runId });
|
|
134
674
|
}
|
|
675
|
+
if (opts.signal?.aborted && !canceled) {
|
|
676
|
+
await handleCancel();
|
|
677
|
+
}
|
|
135
678
|
}
|
|
136
679
|
}
|
|
137
680
|
function toProviderParams(params) {
|
|
138
681
|
if (!params)
|
|
139
682
|
return undefined;
|
|
140
|
-
const { model: _model, temperature, top_p, frequency_penalty, presence_penalty, max_tokens, } = params;
|
|
683
|
+
const { model: _model, temperature, top_p, frequency_penalty, presence_penalty, max_tokens, verbosity, reasoning, } = params;
|
|
141
684
|
const out = {};
|
|
142
685
|
if (temperature !== undefined)
|
|
143
686
|
out.temperature = temperature;
|
|
@@ -150,15 +693,51 @@ function toProviderParams(params) {
|
|
|
150
693
|
out.presence_penalty = presence_penalty;
|
|
151
694
|
if (max_tokens !== undefined)
|
|
152
695
|
out.max_tokens = max_tokens;
|
|
696
|
+
if (verbosity !== undefined)
|
|
697
|
+
out.verbosity = verbosity;
|
|
698
|
+
if (reasoning !== undefined)
|
|
699
|
+
out.reasoning = reasoning;
|
|
153
700
|
return Object.keys(out).length ? out : undefined;
|
|
154
701
|
}
|
|
702
|
+
async function resolveModelChoice(args) {
|
|
703
|
+
const resolver = args.modelProvider.resolveModel;
|
|
704
|
+
if (resolver) {
|
|
705
|
+
return await resolver({
|
|
706
|
+
model: args.model,
|
|
707
|
+
params: args.params,
|
|
708
|
+
deckPath: args.deckPath,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
if (Array.isArray(args.model)) {
|
|
712
|
+
const first = args.model.find((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
713
|
+
if (!first) {
|
|
714
|
+
throw new Error(`No model configured for deck ${args.deckPath}`);
|
|
715
|
+
}
|
|
716
|
+
return { model: first, params: args.params };
|
|
717
|
+
}
|
|
718
|
+
if (!args.model || !args.model.trim()) {
|
|
719
|
+
throw new Error(`No model configured for deck ${args.deckPath}`);
|
|
720
|
+
}
|
|
721
|
+
return { model: args.model, params: args.params };
|
|
722
|
+
}
|
|
723
|
+
function resolveContextSchema(deck) {
|
|
724
|
+
return deck.contextSchema ?? deck.inputSchema;
|
|
725
|
+
}
|
|
726
|
+
function resolveResponseSchema(deck) {
|
|
727
|
+
return deck.responseSchema ?? deck.outputSchema;
|
|
728
|
+
}
|
|
729
|
+
function isContextToolName(name) {
|
|
730
|
+
return name === constants_js_1.GAMBIT_TOOL_CONTEXT || name === constants_js_1.GAMBIT_TOOL_INIT;
|
|
731
|
+
}
|
|
155
732
|
function ensureSchemaPresence(deck, isRoot) {
|
|
156
733
|
if (!isRoot) {
|
|
157
|
-
|
|
158
|
-
|
|
734
|
+
const contextSchema = resolveContextSchema(deck);
|
|
735
|
+
const responseSchema = resolveResponseSchema(deck);
|
|
736
|
+
if (!contextSchema || !responseSchema) {
|
|
737
|
+
throw new Error(`Deck ${deck.path} must declare contextSchema and responseSchema (non-root)`);
|
|
159
738
|
}
|
|
160
|
-
(0, schema_js_1.assertZodSchema)(
|
|
161
|
-
(0, schema_js_1.assertZodSchema)(
|
|
739
|
+
(0, schema_js_1.assertZodSchema)(contextSchema, "contextSchema");
|
|
740
|
+
(0, schema_js_1.assertZodSchema)(responseSchema, "responseSchema");
|
|
162
741
|
}
|
|
163
742
|
}
|
|
164
743
|
function resolveInput(args) {
|
|
@@ -166,11 +745,11 @@ function resolveInput(args) {
|
|
|
166
745
|
return args.input;
|
|
167
746
|
if (!args.isRoot)
|
|
168
747
|
return args.input;
|
|
169
|
-
const persisted =
|
|
748
|
+
const persisted = extractContextInput(args.state);
|
|
170
749
|
if (persisted !== undefined)
|
|
171
750
|
return persisted;
|
|
172
751
|
if (args.initialUserMessage !== undefined) {
|
|
173
|
-
const schema = args.deck
|
|
752
|
+
const schema = resolveContextSchema(args.deck);
|
|
174
753
|
if (schema?.safeParse) {
|
|
175
754
|
const candidates = [undefined, {}, ""];
|
|
176
755
|
for (const candidate of candidates) {
|
|
@@ -188,12 +767,30 @@ function resolveInput(args) {
|
|
|
188
767
|
}
|
|
189
768
|
return args.input;
|
|
190
769
|
}
|
|
191
|
-
function
|
|
192
|
-
if (
|
|
770
|
+
function resolveInputWithoutDeck(args) {
|
|
771
|
+
if (args.input !== undefined)
|
|
772
|
+
return args.input;
|
|
773
|
+
if (!args.isRoot)
|
|
774
|
+
return args.input;
|
|
775
|
+
const persisted = extractContextInput(args.state);
|
|
776
|
+
if (persisted !== undefined)
|
|
777
|
+
return persisted;
|
|
778
|
+
if (args.initialUserMessage !== undefined) {
|
|
779
|
+
return "";
|
|
780
|
+
}
|
|
781
|
+
return args.input;
|
|
782
|
+
}
|
|
783
|
+
function extractContextInput(state) {
|
|
784
|
+
if (!state)
|
|
785
|
+
return undefined;
|
|
786
|
+
if (state.format === "responses" && Array.isArray(state.items)) {
|
|
787
|
+
return extractContextInputFromItems(state.items);
|
|
788
|
+
}
|
|
789
|
+
if (!state.messages)
|
|
193
790
|
return undefined;
|
|
194
791
|
for (let i = state.messages.length - 1; i >= 0; i--) {
|
|
195
792
|
const msg = state.messages[i];
|
|
196
|
-
if (msg.role === "tool" && msg.name
|
|
793
|
+
if (msg.role === "tool" && isContextToolName(msg.name ?? "")) {
|
|
197
794
|
const content = msg.content;
|
|
198
795
|
if (typeof content !== "string")
|
|
199
796
|
return undefined;
|
|
@@ -207,17 +804,244 @@ function extractInitInput(state) {
|
|
|
207
804
|
}
|
|
208
805
|
return undefined;
|
|
209
806
|
}
|
|
807
|
+
function extractContextInputFromItems(items) {
|
|
808
|
+
const contextToolNames = new Set([constants_js_1.GAMBIT_TOOL_CONTEXT, constants_js_1.GAMBIT_TOOL_INIT]);
|
|
809
|
+
const callNameById = new Map();
|
|
810
|
+
for (const item of items) {
|
|
811
|
+
if (item.type === "function_call") {
|
|
812
|
+
callNameById.set(item.call_id, item.name);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
816
|
+
const item = items[i];
|
|
817
|
+
if (item.type !== "function_call_output")
|
|
818
|
+
continue;
|
|
819
|
+
const name = callNameById.get(item.call_id);
|
|
820
|
+
if (!name || !contextToolNames.has(name))
|
|
821
|
+
continue;
|
|
822
|
+
try {
|
|
823
|
+
return JSON.parse(item.output);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
return item.output;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return undefined;
|
|
830
|
+
}
|
|
831
|
+
function messagesFromResponseItems(items) {
|
|
832
|
+
const messages = [];
|
|
833
|
+
const callNameById = new Map();
|
|
834
|
+
for (const item of items) {
|
|
835
|
+
if (item.type === "message") {
|
|
836
|
+
const text = item.content.map((part) => part.text).join("");
|
|
837
|
+
messages.push({
|
|
838
|
+
role: item.role,
|
|
839
|
+
content: text || null,
|
|
840
|
+
});
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (item.type === "function_call") {
|
|
844
|
+
callNameById.set(item.call_id, item.name);
|
|
845
|
+
messages.push({
|
|
846
|
+
role: "assistant",
|
|
847
|
+
content: null,
|
|
848
|
+
tool_calls: [{
|
|
849
|
+
id: item.call_id,
|
|
850
|
+
type: "function",
|
|
851
|
+
function: { name: item.name, arguments: item.arguments },
|
|
852
|
+
}],
|
|
853
|
+
});
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (item.type === "function_call_output") {
|
|
857
|
+
messages.push({
|
|
858
|
+
role: "tool",
|
|
859
|
+
name: callNameById.get(item.call_id),
|
|
860
|
+
tool_call_id: item.call_id,
|
|
861
|
+
content: item.output,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return messages;
|
|
866
|
+
}
|
|
867
|
+
function responseItemsFromMessages(messages) {
|
|
868
|
+
const items = [];
|
|
869
|
+
for (const message of messages) {
|
|
870
|
+
if (message.role === "tool") {
|
|
871
|
+
if (!message.tool_call_id || message.content === null)
|
|
872
|
+
continue;
|
|
873
|
+
items.push({
|
|
874
|
+
type: "function_call_output",
|
|
875
|
+
call_id: message.tool_call_id,
|
|
876
|
+
output: String(message.content),
|
|
877
|
+
});
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const contentText = message.content ?? "";
|
|
881
|
+
if (typeof contentText === "string" && contentText.length > 0) {
|
|
882
|
+
items.push({
|
|
883
|
+
type: "message",
|
|
884
|
+
role: message.role,
|
|
885
|
+
content: [{
|
|
886
|
+
type: message.role === "assistant" ? "output_text" : "input_text",
|
|
887
|
+
text: contentText,
|
|
888
|
+
}],
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
if (message.role === "assistant" && message.tool_calls) {
|
|
892
|
+
for (const call of message.tool_calls) {
|
|
893
|
+
items.push({
|
|
894
|
+
type: "function_call",
|
|
895
|
+
call_id: call.id,
|
|
896
|
+
name: call.function.name,
|
|
897
|
+
arguments: call.function.arguments,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return items;
|
|
903
|
+
}
|
|
904
|
+
function safeJsonArgs(value) {
|
|
905
|
+
if (!value)
|
|
906
|
+
return {};
|
|
907
|
+
try {
|
|
908
|
+
const parsed = JSON.parse(value);
|
|
909
|
+
if (parsed && typeof parsed === "object") {
|
|
910
|
+
return parsed;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
catch {
|
|
914
|
+
// ignore
|
|
915
|
+
}
|
|
916
|
+
return {};
|
|
917
|
+
}
|
|
918
|
+
function asToolKind(value, fallback) {
|
|
919
|
+
if (value === "action" || value === "external" || value === "mcp_bridge" ||
|
|
920
|
+
value === "internal") {
|
|
921
|
+
return value;
|
|
922
|
+
}
|
|
923
|
+
return fallback;
|
|
924
|
+
}
|
|
925
|
+
function projectStreamToolTraceEvents(input) {
|
|
926
|
+
if (!input.trace)
|
|
927
|
+
return;
|
|
928
|
+
const type = typeof input.streamEvent.type === "string"
|
|
929
|
+
? input.streamEvent.type
|
|
930
|
+
: "";
|
|
931
|
+
if (type !== "tool.call" && type !== "tool.result")
|
|
932
|
+
return;
|
|
933
|
+
const actionCallId = typeof input.streamEvent.actionCallId === "string"
|
|
934
|
+
? input.streamEvent.actionCallId
|
|
935
|
+
: "";
|
|
936
|
+
const name = typeof input.streamEvent.name === "string"
|
|
937
|
+
? input.streamEvent.name
|
|
938
|
+
: input.toolNames.get(actionCallId) ?? "";
|
|
939
|
+
if (!actionCallId || !name)
|
|
940
|
+
return;
|
|
941
|
+
if (type === "tool.call") {
|
|
942
|
+
if (input.emittedCalls.has(actionCallId))
|
|
943
|
+
return;
|
|
944
|
+
input.emittedCalls.add(actionCallId);
|
|
945
|
+
input.toolNames.set(actionCallId, name);
|
|
946
|
+
const args = "args" in input.streamEvent
|
|
947
|
+
? (input.streamEvent.args ?? {})
|
|
948
|
+
: {};
|
|
949
|
+
const toolKind = asToolKind(input.streamEvent.toolKind, "mcp_bridge");
|
|
950
|
+
input.trace({
|
|
951
|
+
type: "tool.call",
|
|
952
|
+
runId: input.runId,
|
|
953
|
+
actionCallId,
|
|
954
|
+
name,
|
|
955
|
+
args,
|
|
956
|
+
toolKind,
|
|
957
|
+
parentActionCallId: input.parentActionCallId,
|
|
958
|
+
});
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (input.emittedResults.has(actionCallId))
|
|
962
|
+
return;
|
|
963
|
+
input.emittedResults.add(actionCallId);
|
|
964
|
+
const result = "result" in input.streamEvent
|
|
965
|
+
? (input.streamEvent.result ?? null)
|
|
966
|
+
: null;
|
|
967
|
+
const toolKind = asToolKind(input.streamEvent.toolKind, "mcp_bridge");
|
|
968
|
+
input.trace({
|
|
969
|
+
type: "tool.result",
|
|
970
|
+
runId: input.runId,
|
|
971
|
+
actionCallId,
|
|
972
|
+
name,
|
|
973
|
+
result,
|
|
974
|
+
toolKind,
|
|
975
|
+
parentActionCallId: input.parentActionCallId,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function traceOpenResponsesStreamEvent(input) {
|
|
979
|
+
if (!input.trace)
|
|
980
|
+
return false;
|
|
981
|
+
const type = typeof input.streamEvent.type === "string"
|
|
982
|
+
? input.streamEvent.type
|
|
983
|
+
: "";
|
|
984
|
+
if (!type.startsWith("response."))
|
|
985
|
+
return false;
|
|
986
|
+
const rawMeta = input.streamEvent._gambit;
|
|
987
|
+
const existingMeta = rawMeta && typeof rawMeta === "object" &&
|
|
988
|
+
!Array.isArray(rawMeta)
|
|
989
|
+
? rawMeta
|
|
990
|
+
: {};
|
|
991
|
+
input.trace({
|
|
992
|
+
...input.streamEvent,
|
|
993
|
+
type,
|
|
994
|
+
_gambit: {
|
|
995
|
+
...existingMeta,
|
|
996
|
+
run_id: input.runId,
|
|
997
|
+
action_call_id: input.actionCallId,
|
|
998
|
+
parent_action_call_id: input.parentActionCallId,
|
|
999
|
+
deck_path: input.deckPath,
|
|
1000
|
+
model: input.model,
|
|
1001
|
+
},
|
|
1002
|
+
});
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
function mapResponseOutput(output) {
|
|
1006
|
+
const toolCalls = [];
|
|
1007
|
+
const textParts = [];
|
|
1008
|
+
for (const item of output) {
|
|
1009
|
+
if (item.type === "function_call") {
|
|
1010
|
+
toolCalls.push({
|
|
1011
|
+
id: item.call_id,
|
|
1012
|
+
name: item.name,
|
|
1013
|
+
args: safeJsonArgs(item.arguments),
|
|
1014
|
+
});
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (item.type === "message" && item.role === "assistant") {
|
|
1018
|
+
for (const part of item.content) {
|
|
1019
|
+
if (part.type === "output_text") {
|
|
1020
|
+
textParts.push(part.text);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
message: {
|
|
1027
|
+
role: "assistant",
|
|
1028
|
+
content: textParts.length ? textParts.join("") : null,
|
|
1029
|
+
},
|
|
1030
|
+
toolCalls: toolCalls.length ? toolCalls : undefined,
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
210
1033
|
function validateInput(deck, input, isRoot, allowRootStringInput) {
|
|
211
|
-
|
|
1034
|
+
const contextSchema = resolveContextSchema(deck);
|
|
1035
|
+
if (contextSchema) {
|
|
212
1036
|
if (isRoot && typeof input === "string" && allowRootStringInput) {
|
|
213
1037
|
try {
|
|
214
|
-
return (0, schema_js_1.validateWithSchema)(
|
|
1038
|
+
return (0, schema_js_1.validateWithSchema)(contextSchema, input);
|
|
215
1039
|
}
|
|
216
1040
|
catch {
|
|
217
1041
|
return input;
|
|
218
1042
|
}
|
|
219
1043
|
}
|
|
220
|
-
return (0, schema_js_1.validateWithSchema)(
|
|
1044
|
+
return (0, schema_js_1.validateWithSchema)(contextSchema, input);
|
|
221
1045
|
}
|
|
222
1046
|
if (isRoot) {
|
|
223
1047
|
if (input === undefined)
|
|
@@ -226,28 +1050,1209 @@ function validateInput(deck, input, isRoot, allowRootStringInput) {
|
|
|
226
1050
|
return input;
|
|
227
1051
|
return input;
|
|
228
1052
|
}
|
|
229
|
-
throw new Error(`Deck ${deck.path} requires
|
|
1053
|
+
throw new Error(`Deck ${deck.path} requires contextSchema (non-root)`);
|
|
230
1054
|
}
|
|
231
1055
|
function validateOutput(deck, output, isRoot) {
|
|
232
|
-
|
|
233
|
-
|
|
1056
|
+
const responseSchema = resolveResponseSchema(deck);
|
|
1057
|
+
if (responseSchema) {
|
|
1058
|
+
return (0, schema_js_1.validateWithSchema)(responseSchema, output);
|
|
1059
|
+
}
|
|
1060
|
+
if (isRoot) {
|
|
1061
|
+
if (typeof output === "string")
|
|
1062
|
+
return output;
|
|
1063
|
+
return JSON.stringify(output);
|
|
1064
|
+
}
|
|
1065
|
+
throw new Error(`Deck ${deck.path} requires responseSchema (non-root)`);
|
|
1066
|
+
}
|
|
1067
|
+
async function runComputeDeck(ctx) {
|
|
1068
|
+
if (ctx.workerSandbox) {
|
|
1069
|
+
return await runComputeDeckInWorker({
|
|
1070
|
+
guardrails: ctx.guardrails,
|
|
1071
|
+
depth: ctx.depth,
|
|
1072
|
+
runId: ctx.runId,
|
|
1073
|
+
inputProvided: ctx.inputProvided,
|
|
1074
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
1075
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
1076
|
+
modelProvider: ctx.modelProvider,
|
|
1077
|
+
input: ctx.input,
|
|
1078
|
+
defaultModel: ctx.defaultModel,
|
|
1079
|
+
modelOverride: ctx.modelOverride,
|
|
1080
|
+
trace: ctx.trace,
|
|
1081
|
+
stream: ctx.stream,
|
|
1082
|
+
state: ctx.state,
|
|
1083
|
+
onStateUpdate: ctx.onStateUpdate,
|
|
1084
|
+
onStreamText: ctx.onStreamText,
|
|
1085
|
+
responsesMode: ctx.responsesMode,
|
|
1086
|
+
permissions: ctx.permissions,
|
|
1087
|
+
permissionsTrace: ctx.permissionsTrace,
|
|
1088
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
1089
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
1090
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
1091
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
1092
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
1093
|
+
deckPath: ctx.deck.path,
|
|
1094
|
+
isRoot: ctx.depth === 0 && !ctx.parentActionCallId,
|
|
1095
|
+
allowRootStringInput: false,
|
|
1096
|
+
signal: ctx.signal,
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
return await runComputeDeckInProcess(ctx);
|
|
1100
|
+
}
|
|
1101
|
+
function toDenoPermissionList(scope) {
|
|
1102
|
+
if (scope.all)
|
|
1103
|
+
return true;
|
|
1104
|
+
if (scope.values.size === 0)
|
|
1105
|
+
return false;
|
|
1106
|
+
return Array.from(scope.values).sort();
|
|
1107
|
+
}
|
|
1108
|
+
function toDenoRunPermission(scope) {
|
|
1109
|
+
if (scope.all)
|
|
1110
|
+
return true;
|
|
1111
|
+
const values = new Set([
|
|
1112
|
+
...Array.from(scope.paths),
|
|
1113
|
+
...Array.from(scope.commands),
|
|
1114
|
+
]);
|
|
1115
|
+
if (values.size === 0)
|
|
1116
|
+
return false;
|
|
1117
|
+
return Array.from(values).sort();
|
|
1118
|
+
}
|
|
1119
|
+
const IMPORT_SOURCE_EXTENSIONS = new Set([
|
|
1120
|
+
".ts",
|
|
1121
|
+
".tsx",
|
|
1122
|
+
".mts",
|
|
1123
|
+
".cts",
|
|
1124
|
+
".js",
|
|
1125
|
+
".jsx",
|
|
1126
|
+
".mjs",
|
|
1127
|
+
".cjs",
|
|
1128
|
+
]);
|
|
1129
|
+
const RESOLVABLE_MODULE_EXTENSIONS = [
|
|
1130
|
+
".ts",
|
|
1131
|
+
".tsx",
|
|
1132
|
+
".mts",
|
|
1133
|
+
".cts",
|
|
1134
|
+
".js",
|
|
1135
|
+
".jsx",
|
|
1136
|
+
".mjs",
|
|
1137
|
+
".cjs",
|
|
1138
|
+
".json",
|
|
1139
|
+
];
|
|
1140
|
+
function stripSpecifierSuffix(specifier) {
|
|
1141
|
+
let out = specifier;
|
|
1142
|
+
const q = out.indexOf("?");
|
|
1143
|
+
if (q >= 0)
|
|
1144
|
+
out = out.slice(0, q);
|
|
1145
|
+
const h = out.indexOf("#");
|
|
1146
|
+
if (h >= 0)
|
|
1147
|
+
out = out.slice(0, h);
|
|
1148
|
+
return out.trim();
|
|
1149
|
+
}
|
|
1150
|
+
function isIdentifierStart(ch) {
|
|
1151
|
+
return /[A-Za-z_$]/.test(ch);
|
|
1152
|
+
}
|
|
1153
|
+
function isIdentifierContinue(ch) {
|
|
1154
|
+
return /[A-Za-z0-9_$]/.test(ch);
|
|
1155
|
+
}
|
|
1156
|
+
function skipWhitespaceAndComments(source, start) {
|
|
1157
|
+
let i = start;
|
|
1158
|
+
while (i < source.length) {
|
|
1159
|
+
const ch = source[i];
|
|
1160
|
+
if (/\s/.test(ch)) {
|
|
1161
|
+
i++;
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
1165
|
+
i += 2;
|
|
1166
|
+
while (i < source.length && source[i] !== "\n" && source[i] !== "\r") {
|
|
1167
|
+
i++;
|
|
1168
|
+
}
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
1172
|
+
i += 2;
|
|
1173
|
+
while (i < source.length) {
|
|
1174
|
+
if (source[i] === "*" && source[i + 1] === "/") {
|
|
1175
|
+
i += 2;
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
i++;
|
|
1179
|
+
}
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
return i;
|
|
1185
|
+
}
|
|
1186
|
+
function readIdentifier(source, start) {
|
|
1187
|
+
if (start >= source.length)
|
|
1188
|
+
return undefined;
|
|
1189
|
+
if (!isIdentifierStart(source[start]))
|
|
1190
|
+
return undefined;
|
|
1191
|
+
let i = start + 1;
|
|
1192
|
+
while (i < source.length && isIdentifierContinue(source[i]))
|
|
1193
|
+
i++;
|
|
1194
|
+
return { value: source.slice(start, i), end: i };
|
|
1195
|
+
}
|
|
1196
|
+
function readStringLiteral(source, start) {
|
|
1197
|
+
const quote = source[start];
|
|
1198
|
+
if (quote !== "'" && quote !== '"')
|
|
1199
|
+
return undefined;
|
|
1200
|
+
let i = start + 1;
|
|
1201
|
+
let value = "";
|
|
1202
|
+
while (i < source.length) {
|
|
1203
|
+
const ch = source[i];
|
|
1204
|
+
if (ch === "\\") {
|
|
1205
|
+
if (i + 1 >= source.length)
|
|
1206
|
+
return undefined;
|
|
1207
|
+
value += source[i + 1];
|
|
1208
|
+
i += 2;
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (ch === quote)
|
|
1212
|
+
return { value, end: i + 1 };
|
|
1213
|
+
if (ch === "\n" || ch === "\r")
|
|
1214
|
+
return undefined;
|
|
1215
|
+
value += ch;
|
|
1216
|
+
i++;
|
|
1217
|
+
}
|
|
1218
|
+
return undefined;
|
|
1219
|
+
}
|
|
1220
|
+
function skipTemplateExpression(source, start) {
|
|
1221
|
+
let i = start;
|
|
1222
|
+
let depth = 1;
|
|
1223
|
+
while (i < source.length && depth > 0) {
|
|
1224
|
+
i = skipWhitespaceAndComments(source, i);
|
|
1225
|
+
if (i >= source.length)
|
|
1226
|
+
break;
|
|
1227
|
+
const ch = source[i];
|
|
1228
|
+
if (ch === "'" || ch === '"') {
|
|
1229
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1230
|
+
i = stringLiteral ? stringLiteral.end : i + 1;
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (ch === "`") {
|
|
1234
|
+
i = skipTemplateLiteral(source, i);
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (ch === "{") {
|
|
1238
|
+
depth++;
|
|
1239
|
+
i++;
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (ch === "}") {
|
|
1243
|
+
depth--;
|
|
1244
|
+
i++;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
i++;
|
|
1248
|
+
}
|
|
1249
|
+
return i;
|
|
1250
|
+
}
|
|
1251
|
+
function skipTemplateLiteral(source, start) {
|
|
1252
|
+
let i = start + 1;
|
|
1253
|
+
while (i < source.length) {
|
|
1254
|
+
const ch = source[i];
|
|
1255
|
+
if (ch === "\\") {
|
|
1256
|
+
i += 2;
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
if (ch === "`")
|
|
1260
|
+
return i + 1;
|
|
1261
|
+
if (ch === "$" && source[i + 1] === "{") {
|
|
1262
|
+
i = skipTemplateExpression(source, i + 2);
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
i++;
|
|
1266
|
+
}
|
|
1267
|
+
return i;
|
|
1268
|
+
}
|
|
1269
|
+
function readSpecifierAfterFrom(source, start) {
|
|
1270
|
+
const i = skipWhitespaceAndComments(source, start);
|
|
1271
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1272
|
+
if (!stringLiteral)
|
|
1273
|
+
return { end: i };
|
|
1274
|
+
return { specifier: stringLiteral.value, end: stringLiteral.end };
|
|
1275
|
+
}
|
|
1276
|
+
function readImportCallSpecifier(source, start) {
|
|
1277
|
+
let i = skipWhitespaceAndComments(source, start);
|
|
1278
|
+
if (source[i] !== "(")
|
|
1279
|
+
return { end: i };
|
|
1280
|
+
i = skipWhitespaceAndComments(source, i + 1);
|
|
1281
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1282
|
+
if (!stringLiteral)
|
|
1283
|
+
return { end: i };
|
|
1284
|
+
i = skipWhitespaceAndComments(source, stringLiteral.end);
|
|
1285
|
+
if (source[i] === ")")
|
|
1286
|
+
i++;
|
|
1287
|
+
return { specifier: stringLiteral.value, end: i };
|
|
1288
|
+
}
|
|
1289
|
+
function readImportOrExportStatementSpecifier(source, start, keyword) {
|
|
1290
|
+
let i = skipWhitespaceAndComments(source, start);
|
|
1291
|
+
if (keyword === "import") {
|
|
1292
|
+
if (source[i] === ".")
|
|
1293
|
+
return { end: i + 1 }; // import.meta
|
|
1294
|
+
const sideEffectImport = readStringLiteral(source, i);
|
|
1295
|
+
if (sideEffectImport) {
|
|
1296
|
+
return { specifier: sideEffectImport.value, end: sideEffectImport.end };
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
let depth = 0;
|
|
1300
|
+
while (i < source.length) {
|
|
1301
|
+
i = skipWhitespaceAndComments(source, i);
|
|
1302
|
+
if (i >= source.length)
|
|
1303
|
+
break;
|
|
1304
|
+
const ch = source[i];
|
|
1305
|
+
if (ch === "'" || ch === '"') {
|
|
1306
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1307
|
+
i = stringLiteral ? stringLiteral.end : i + 1;
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
if (ch === "`") {
|
|
1311
|
+
i = skipTemplateLiteral(source, i);
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
1315
|
+
depth++;
|
|
1316
|
+
i++;
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
1320
|
+
if (depth > 0)
|
|
1321
|
+
depth--;
|
|
1322
|
+
i++;
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (depth === 0) {
|
|
1326
|
+
if (ch === ";")
|
|
1327
|
+
return { end: i + 1 };
|
|
1328
|
+
const identifier = readIdentifier(source, i);
|
|
1329
|
+
if (identifier?.value === "from") {
|
|
1330
|
+
return readSpecifierAfterFrom(source, identifier.end);
|
|
1331
|
+
}
|
|
1332
|
+
if (identifier) {
|
|
1333
|
+
i = identifier.end;
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
i++;
|
|
1338
|
+
}
|
|
1339
|
+
return { end: i };
|
|
1340
|
+
}
|
|
1341
|
+
function extractModuleSpecifiers(source) {
|
|
1342
|
+
const out = new Set();
|
|
1343
|
+
let i = 0;
|
|
1344
|
+
while (i < source.length) {
|
|
1345
|
+
i = skipWhitespaceAndComments(source, i);
|
|
1346
|
+
if (i >= source.length)
|
|
1347
|
+
break;
|
|
1348
|
+
const ch = source[i];
|
|
1349
|
+
if (ch === "'" || ch === '"') {
|
|
1350
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1351
|
+
i = stringLiteral ? stringLiteral.end : i + 1;
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
if (ch === "`") {
|
|
1355
|
+
i = skipTemplateLiteral(source, i);
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
const identifier = readIdentifier(source, i);
|
|
1359
|
+
if (!identifier) {
|
|
1360
|
+
i++;
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
if (identifier.value === "import") {
|
|
1364
|
+
const afterImport = skipWhitespaceAndComments(source, identifier.end);
|
|
1365
|
+
if (source[afterImport] === "(") {
|
|
1366
|
+
const result = readImportCallSpecifier(source, afterImport);
|
|
1367
|
+
if (result.specifier)
|
|
1368
|
+
out.add(result.specifier);
|
|
1369
|
+
i = Math.max(result.end, afterImport + 1);
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
const result = readImportOrExportStatementSpecifier(source, identifier.end, "import");
|
|
1373
|
+
if (result.specifier)
|
|
1374
|
+
out.add(result.specifier);
|
|
1375
|
+
i = Math.max(result.end, identifier.end);
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
if (identifier.value === "export") {
|
|
1379
|
+
const result = readImportOrExportStatementSpecifier(source, identifier.end, "export");
|
|
1380
|
+
if (result.specifier)
|
|
1381
|
+
out.add(result.specifier);
|
|
1382
|
+
i = Math.max(result.end, identifier.end);
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
i = identifier.end;
|
|
1386
|
+
}
|
|
1387
|
+
return out;
|
|
1388
|
+
}
|
|
1389
|
+
function resolveExistingModulePath(candidate) {
|
|
1390
|
+
const resolved = path.resolve(candidate);
|
|
1391
|
+
const candidates = new Set([resolved]);
|
|
1392
|
+
if (!path.extname(resolved)) {
|
|
1393
|
+
for (const ext of RESOLVABLE_MODULE_EXTENSIONS) {
|
|
1394
|
+
candidates.add(`${resolved}${ext}`);
|
|
1395
|
+
candidates.add(path.join(resolved, `index${ext}`));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
for (const filePath of candidates) {
|
|
1399
|
+
try {
|
|
1400
|
+
if (dntShim.Deno.statSync(filePath).isFile) {
|
|
1401
|
+
return path.resolve(filePath);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
catch {
|
|
1405
|
+
// ignore unresolved module candidates
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return undefined;
|
|
1409
|
+
}
|
|
1410
|
+
function resolveLocalImportPath(importerPath, specifier) {
|
|
1411
|
+
const cleaned = stripSpecifierSuffix(specifier);
|
|
1412
|
+
if (!cleaned)
|
|
1413
|
+
return undefined;
|
|
1414
|
+
if (cleaned.startsWith("file://")) {
|
|
1415
|
+
try {
|
|
1416
|
+
return resolveExistingModulePath(path.fromFileUrl(cleaned));
|
|
1417
|
+
}
|
|
1418
|
+
catch {
|
|
1419
|
+
return undefined;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (!(cleaned.startsWith("./") || cleaned.startsWith("../") ||
|
|
1423
|
+
path.isAbsolute(cleaned))) {
|
|
1424
|
+
return undefined;
|
|
1425
|
+
}
|
|
1426
|
+
const base = path.isAbsolute(cleaned)
|
|
1427
|
+
? cleaned
|
|
1428
|
+
: path.resolve(path.dirname(importerPath), cleaned);
|
|
1429
|
+
return resolveExistingModulePath(base);
|
|
1430
|
+
}
|
|
1431
|
+
function collectLocalImportGraph(entryPath) {
|
|
1432
|
+
const visited = new Set();
|
|
1433
|
+
const queue = [path.resolve(entryPath)];
|
|
1434
|
+
while (queue.length > 0) {
|
|
1435
|
+
const current = queue.pop();
|
|
1436
|
+
if (visited.has(current))
|
|
1437
|
+
continue;
|
|
1438
|
+
visited.add(current);
|
|
1439
|
+
const ext = path.extname(current).toLowerCase();
|
|
1440
|
+
if (!IMPORT_SOURCE_EXTENSIONS.has(ext)) {
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
let source;
|
|
1444
|
+
try {
|
|
1445
|
+
source = dntShim.Deno.readTextFileSync(current);
|
|
1446
|
+
}
|
|
1447
|
+
catch {
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
const specifiers = extractModuleSpecifiers(source);
|
|
1451
|
+
for (const specifier of specifiers) {
|
|
1452
|
+
const resolved = resolveLocalImportPath(current, specifier);
|
|
1453
|
+
if (resolved && !visited.has(resolved)) {
|
|
1454
|
+
queue.push(resolved);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return visited;
|
|
1459
|
+
}
|
|
1460
|
+
const WORKER_ENTRY_PATHS = [
|
|
1461
|
+
"./runtime_worker.ts",
|
|
1462
|
+
"./runtime_orchestration_worker.ts",
|
|
1463
|
+
].map((relative) => path.fromFileUrl(new URL(relative, globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url)));
|
|
1464
|
+
const BUILTIN_SCHEMAS_DIR = path.resolve(path.dirname(path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url)), "../schemas");
|
|
1465
|
+
const BUILTIN_SNIPPETS_DIR = path.resolve(path.dirname(path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url)), "../snippets");
|
|
1466
|
+
let builtinSchemaBootstrapCache;
|
|
1467
|
+
function builtinSchemaBootstrapReads() {
|
|
1468
|
+
if (builtinSchemaBootstrapCache)
|
|
1469
|
+
return builtinSchemaBootstrapCache;
|
|
1470
|
+
const schemaModules = [];
|
|
1471
|
+
const stack = [BUILTIN_SCHEMAS_DIR];
|
|
1472
|
+
while (stack.length > 0) {
|
|
1473
|
+
const current = stack.pop();
|
|
1474
|
+
let entries = [];
|
|
1475
|
+
try {
|
|
1476
|
+
entries = Array.from(dntShim.Deno.readDirSync(current));
|
|
1477
|
+
}
|
|
1478
|
+
catch {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
for (const entry of entries) {
|
|
1482
|
+
const target = path.join(current, entry.name);
|
|
1483
|
+
if (entry.isDirectory) {
|
|
1484
|
+
stack.push(target);
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (!entry.isFile)
|
|
1488
|
+
continue;
|
|
1489
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1490
|
+
if (ext !== ".ts")
|
|
1491
|
+
continue;
|
|
1492
|
+
schemaModules.push(target);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
builtinSchemaBootstrapCache = Array.from(new Set(schemaModules.flatMap((entry) => Array.from(collectLocalImportGraph(entry))))).sort();
|
|
1496
|
+
return builtinSchemaBootstrapCache;
|
|
1497
|
+
}
|
|
1498
|
+
let builtinSnippetBootstrapCache;
|
|
1499
|
+
function builtinSnippetBootstrapReads() {
|
|
1500
|
+
if (builtinSnippetBootstrapCache)
|
|
1501
|
+
return builtinSnippetBootstrapCache;
|
|
1502
|
+
const snippetFiles = [];
|
|
1503
|
+
const stack = [BUILTIN_SNIPPETS_DIR];
|
|
1504
|
+
while (stack.length > 0) {
|
|
1505
|
+
const current = stack.pop();
|
|
1506
|
+
let entries = [];
|
|
1507
|
+
try {
|
|
1508
|
+
entries = Array.from(dntShim.Deno.readDirSync(current));
|
|
1509
|
+
}
|
|
1510
|
+
catch {
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
for (const entry of entries) {
|
|
1514
|
+
const target = path.join(current, entry.name);
|
|
1515
|
+
if (entry.isDirectory) {
|
|
1516
|
+
stack.push(target);
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
if (!entry.isFile)
|
|
1520
|
+
continue;
|
|
1521
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1522
|
+
if (ext !== ".md")
|
|
1523
|
+
continue;
|
|
1524
|
+
snippetFiles.push(target);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
builtinSnippetBootstrapCache = Array.from(new Set(snippetFiles)).sort();
|
|
1528
|
+
return builtinSnippetBootstrapCache;
|
|
1529
|
+
}
|
|
1530
|
+
function workerBootstrapReadAllowlist(deckPath) {
|
|
1531
|
+
return Array.from(new Set([
|
|
1532
|
+
...Array.from(collectLocalImportGraph(deckPath)),
|
|
1533
|
+
...WORKER_ENTRY_PATHS.flatMap((entry) => Array.from(collectLocalImportGraph(entry))),
|
|
1534
|
+
...builtinSchemaBootstrapReads(),
|
|
1535
|
+
...builtinSnippetBootstrapReads(),
|
|
1536
|
+
])).sort();
|
|
1537
|
+
}
|
|
1538
|
+
let trustedWorkerBootstrapCache;
|
|
1539
|
+
function trustedWorkerBootstrapReads() {
|
|
1540
|
+
if (trustedWorkerBootstrapCache)
|
|
1541
|
+
return trustedWorkerBootstrapCache;
|
|
1542
|
+
const definitionsPath = path.fromFileUrl(new URL("./definitions.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url));
|
|
1543
|
+
const modPath = path.fromFileUrl(new URL("../mod.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url));
|
|
1544
|
+
trustedWorkerBootstrapCache = Array.from(new Set([
|
|
1545
|
+
...WORKER_ENTRY_PATHS.flatMap((entry) => Array.from(collectLocalImportGraph(entry))),
|
|
1546
|
+
...Array.from(collectLocalImportGraph(definitionsPath)),
|
|
1547
|
+
...Array.from(collectLocalImportGraph(modPath)),
|
|
1548
|
+
...builtinSchemaBootstrapReads(),
|
|
1549
|
+
...builtinSnippetBootstrapReads(),
|
|
1550
|
+
])).sort();
|
|
1551
|
+
return trustedWorkerBootstrapCache;
|
|
1552
|
+
}
|
|
1553
|
+
function pathMatchesPermissionRoot(root, target) {
|
|
1554
|
+
if (root === target)
|
|
1555
|
+
return true;
|
|
1556
|
+
const rel = path.relative(root, target);
|
|
1557
|
+
return rel.length > 0 && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
1558
|
+
}
|
|
1559
|
+
function constrainBootstrapReads(permissions, roots, trustedReads, reads) {
|
|
1560
|
+
const allowedRoots = [
|
|
1561
|
+
...roots.map((entry) => path.resolve(entry)),
|
|
1562
|
+
...Array.from(permissions.read.values).map((entry) => path.resolve(permissions.baseDir, entry)),
|
|
1563
|
+
];
|
|
1564
|
+
if (permissions.read.all) {
|
|
1565
|
+
return Array.from(new Set(reads)).sort();
|
|
1566
|
+
}
|
|
1567
|
+
if (allowedRoots.length === 0)
|
|
1568
|
+
return [];
|
|
1569
|
+
return reads.filter((entry) => {
|
|
1570
|
+
const target = path.resolve(permissions.baseDir, entry);
|
|
1571
|
+
if (trustedReads.has(target))
|
|
1572
|
+
return true;
|
|
1573
|
+
return allowedRoots.some((root) => pathMatchesPermissionRoot(root, target));
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
function buildWorkerPermissions(permissions, deckPath) {
|
|
1577
|
+
const workerDirs = WORKER_ENTRY_PATHS.map((entry) => path.dirname(entry));
|
|
1578
|
+
const bootstrapReads = constrainBootstrapReads(permissions, [path.dirname(deckPath), ...workerDirs], new Set(trustedWorkerBootstrapReads()), workerBootstrapReadAllowlist(deckPath));
|
|
1579
|
+
const mergedRead = permissions.read.all ? true : Array.from(new Set([
|
|
1580
|
+
...Array.from(permissions.read.values),
|
|
1581
|
+
...bootstrapReads,
|
|
1582
|
+
])).sort();
|
|
1583
|
+
return {
|
|
1584
|
+
permissions: {
|
|
1585
|
+
read: mergedRead === true
|
|
1586
|
+
? true
|
|
1587
|
+
: mergedRead.length > 0
|
|
1588
|
+
? mergedRead
|
|
1589
|
+
: false,
|
|
1590
|
+
write: toDenoPermissionList(permissions.write),
|
|
1591
|
+
run: toDenoRunPermission(permissions.run),
|
|
1592
|
+
net: toDenoPermissionList(permissions.net),
|
|
1593
|
+
env: toDenoPermissionList(permissions.env),
|
|
1594
|
+
// Worker module graphs include JSR dependencies (e.g. @std/*). Allow
|
|
1595
|
+
// manifest resolution without widening deck runtime file/run permissions.
|
|
1596
|
+
import: ["jsr.io:443"],
|
|
1597
|
+
},
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
function buildDeckInspectWorkerPermissions(deckPath) {
|
|
1601
|
+
const deckDir = path.dirname(deckPath);
|
|
1602
|
+
const workerDirs = WORKER_ENTRY_PATHS.map((entry) => path.dirname(entry));
|
|
1603
|
+
const inspectSeedPermissions = {
|
|
1604
|
+
baseDir: deckDir,
|
|
1605
|
+
read: { all: false, values: new Set() },
|
|
1606
|
+
write: { all: false, values: new Set() },
|
|
1607
|
+
run: { all: false, paths: new Set(), commands: new Set() },
|
|
1608
|
+
net: { all: false, values: new Set() },
|
|
1609
|
+
env: { all: false, values: new Set() },
|
|
1610
|
+
};
|
|
1611
|
+
const bootstrapReads = constrainBootstrapReads(inspectSeedPermissions, [path.dirname(deckPath), ...workerDirs], new Set(trustedWorkerBootstrapReads()), workerBootstrapReadAllowlist(deckPath));
|
|
1612
|
+
const inspectReads = Array.from(new Set([deckDir, ...bootstrapReads])).sort();
|
|
1613
|
+
return {
|
|
1614
|
+
permissions: {
|
|
1615
|
+
read: inspectReads.length > 0 ? inspectReads : false,
|
|
1616
|
+
write: false,
|
|
1617
|
+
run: false,
|
|
1618
|
+
net: false,
|
|
1619
|
+
env: false,
|
|
1620
|
+
},
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
async function inspectDeckInWorker(deckPath, runDeadlineMs) {
|
|
1624
|
+
if (typeof runDeadlineMs === "number" && Number.isFinite(runDeadlineMs)) {
|
|
1625
|
+
ensureNotExpired(runDeadlineMs);
|
|
1626
|
+
}
|
|
1627
|
+
const bridgeSession = randomId("bridge");
|
|
1628
|
+
const worker = (0, runtime_worker_host_js_1.createWorkerSandboxBridge)(new URL("./runtime_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).href, buildDeckInspectWorkerPermissions(deckPath));
|
|
1629
|
+
let settled = false;
|
|
1630
|
+
const clearAndTerminate = () => {
|
|
1631
|
+
try {
|
|
1632
|
+
worker.terminate();
|
|
1633
|
+
}
|
|
1634
|
+
catch {
|
|
1635
|
+
// ignore
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
let timeoutId;
|
|
1639
|
+
const outcome = new Promise((resolve, reject) => {
|
|
1640
|
+
const finishResolve = (value) => {
|
|
1641
|
+
if (settled)
|
|
1642
|
+
return;
|
|
1643
|
+
settled = true;
|
|
1644
|
+
if (timeoutId !== undefined)
|
|
1645
|
+
clearTimeout(timeoutId);
|
|
1646
|
+
resolve(value);
|
|
1647
|
+
};
|
|
1648
|
+
const finishReject = (err) => {
|
|
1649
|
+
if (settled)
|
|
1650
|
+
return;
|
|
1651
|
+
settled = true;
|
|
1652
|
+
if (timeoutId !== undefined)
|
|
1653
|
+
clearTimeout(timeoutId);
|
|
1654
|
+
reject(err);
|
|
1655
|
+
};
|
|
1656
|
+
const deadlineConstrained = typeof runDeadlineMs === "number" &&
|
|
1657
|
+
Number.isFinite(runDeadlineMs);
|
|
1658
|
+
const timeoutMs = deadlineConstrained
|
|
1659
|
+
? Math.max(0, Math.min(INSPECT_WORKER_TIMEOUT_MS, Math.floor(runDeadlineMs - performance.now())))
|
|
1660
|
+
: INSPECT_WORKER_TIMEOUT_MS;
|
|
1661
|
+
const timeoutMessage = deadlineConstrained &&
|
|
1662
|
+
timeoutMs < INSPECT_WORKER_TIMEOUT_MS
|
|
1663
|
+
? WORKER_TIMEOUT_MESSAGE
|
|
1664
|
+
: INSPECT_WORKER_TIMEOUT_MESSAGE;
|
|
1665
|
+
timeoutId = setTimeout(() => {
|
|
1666
|
+
finishReject(new Error(timeoutMessage));
|
|
1667
|
+
clearAndTerminate();
|
|
1668
|
+
}, timeoutMs);
|
|
1669
|
+
worker.addEventListener("error", (event) => {
|
|
1670
|
+
event.preventDefault?.();
|
|
1671
|
+
finishReject(event.error ??
|
|
1672
|
+
new Error(typeof event.message === "string"
|
|
1673
|
+
? event.message
|
|
1674
|
+
: "Worker execution failed"));
|
|
1675
|
+
});
|
|
1676
|
+
worker.addEventListener("messageerror", () => {
|
|
1677
|
+
finishReject(new Error("Worker bridge message serialization failed"));
|
|
1678
|
+
});
|
|
1679
|
+
worker.addEventListener("message", (event) => {
|
|
1680
|
+
const msg = event.data;
|
|
1681
|
+
const receivedSession = typeof msg.bridgeSession === "string"
|
|
1682
|
+
? msg.bridgeSession
|
|
1683
|
+
: "";
|
|
1684
|
+
if (receivedSession !== bridgeSession) {
|
|
1685
|
+
if (typeof msg.type === "string") {
|
|
1686
|
+
logger.warn(`[gambit] rejected inspect-worker message with mismatched bridge session (type=${msg.type})`);
|
|
1687
|
+
}
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
1691
|
+
if (type === "deck.inspect.result") {
|
|
1692
|
+
finishResolve(msg.result);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (type === "deck.inspect.error" || type === "run.error") {
|
|
1696
|
+
finishReject(normalizeWorkerError(msg.error));
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
try {
|
|
1701
|
+
worker.postMessage({ type: "deck.inspect", bridgeSession, deckPath });
|
|
1702
|
+
return await outcome;
|
|
1703
|
+
}
|
|
1704
|
+
finally {
|
|
1705
|
+
if (timeoutId !== undefined)
|
|
1706
|
+
clearTimeout(timeoutId);
|
|
1707
|
+
clearAndTerminate();
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
function normalizeWorkerError(err) {
|
|
1711
|
+
if (!err || typeof err !== "object") {
|
|
1712
|
+
return new Error(String(err));
|
|
1713
|
+
}
|
|
1714
|
+
const rec = err;
|
|
1715
|
+
const message = typeof rec.message === "string" && rec.message.trim().length > 0
|
|
1716
|
+
? rec.message
|
|
1717
|
+
: "Worker execution failed";
|
|
1718
|
+
const code = typeof rec.code === "string" ? rec.code : undefined;
|
|
1719
|
+
const name = typeof rec.name === "string" ? rec.name : undefined;
|
|
1720
|
+
const source = typeof rec.source === "string" ? rec.source : undefined;
|
|
1721
|
+
const out = new Error(source ? `[${source}] ${message}${code ? ` (${code})` : ""}` : message);
|
|
1722
|
+
if (name)
|
|
1723
|
+
out.name = name;
|
|
1724
|
+
return out;
|
|
1725
|
+
}
|
|
1726
|
+
async function runLlmDeckInWorker(ctx) {
|
|
1727
|
+
throwIfCanceled(ctx.signal);
|
|
1728
|
+
const bridgeSession = randomId("bridge");
|
|
1729
|
+
const completionNonce = randomId("done");
|
|
1730
|
+
const worker = (0, runtime_worker_host_js_1.createWorkerSandboxBridge)(new URL("./runtime_orchestration_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
|
|
1731
|
+
let settled = false;
|
|
1732
|
+
const clearAndTerminate = () => {
|
|
1733
|
+
try {
|
|
1734
|
+
worker.terminate();
|
|
1735
|
+
}
|
|
1736
|
+
catch {
|
|
1737
|
+
// ignore
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
let timeoutId;
|
|
1741
|
+
const outcome = new Promise((resolve, reject) => {
|
|
1742
|
+
const finishResolve = (value) => {
|
|
1743
|
+
if (settled)
|
|
1744
|
+
return;
|
|
1745
|
+
settled = true;
|
|
1746
|
+
if (timeoutId !== undefined)
|
|
1747
|
+
clearTimeout(timeoutId);
|
|
1748
|
+
resolve(value);
|
|
1749
|
+
};
|
|
1750
|
+
const finishReject = (err) => {
|
|
1751
|
+
if (settled)
|
|
1752
|
+
return;
|
|
1753
|
+
settled = true;
|
|
1754
|
+
if (timeoutId !== undefined)
|
|
1755
|
+
clearTimeout(timeoutId);
|
|
1756
|
+
reject(err);
|
|
1757
|
+
};
|
|
1758
|
+
const remainingMs = Math.max(0, Math.floor(ctx.runDeadlineMs - performance.now()));
|
|
1759
|
+
timeoutId = setTimeout(() => {
|
|
1760
|
+
finishReject(new Error(WORKER_TIMEOUT_MESSAGE));
|
|
1761
|
+
clearAndTerminate();
|
|
1762
|
+
}, remainingMs);
|
|
1763
|
+
worker.addEventListener("error", (event) => {
|
|
1764
|
+
event.preventDefault?.();
|
|
1765
|
+
finishReject(event.error ??
|
|
1766
|
+
new Error(typeof event.message === "string"
|
|
1767
|
+
? event.message
|
|
1768
|
+
: "Worker execution failed"));
|
|
1769
|
+
});
|
|
1770
|
+
worker.addEventListener("messageerror", () => {
|
|
1771
|
+
finishReject(new Error("Worker bridge message serialization failed"));
|
|
1772
|
+
});
|
|
1773
|
+
worker.addEventListener("message", (event) => {
|
|
1774
|
+
const msg = event.data;
|
|
1775
|
+
if (!msg || typeof msg !== "object")
|
|
1776
|
+
return;
|
|
1777
|
+
if (msg.bridgeSession !== bridgeSession) {
|
|
1778
|
+
logger.warn(`[gambit] rejected orchestration-worker message with mismatched bridge session (type=${msg.type})`);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
if (msg.type === "trace.event") {
|
|
1782
|
+
ctx.trace?.(msg.event);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
if (msg.type === "state.update") {
|
|
1786
|
+
ctx.onStateUpdate?.(msg.state);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
if (msg.type === "stream.text") {
|
|
1790
|
+
ctx.onStreamText?.(msg.chunk);
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
if (msg.type === "model.chat.request") {
|
|
1794
|
+
(async () => {
|
|
1795
|
+
try {
|
|
1796
|
+
const result = await ctx.modelProvider.chat({
|
|
1797
|
+
...msg.input,
|
|
1798
|
+
signal: ctx.signal,
|
|
1799
|
+
onStreamText: (chunk) => {
|
|
1800
|
+
worker.postMessage({
|
|
1801
|
+
type: "model.chat.stream",
|
|
1802
|
+
requestId: msg.requestId,
|
|
1803
|
+
chunk,
|
|
1804
|
+
});
|
|
1805
|
+
},
|
|
1806
|
+
});
|
|
1807
|
+
worker.postMessage({
|
|
1808
|
+
type: "model.chat.result",
|
|
1809
|
+
requestId: msg.requestId,
|
|
1810
|
+
result,
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
catch (err) {
|
|
1814
|
+
worker.postMessage({
|
|
1815
|
+
type: "model.chat.error",
|
|
1816
|
+
requestId: msg.requestId,
|
|
1817
|
+
error: {
|
|
1818
|
+
source: "model",
|
|
1819
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1820
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1821
|
+
code: err?.code,
|
|
1822
|
+
},
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
})();
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (msg.type === "model.responses.request") {
|
|
1829
|
+
(async () => {
|
|
1830
|
+
try {
|
|
1831
|
+
if (!ctx.modelProvider.responses) {
|
|
1832
|
+
throw new Error("Responses API unavailable for current model provider");
|
|
1833
|
+
}
|
|
1834
|
+
const result = await ctx.modelProvider.responses({
|
|
1835
|
+
...msg.input,
|
|
1836
|
+
signal: ctx.signal,
|
|
1837
|
+
onStreamEvent: (streamEvent) => {
|
|
1838
|
+
worker.postMessage({
|
|
1839
|
+
type: "model.responses.event",
|
|
1840
|
+
requestId: msg.requestId,
|
|
1841
|
+
event: streamEvent,
|
|
1842
|
+
});
|
|
1843
|
+
},
|
|
1844
|
+
});
|
|
1845
|
+
worker.postMessage({
|
|
1846
|
+
type: "model.responses.result",
|
|
1847
|
+
requestId: msg.requestId,
|
|
1848
|
+
result,
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
catch (err) {
|
|
1852
|
+
worker.postMessage({
|
|
1853
|
+
type: "model.responses.error",
|
|
1854
|
+
requestId: msg.requestId,
|
|
1855
|
+
error: {
|
|
1856
|
+
source: "model",
|
|
1857
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1858
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1859
|
+
code: err?.code,
|
|
1860
|
+
},
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
})();
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (msg.type === "model.resolveModel.request") {
|
|
1867
|
+
(async () => {
|
|
1868
|
+
try {
|
|
1869
|
+
const result = ctx.modelProvider.resolveModel
|
|
1870
|
+
? await ctx.modelProvider.resolveModel(msg.input)
|
|
1871
|
+
: {
|
|
1872
|
+
model: Array.isArray(msg.input.model)
|
|
1873
|
+
? msg.input.model[0]
|
|
1874
|
+
: msg.input.model,
|
|
1875
|
+
params: msg.input.params,
|
|
1876
|
+
};
|
|
1877
|
+
worker.postMessage({
|
|
1878
|
+
type: "model.resolveModel.result",
|
|
1879
|
+
requestId: msg.requestId,
|
|
1880
|
+
result,
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
catch (err) {
|
|
1884
|
+
worker.postMessage({
|
|
1885
|
+
type: "model.resolveModel.error",
|
|
1886
|
+
requestId: msg.requestId,
|
|
1887
|
+
error: {
|
|
1888
|
+
source: "model",
|
|
1889
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1890
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1891
|
+
code: err?.code,
|
|
1892
|
+
},
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
})();
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
if (msg.type === "run.result") {
|
|
1899
|
+
if (msg.completionNonce !== completionNonce) {
|
|
1900
|
+
logger.warn(`[gambit] rejected orchestration-worker run.result with invalid completion nonce`);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
finishResolve(msg.result);
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (msg.type === "run.error") {
|
|
1907
|
+
if (msg.completionNonce !== completionNonce) {
|
|
1908
|
+
logger.warn(`[gambit] rejected orchestration-worker run.error with invalid completion nonce`);
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
finishReject(normalizeWorkerError(msg.error));
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1914
|
+
});
|
|
1915
|
+
try {
|
|
1916
|
+
worker.postMessage({
|
|
1917
|
+
type: "run.start",
|
|
1918
|
+
bridgeSession,
|
|
1919
|
+
completionNonce,
|
|
1920
|
+
options: {
|
|
1921
|
+
path: ctx.deckPath,
|
|
1922
|
+
input: ctx.input,
|
|
1923
|
+
inputProvided: ctx.inputProvided,
|
|
1924
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
1925
|
+
isRoot: ctx.isRoot,
|
|
1926
|
+
guardrails: ctx.guardrails,
|
|
1927
|
+
depth: ctx.depth,
|
|
1928
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
1929
|
+
runId: ctx.runId,
|
|
1930
|
+
defaultModel: ctx.defaultModel,
|
|
1931
|
+
modelOverride: ctx.modelOverride,
|
|
1932
|
+
stream: ctx.stream,
|
|
1933
|
+
state: ctx.state,
|
|
1934
|
+
responsesMode: ctx.responsesMode,
|
|
1935
|
+
allowRootStringInput: ctx.allowRootStringInput,
|
|
1936
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
1937
|
+
},
|
|
1938
|
+
permissionCeiling: toWirePermissionSet(ctx.permissions),
|
|
1939
|
+
});
|
|
1940
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
1941
|
+
return await outcome;
|
|
1942
|
+
}
|
|
1943
|
+
finally {
|
|
1944
|
+
if (timeoutId !== undefined)
|
|
1945
|
+
clearTimeout(timeoutId);
|
|
1946
|
+
clearAndTerminate();
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
async function runComputeDeckInWorker(ctx) {
|
|
1950
|
+
throwIfCanceled(ctx.signal);
|
|
1951
|
+
const { runId } = ctx;
|
|
1952
|
+
const actionCallId = randomId("action");
|
|
1953
|
+
const bridgeSession = randomId("bridge");
|
|
1954
|
+
const completionNonce = randomId("done");
|
|
1955
|
+
const worker = (0, runtime_worker_host_js_1.createWorkerSandboxBridge)(new URL("./runtime_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
|
|
1956
|
+
let settled = false;
|
|
1957
|
+
const clearAndTerminate = () => {
|
|
1958
|
+
try {
|
|
1959
|
+
worker.terminate();
|
|
1960
|
+
}
|
|
1961
|
+
catch {
|
|
1962
|
+
// ignore
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
let timeoutId;
|
|
1966
|
+
const activeSpawnRequests = new Set();
|
|
1967
|
+
let currentState = ctx.state;
|
|
1968
|
+
const outcome = new Promise((resolve, reject) => {
|
|
1969
|
+
const finishResolve = (value) => {
|
|
1970
|
+
if (settled)
|
|
1971
|
+
return;
|
|
1972
|
+
settled = true;
|
|
1973
|
+
if (timeoutId !== undefined)
|
|
1974
|
+
clearTimeout(timeoutId);
|
|
1975
|
+
resolve(value);
|
|
1976
|
+
};
|
|
1977
|
+
const finishReject = (err) => {
|
|
1978
|
+
if (settled)
|
|
1979
|
+
return;
|
|
1980
|
+
settled = true;
|
|
1981
|
+
if (timeoutId !== undefined)
|
|
1982
|
+
clearTimeout(timeoutId);
|
|
1983
|
+
reject(err);
|
|
1984
|
+
};
|
|
1985
|
+
const remainingMs = Math.max(0, Math.floor(ctx.runDeadlineMs - performance.now()));
|
|
1986
|
+
timeoutId = setTimeout(() => {
|
|
1987
|
+
finishReject(new Error(WORKER_TIMEOUT_MESSAGE));
|
|
1988
|
+
clearAndTerminate();
|
|
1989
|
+
}, remainingMs);
|
|
1990
|
+
worker.addEventListener("error", (event) => {
|
|
1991
|
+
event.preventDefault?.();
|
|
1992
|
+
finishReject(event.error ??
|
|
1993
|
+
new Error(typeof event.message === "string"
|
|
1994
|
+
? event.message
|
|
1995
|
+
: "Worker execution failed"));
|
|
1996
|
+
});
|
|
1997
|
+
worker.addEventListener("messageerror", () => {
|
|
1998
|
+
finishReject(new Error("Worker bridge message serialization failed"));
|
|
1999
|
+
});
|
|
2000
|
+
worker.addEventListener("message", (event) => {
|
|
2001
|
+
const msg = event.data;
|
|
2002
|
+
const receivedBridgeSession = typeof msg.bridgeSession === "string"
|
|
2003
|
+
? msg.bridgeSession
|
|
2004
|
+
: "";
|
|
2005
|
+
if (receivedBridgeSession !== bridgeSession) {
|
|
2006
|
+
const type = typeof msg.type === "string" ? msg.type : "unknown";
|
|
2007
|
+
logger.warn(`[gambit] rejected compute-worker message with mismatched bridge session (type=${type})`);
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
// Ignore any late worker messages once this run has already settled.
|
|
2011
|
+
if (settled)
|
|
2012
|
+
return;
|
|
2013
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
2014
|
+
if (type === "log.entry") {
|
|
2015
|
+
if (!ctx.trace)
|
|
2016
|
+
return;
|
|
2017
|
+
const entry = msg.entry;
|
|
2018
|
+
const raw = typeof entry === "string"
|
|
2019
|
+
? { message: entry }
|
|
2020
|
+
: entry && typeof entry === "object"
|
|
2021
|
+
? entry
|
|
2022
|
+
: { message: "" };
|
|
2023
|
+
const message = typeof raw.message === "string"
|
|
2024
|
+
? raw.message
|
|
2025
|
+
: raw.message !== undefined
|
|
2026
|
+
? String(raw.message)
|
|
2027
|
+
: "";
|
|
2028
|
+
const title = typeof raw.title === "string" ? raw.title : undefined;
|
|
2029
|
+
const body = raw.body ?? raw.message ?? message;
|
|
2030
|
+
ctx.trace({
|
|
2031
|
+
type: "log",
|
|
2032
|
+
runId,
|
|
2033
|
+
deckPath: ctx.deckPath,
|
|
2034
|
+
actionCallId,
|
|
2035
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2036
|
+
level: raw.level ?? "info",
|
|
2037
|
+
title: title ?? (message || undefined),
|
|
2038
|
+
message,
|
|
2039
|
+
body,
|
|
2040
|
+
meta: raw.meta,
|
|
2041
|
+
});
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (type === "spawn.request") {
|
|
2045
|
+
const req = msg;
|
|
2046
|
+
const requestId = req.requestId;
|
|
2047
|
+
if (!requestId)
|
|
2048
|
+
return;
|
|
2049
|
+
if (activeSpawnRequests.has(requestId)) {
|
|
2050
|
+
logger.warn(`[gambit] rejected duplicate compute-worker spawn.request (${requestId})`);
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
activeSpawnRequests.add(requestId);
|
|
2054
|
+
(async () => {
|
|
2055
|
+
try {
|
|
2056
|
+
const parentFromWorker = normalizePermissionBaseDir(fromWirePermissionSet(req.payload.parentPermissions), req.payload.parentPermissionsBaseDir);
|
|
2057
|
+
// Enforce monotonicity against the parent effective ceiling.
|
|
2058
|
+
const bridgedParent = (0, permissions_js_1.intersectPermissions)(ctx.permissions, parentFromWorker, req.payload.parentPermissionsBaseDir);
|
|
2059
|
+
const childResult = await runDeck({
|
|
2060
|
+
path: req.payload.path,
|
|
2061
|
+
input: req.payload.input,
|
|
2062
|
+
modelProvider: ctx.modelProvider,
|
|
2063
|
+
isRoot: false,
|
|
2064
|
+
guardrails: ctx.guardrails,
|
|
2065
|
+
depth: ctx.depth + 1,
|
|
2066
|
+
parentActionCallId: req.payload.parentActionCallId,
|
|
2067
|
+
runId,
|
|
2068
|
+
defaultModel: ctx.defaultModel,
|
|
2069
|
+
modelOverride: ctx.modelOverride,
|
|
2070
|
+
trace: ctx.trace,
|
|
2071
|
+
stream: ctx.stream,
|
|
2072
|
+
state: currentState,
|
|
2073
|
+
onStateUpdate: (state) => {
|
|
2074
|
+
currentState = state;
|
|
2075
|
+
ctx.onStateUpdate?.(state);
|
|
2076
|
+
},
|
|
2077
|
+
onStreamText: ctx.onStreamText,
|
|
2078
|
+
responsesMode: ctx.responsesMode,
|
|
2079
|
+
initialUserMessage: req.payload.initialUserMessage,
|
|
2080
|
+
inputProvided: true,
|
|
2081
|
+
parentPermissions: bridgedParent,
|
|
2082
|
+
workspacePermissions: req.payload.workspacePermissions,
|
|
2083
|
+
workspacePermissionsBaseDir: req.payload.workspacePermissionsBaseDir,
|
|
2084
|
+
sessionPermissions: req.payload.sessionPermissions,
|
|
2085
|
+
sessionPermissionsBaseDir: req.payload.sessionPermissionsBaseDir,
|
|
2086
|
+
runDeadlineMs: Math.min(ctx.runDeadlineMs, Number.isFinite(req.payload.runDeadlineMs)
|
|
2087
|
+
? req.payload.runDeadlineMs
|
|
2088
|
+
: ctx.runDeadlineMs),
|
|
2089
|
+
workerSandbox: true,
|
|
2090
|
+
signal: ctx.signal,
|
|
2091
|
+
onTool: ctx.onTool,
|
|
2092
|
+
});
|
|
2093
|
+
worker.postMessage({
|
|
2094
|
+
type: "spawn.result",
|
|
2095
|
+
requestId,
|
|
2096
|
+
result: childResult,
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
catch (err) {
|
|
2100
|
+
worker.postMessage({
|
|
2101
|
+
type: "spawn.error",
|
|
2102
|
+
requestId,
|
|
2103
|
+
error: {
|
|
2104
|
+
source: "child",
|
|
2105
|
+
name: err instanceof Error ? err.name : undefined,
|
|
2106
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2107
|
+
code: err?.code,
|
|
2108
|
+
},
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
finally {
|
|
2112
|
+
activeSpawnRequests.delete(requestId);
|
|
2113
|
+
}
|
|
2114
|
+
})();
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (type === "state.update") {
|
|
2118
|
+
const nextState = msg.state;
|
|
2119
|
+
if (!nextState || typeof nextState !== "object")
|
|
2120
|
+
return;
|
|
2121
|
+
currentState = nextState;
|
|
2122
|
+
ctx.onStateUpdate?.(nextState);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
if (type === "run.result") {
|
|
2126
|
+
if (msg.completionNonce !==
|
|
2127
|
+
completionNonce) {
|
|
2128
|
+
logger.warn(`[gambit] rejected compute-worker run.result with invalid completion nonce`);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
finishResolve(msg.result);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
if (type === "run.error") {
|
|
2135
|
+
if (msg.completionNonce !==
|
|
2136
|
+
completionNonce) {
|
|
2137
|
+
logger.warn(`[gambit] rejected compute-worker run.error with invalid completion nonce`);
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
finishReject(normalizeWorkerError(msg.error));
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
});
|
|
2144
|
+
try {
|
|
2145
|
+
worker.postMessage({
|
|
2146
|
+
type: "run.start",
|
|
2147
|
+
bridgeSession,
|
|
2148
|
+
completionNonce,
|
|
2149
|
+
runId,
|
|
2150
|
+
actionCallId,
|
|
2151
|
+
deckPath: ctx.deckPath,
|
|
2152
|
+
input: ctx.input,
|
|
2153
|
+
state: ctx.state,
|
|
2154
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
2155
|
+
depth: ctx.depth,
|
|
2156
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2157
|
+
permissions: toWirePermissionSet(ctx.permissions),
|
|
2158
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2159
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2160
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2161
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2162
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2163
|
+
isRoot: ctx.isRoot,
|
|
2164
|
+
allowRootStringInput: ctx.allowRootStringInput,
|
|
2165
|
+
});
|
|
2166
|
+
const raw = await outcome;
|
|
2167
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
2168
|
+
return raw;
|
|
234
2169
|
}
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
2170
|
+
finally {
|
|
2171
|
+
if (timeoutId !== undefined)
|
|
2172
|
+
clearTimeout(timeoutId);
|
|
2173
|
+
clearAndTerminate();
|
|
239
2174
|
}
|
|
240
|
-
throw new Error(`Deck ${deck.path} requires outputSchema (non-root)`);
|
|
241
2175
|
}
|
|
242
|
-
async function
|
|
2176
|
+
async function runComputeDeckInProcess(ctx) {
|
|
243
2177
|
const { deck, runId } = ctx;
|
|
244
2178
|
const actionCallId = randomId("action");
|
|
2179
|
+
let computeState = ctx.state
|
|
2180
|
+
? {
|
|
2181
|
+
...ctx.state,
|
|
2182
|
+
messages: Array.isArray(ctx.state.messages)
|
|
2183
|
+
? ctx.state.messages.map(sanitizeMessage)
|
|
2184
|
+
: [],
|
|
2185
|
+
meta: ctx.state.meta ? { ...ctx.state.meta } : undefined,
|
|
2186
|
+
messageRefs: Array.isArray(ctx.state.messageRefs)
|
|
2187
|
+
? [...ctx.state.messageRefs]
|
|
2188
|
+
: undefined,
|
|
2189
|
+
}
|
|
2190
|
+
: undefined;
|
|
2191
|
+
const ensureComputeState = () => {
|
|
2192
|
+
if (computeState)
|
|
2193
|
+
return computeState;
|
|
2194
|
+
computeState = {
|
|
2195
|
+
runId,
|
|
2196
|
+
messages: [],
|
|
2197
|
+
meta: {},
|
|
2198
|
+
messageRefs: [],
|
|
2199
|
+
};
|
|
2200
|
+
return computeState;
|
|
2201
|
+
};
|
|
2202
|
+
const publishComputeState = () => {
|
|
2203
|
+
if (!computeState)
|
|
2204
|
+
return;
|
|
2205
|
+
ctx.onStateUpdate?.({
|
|
2206
|
+
...computeState,
|
|
2207
|
+
messages: computeState.messages.map(sanitizeMessage),
|
|
2208
|
+
meta: computeState.meta ? { ...computeState.meta } : undefined,
|
|
2209
|
+
messageRefs: Array.isArray(computeState.messageRefs)
|
|
2210
|
+
? [...computeState.messageRefs]
|
|
2211
|
+
: undefined,
|
|
2212
|
+
});
|
|
2213
|
+
};
|
|
245
2214
|
const execContext = {
|
|
246
2215
|
runId,
|
|
247
2216
|
actionCallId,
|
|
248
2217
|
parentActionCallId: ctx.parentActionCallId,
|
|
249
2218
|
depth: ctx.depth,
|
|
250
2219
|
input: ctx.input,
|
|
2220
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
2221
|
+
getSessionMeta: (key) => {
|
|
2222
|
+
if (!key)
|
|
2223
|
+
return undefined;
|
|
2224
|
+
return computeState?.meta?.[key];
|
|
2225
|
+
},
|
|
2226
|
+
setSessionMeta: (key, value) => {
|
|
2227
|
+
if (!key)
|
|
2228
|
+
return;
|
|
2229
|
+
const state = ensureComputeState();
|
|
2230
|
+
const nextMeta = { ...(state.meta ?? {}) };
|
|
2231
|
+
if (value === undefined) {
|
|
2232
|
+
delete nextMeta[key];
|
|
2233
|
+
}
|
|
2234
|
+
else {
|
|
2235
|
+
nextMeta[key] = value;
|
|
2236
|
+
}
|
|
2237
|
+
state.meta = nextMeta;
|
|
2238
|
+
publishComputeState();
|
|
2239
|
+
},
|
|
2240
|
+
appendMessage: (message) => {
|
|
2241
|
+
const role = message.role;
|
|
2242
|
+
const content = String(message.content ?? "");
|
|
2243
|
+
if ((role !== "user" && role !== "assistant") || !content.trim()) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
const state = ensureComputeState();
|
|
2247
|
+
const sanitized = sanitizeMessage({ role, content: content.trim() });
|
|
2248
|
+
state.messages = [...(state.messages ?? []), sanitized];
|
|
2249
|
+
const refs = Array.isArray(state.messageRefs)
|
|
2250
|
+
? [...state.messageRefs]
|
|
2251
|
+
: [];
|
|
2252
|
+
refs.push({ id: randomId("msg"), role: sanitized.role });
|
|
2253
|
+
state.messageRefs = refs;
|
|
2254
|
+
publishComputeState();
|
|
2255
|
+
},
|
|
251
2256
|
label: deck.label,
|
|
252
2257
|
log: (entry) => {
|
|
253
2258
|
if (!ctx.trace)
|
|
@@ -278,9 +2283,13 @@ async function runComputeDeck(ctx) {
|
|
|
278
2283
|
});
|
|
279
2284
|
},
|
|
280
2285
|
spawnAndWait: async (opts) => {
|
|
2286
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
281
2287
|
const childPath = path.isAbsolute(opts.path)
|
|
282
2288
|
? opts.path
|
|
283
2289
|
: path.resolve(path.dirname(deck.path), opts.path);
|
|
2290
|
+
const childInitialUserMessage = Object.hasOwn(opts, "initialUserMessage")
|
|
2291
|
+
? opts.initialUserMessage
|
|
2292
|
+
: ctx.initialUserMessage;
|
|
284
2293
|
return await runDeck({
|
|
285
2294
|
path: childPath,
|
|
286
2295
|
input: opts.input,
|
|
@@ -294,11 +2303,33 @@ async function runComputeDeck(ctx) {
|
|
|
294
2303
|
modelOverride: ctx.modelOverride,
|
|
295
2304
|
trace: ctx.trace,
|
|
296
2305
|
stream: ctx.stream,
|
|
297
|
-
state:
|
|
298
|
-
onStateUpdate:
|
|
2306
|
+
state: computeState,
|
|
2307
|
+
onStateUpdate: (state) => {
|
|
2308
|
+
computeState = {
|
|
2309
|
+
...state,
|
|
2310
|
+
messages: Array.isArray(state.messages)
|
|
2311
|
+
? state.messages.map(sanitizeMessage)
|
|
2312
|
+
: [],
|
|
2313
|
+
meta: state.meta ? { ...state.meta } : undefined,
|
|
2314
|
+
messageRefs: Array.isArray(state.messageRefs)
|
|
2315
|
+
? [...state.messageRefs]
|
|
2316
|
+
: undefined,
|
|
2317
|
+
};
|
|
2318
|
+
ctx.onStateUpdate?.(state);
|
|
2319
|
+
},
|
|
299
2320
|
onStreamText: ctx.onStreamText,
|
|
300
|
-
|
|
2321
|
+
responsesMode: ctx.responsesMode,
|
|
2322
|
+
initialUserMessage: childInitialUserMessage,
|
|
301
2323
|
inputProvided: true,
|
|
2324
|
+
parentPermissions: ctx.permissions,
|
|
2325
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2326
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2327
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2328
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2329
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2330
|
+
workerSandbox: ctx.workerSandbox,
|
|
2331
|
+
signal: ctx.signal,
|
|
2332
|
+
onTool: ctx.onTool,
|
|
302
2333
|
});
|
|
303
2334
|
},
|
|
304
2335
|
fail: (opts) => {
|
|
@@ -306,7 +2337,9 @@ async function runComputeDeck(ctx) {
|
|
|
306
2337
|
},
|
|
307
2338
|
return: (payload) => Promise.resolve(payload),
|
|
308
2339
|
};
|
|
2340
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
309
2341
|
const raw = await deck.executor(execContext);
|
|
2342
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
310
2343
|
return validateOutput(deck, raw, ctx.depth === 0);
|
|
311
2344
|
}
|
|
312
2345
|
async function runLlmDeck(ctx) {
|
|
@@ -314,13 +2347,17 @@ async function runLlmDeck(ctx) {
|
|
|
314
2347
|
const actionCallId = randomId("action");
|
|
315
2348
|
const start = performance.now();
|
|
316
2349
|
const respondEnabled = Boolean(deck.respond);
|
|
2350
|
+
const useResponses = Boolean(ctx.responsesMode) ||
|
|
2351
|
+
ctx.state?.format === "responses";
|
|
317
2352
|
const systemPrompt = buildSystemPrompt(deck);
|
|
318
2353
|
const refToolCallId = randomId("call");
|
|
319
|
-
const messages = ctx.state?.messages
|
|
2354
|
+
const messages = ctx.state?.messages?.length
|
|
320
2355
|
? ctx.state.messages.map(sanitizeMessage)
|
|
321
|
-
:
|
|
2356
|
+
: ctx.state?.items?.length
|
|
2357
|
+
? messagesFromResponseItems(ctx.state.items).map(sanitizeMessage)
|
|
2358
|
+
: [];
|
|
322
2359
|
const resumed = messages.length > 0;
|
|
323
|
-
const
|
|
2360
|
+
const sendContext = Boolean(inputProvided) && input !== undefined && !resumed;
|
|
324
2361
|
const idleController = createIdleController({
|
|
325
2362
|
cfg: deck.handlers?.onIdle,
|
|
326
2363
|
deck,
|
|
@@ -335,11 +2372,21 @@ async function runLlmDeck(ctx) {
|
|
|
335
2372
|
stream: ctx.stream,
|
|
336
2373
|
onStreamText: ctx.onStreamText,
|
|
337
2374
|
pushMessages: (msgs) => messages.push(...msgs.map(sanitizeMessage)),
|
|
2375
|
+
responsesMode: ctx.responsesMode,
|
|
2376
|
+
permissions: ctx.permissions,
|
|
2377
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2378
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2379
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2380
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2381
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2382
|
+
workerSandbox: ctx.workerSandbox,
|
|
2383
|
+
signal: ctx.signal,
|
|
2384
|
+
onTool: ctx.onTool,
|
|
338
2385
|
});
|
|
339
2386
|
let streamingBuffer = "";
|
|
340
2387
|
let streamingCommitted = false;
|
|
341
2388
|
const wrappedOnStreamText = (chunk) => {
|
|
342
|
-
if (!chunk)
|
|
2389
|
+
if (!chunk || ctx.signal?.aborted)
|
|
343
2390
|
return;
|
|
344
2391
|
idleController.touch();
|
|
345
2392
|
streamingBuffer += chunk;
|
|
@@ -347,13 +2394,14 @@ async function runLlmDeck(ctx) {
|
|
|
347
2394
|
};
|
|
348
2395
|
if (!resumed) {
|
|
349
2396
|
messages.push(sanitizeMessage({ role: "system", content: systemPrompt }));
|
|
350
|
-
if (
|
|
2397
|
+
if (sendContext) {
|
|
351
2398
|
ctx.trace?.({
|
|
352
2399
|
type: "tool.call",
|
|
353
2400
|
runId,
|
|
354
2401
|
actionCallId: refToolCallId,
|
|
355
|
-
name: constants_js_1.
|
|
2402
|
+
name: constants_js_1.GAMBIT_TOOL_CONTEXT,
|
|
356
2403
|
args: {},
|
|
2404
|
+
toolKind: "internal",
|
|
357
2405
|
parentActionCallId: actionCallId,
|
|
358
2406
|
});
|
|
359
2407
|
messages.push(sanitizeMessage({
|
|
@@ -363,13 +2411,13 @@ async function runLlmDeck(ctx) {
|
|
|
363
2411
|
id: refToolCallId,
|
|
364
2412
|
type: "function",
|
|
365
2413
|
function: {
|
|
366
|
-
name: constants_js_1.
|
|
2414
|
+
name: constants_js_1.GAMBIT_TOOL_CONTEXT,
|
|
367
2415
|
arguments: "{}",
|
|
368
2416
|
},
|
|
369
2417
|
}],
|
|
370
2418
|
}), sanitizeMessage({
|
|
371
2419
|
role: "tool",
|
|
372
|
-
name: constants_js_1.
|
|
2420
|
+
name: constants_js_1.GAMBIT_TOOL_CONTEXT,
|
|
373
2421
|
tool_call_id: refToolCallId,
|
|
374
2422
|
content: JSON.stringify(input),
|
|
375
2423
|
}));
|
|
@@ -377,8 +2425,9 @@ async function runLlmDeck(ctx) {
|
|
|
377
2425
|
type: "tool.result",
|
|
378
2426
|
runId,
|
|
379
2427
|
actionCallId: refToolCallId,
|
|
380
|
-
name: constants_js_1.
|
|
2428
|
+
name: constants_js_1.GAMBIT_TOOL_CONTEXT,
|
|
381
2429
|
result: input,
|
|
2430
|
+
toolKind: "internal",
|
|
382
2431
|
parentActionCallId: actionCallId,
|
|
383
2432
|
});
|
|
384
2433
|
}
|
|
@@ -399,29 +2448,36 @@ async function runLlmDeck(ctx) {
|
|
|
399
2448
|
});
|
|
400
2449
|
}
|
|
401
2450
|
idleController.touch();
|
|
402
|
-
const tools = await buildToolDefs(deck);
|
|
2451
|
+
const tools = await buildToolDefs(deck, ctx.permissions);
|
|
403
2452
|
ctx.trace?.({
|
|
404
2453
|
type: "deck.start",
|
|
405
2454
|
runId,
|
|
406
2455
|
deckPath: deck.path,
|
|
407
2456
|
actionCallId,
|
|
408
2457
|
parentActionCallId: ctx.parentActionCallId,
|
|
2458
|
+
permissions: ctx.permissionsTrace,
|
|
409
2459
|
});
|
|
410
2460
|
let passes = 0;
|
|
411
2461
|
try {
|
|
412
2462
|
while (passes < guardrails.maxPasses) {
|
|
413
2463
|
passes++;
|
|
414
|
-
|
|
415
|
-
throw new Error("Timeout exceeded");
|
|
416
|
-
}
|
|
2464
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
417
2465
|
streamingBuffer = "";
|
|
418
2466
|
streamingCommitted = false;
|
|
419
|
-
const
|
|
2467
|
+
const modelCandidate = ctx.modelOverride ??
|
|
420
2468
|
deck.modelParams?.model ??
|
|
421
2469
|
ctx.defaultModel ??
|
|
422
2470
|
(() => {
|
|
423
2471
|
throw new Error(`No model configured for deck ${deck.path} and no --model provided`);
|
|
424
2472
|
})();
|
|
2473
|
+
const resolved = await resolveModelChoice({
|
|
2474
|
+
model: modelCandidate,
|
|
2475
|
+
params: toProviderParams(deck.modelParams),
|
|
2476
|
+
modelProvider,
|
|
2477
|
+
deckPath: deck.path,
|
|
2478
|
+
});
|
|
2479
|
+
const model = resolved.model;
|
|
2480
|
+
const providerParams = resolved.params;
|
|
425
2481
|
const stateMessages = ctx.state?.messages?.length;
|
|
426
2482
|
ctx.trace?.({
|
|
427
2483
|
type: "model.call",
|
|
@@ -435,21 +2491,134 @@ async function runLlmDeck(ctx) {
|
|
|
435
2491
|
messages: messages.map(sanitizeMessage),
|
|
436
2492
|
tools,
|
|
437
2493
|
stateMessages,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
model,
|
|
442
|
-
messages,
|
|
443
|
-
tools,
|
|
444
|
-
stream: ctx.stream,
|
|
445
|
-
state: ctx.state,
|
|
446
|
-
params: toProviderParams(deck.modelParams),
|
|
447
|
-
onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
|
|
448
|
-
? wrappedOnStreamText
|
|
2494
|
+
mode: useResponses ? "responses" : "chat",
|
|
2495
|
+
responseItems: useResponses
|
|
2496
|
+
? responseItemsFromMessages(messages)
|
|
449
2497
|
: undefined,
|
|
2498
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
450
2499
|
});
|
|
2500
|
+
let responseOutputItems;
|
|
2501
|
+
const responses = modelProvider.responses;
|
|
2502
|
+
const projectedToolCalls = new Set();
|
|
2503
|
+
const projectedToolResults = new Set();
|
|
2504
|
+
const projectedToolNames = new Map();
|
|
2505
|
+
const result = (useResponses && responses)
|
|
2506
|
+
? await (async () => {
|
|
2507
|
+
const responseItems = responseItemsFromMessages(messages);
|
|
2508
|
+
let sawDelta = false;
|
|
2509
|
+
const response = await responses({
|
|
2510
|
+
request: {
|
|
2511
|
+
model,
|
|
2512
|
+
input: responseItems,
|
|
2513
|
+
tools: tools,
|
|
2514
|
+
stream: ctx.stream,
|
|
2515
|
+
params: providerParams,
|
|
2516
|
+
},
|
|
2517
|
+
state: ctx.state,
|
|
2518
|
+
deckPath: deck.path,
|
|
2519
|
+
signal: ctx.signal,
|
|
2520
|
+
onStreamEvent: (ctx.trace || ctx.onStreamText || deck.handlers?.onIdle)
|
|
2521
|
+
? (event) => {
|
|
2522
|
+
if (ctx.trace) {
|
|
2523
|
+
const streamEvent = event;
|
|
2524
|
+
const handledAsResponse = traceOpenResponsesStreamEvent({
|
|
2525
|
+
streamEvent,
|
|
2526
|
+
runId,
|
|
2527
|
+
actionCallId,
|
|
2528
|
+
deckPath: deck.path,
|
|
2529
|
+
model,
|
|
2530
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2531
|
+
trace: ctx.trace,
|
|
2532
|
+
});
|
|
2533
|
+
if (!handledAsResponse) {
|
|
2534
|
+
ctx.trace({
|
|
2535
|
+
type: "model.stream.event",
|
|
2536
|
+
runId,
|
|
2537
|
+
actionCallId,
|
|
2538
|
+
deckPath: deck.path,
|
|
2539
|
+
model,
|
|
2540
|
+
event: streamEvent,
|
|
2541
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
projectStreamToolTraceEvents({
|
|
2545
|
+
streamEvent,
|
|
2546
|
+
runId,
|
|
2547
|
+
parentActionCallId: actionCallId,
|
|
2548
|
+
trace: ctx.trace,
|
|
2549
|
+
emittedCalls: projectedToolCalls,
|
|
2550
|
+
emittedResults: projectedToolResults,
|
|
2551
|
+
toolNames: projectedToolNames,
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
if (event.type === "response.output_text.delta") {
|
|
2555
|
+
sawDelta = true;
|
|
2556
|
+
wrappedOnStreamText(event.delta);
|
|
2557
|
+
}
|
|
2558
|
+
else if (event.type === "response.output_text.done" && !sawDelta) {
|
|
2559
|
+
wrappedOnStreamText(event.text);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
: undefined,
|
|
2563
|
+
});
|
|
2564
|
+
responseOutputItems = response.output ?? [];
|
|
2565
|
+
const mapped = mapResponseOutput(responseOutputItems);
|
|
2566
|
+
return {
|
|
2567
|
+
message: mapped.message,
|
|
2568
|
+
finishReason: mapped.toolCalls?.length ? "tool_calls" : "stop",
|
|
2569
|
+
toolCalls: mapped.toolCalls,
|
|
2570
|
+
usage: response.usage,
|
|
2571
|
+
updatedState: response.updatedState,
|
|
2572
|
+
};
|
|
2573
|
+
})()
|
|
2574
|
+
: await modelProvider.chat({
|
|
2575
|
+
model,
|
|
2576
|
+
messages,
|
|
2577
|
+
tools,
|
|
2578
|
+
stream: ctx.stream,
|
|
2579
|
+
state: ctx.state,
|
|
2580
|
+
deckPath: deck.path,
|
|
2581
|
+
signal: ctx.signal,
|
|
2582
|
+
params: providerParams,
|
|
2583
|
+
onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
|
|
2584
|
+
? wrappedOnStreamText
|
|
2585
|
+
: undefined,
|
|
2586
|
+
onStreamEvent: ctx.trace
|
|
2587
|
+
? (event) => {
|
|
2588
|
+
const handledAsResponse = traceOpenResponsesStreamEvent({
|
|
2589
|
+
streamEvent: event,
|
|
2590
|
+
runId,
|
|
2591
|
+
actionCallId,
|
|
2592
|
+
deckPath: deck.path,
|
|
2593
|
+
model,
|
|
2594
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2595
|
+
trace: ctx.trace,
|
|
2596
|
+
});
|
|
2597
|
+
if (!handledAsResponse) {
|
|
2598
|
+
ctx.trace?.({
|
|
2599
|
+
type: "model.stream.event",
|
|
2600
|
+
runId,
|
|
2601
|
+
actionCallId,
|
|
2602
|
+
deckPath: deck.path,
|
|
2603
|
+
model,
|
|
2604
|
+
event,
|
|
2605
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
projectStreamToolTraceEvents({
|
|
2609
|
+
streamEvent: event,
|
|
2610
|
+
runId,
|
|
2611
|
+
parentActionCallId: actionCallId,
|
|
2612
|
+
trace: ctx.trace,
|
|
2613
|
+
emittedCalls: projectedToolCalls,
|
|
2614
|
+
emittedResults: projectedToolResults,
|
|
2615
|
+
toolNames: projectedToolNames,
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
: undefined,
|
|
2619
|
+
});
|
|
451
2620
|
idleController.touch();
|
|
452
|
-
|
|
2621
|
+
let message = result.message;
|
|
453
2622
|
ctx.trace?.({
|
|
454
2623
|
type: "model.result",
|
|
455
2624
|
runId,
|
|
@@ -460,6 +2629,9 @@ async function runLlmDeck(ctx) {
|
|
|
460
2629
|
message: sanitizeMessage(message),
|
|
461
2630
|
toolCalls: result.toolCalls,
|
|
462
2631
|
stateMessages: result.updatedState?.messages?.length,
|
|
2632
|
+
usage: result.usage,
|
|
2633
|
+
mode: useResponses ? "responses" : "chat",
|
|
2634
|
+
responseItems: responseOutputItems,
|
|
463
2635
|
parentActionCallId: ctx.parentActionCallId,
|
|
464
2636
|
});
|
|
465
2637
|
const computeState = (updated) => {
|
|
@@ -468,17 +2640,31 @@ async function runLlmDeck(ctx) {
|
|
|
468
2640
|
const mergedMessages = base.messages && base.messages.length > 0
|
|
469
2641
|
? base.messages.map(sanitizeMessage)
|
|
470
2642
|
: messages.map(sanitizeMessage);
|
|
2643
|
+
const responseItems = useResponses
|
|
2644
|
+
? responseItemsFromMessages(mergedMessages)
|
|
2645
|
+
: updated?.items ?? ctx.state?.items;
|
|
471
2646
|
const priorRefs = updated?.messageRefs ?? ctx.state?.messageRefs ?? [];
|
|
472
2647
|
const messageRefs = mergedMessages.map((m, idx) => priorRefs[idx] ?? { id: randomId("msg"), role: m.role });
|
|
473
2648
|
const feedback = updated?.feedback ?? ctx.state?.feedback;
|
|
474
2649
|
const traces = updated?.traces ?? ctx.state?.traces;
|
|
2650
|
+
const meta = updated?.meta ?? ctx.state?.meta;
|
|
2651
|
+
const notes = updated?.notes ?? ctx.state?.notes;
|
|
2652
|
+
const conversationScore = updated?.conversationScore ??
|
|
2653
|
+
ctx.state?.conversationScore;
|
|
475
2654
|
return {
|
|
476
2655
|
...base,
|
|
477
2656
|
runId,
|
|
478
2657
|
messages: mergedMessages,
|
|
2658
|
+
format: useResponses
|
|
2659
|
+
? "responses"
|
|
2660
|
+
: updated?.format ?? ctx.state?.format,
|
|
2661
|
+
items: responseItems,
|
|
479
2662
|
messageRefs,
|
|
480
2663
|
feedback,
|
|
481
2664
|
traces,
|
|
2665
|
+
meta,
|
|
2666
|
+
notes,
|
|
2667
|
+
conversationScore,
|
|
482
2668
|
};
|
|
483
2669
|
};
|
|
484
2670
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
@@ -486,8 +2672,10 @@ async function runLlmDeck(ctx) {
|
|
|
486
2672
|
let respondValue;
|
|
487
2673
|
let endSignal;
|
|
488
2674
|
const appendedMessages = [];
|
|
489
|
-
|
|
490
|
-
|
|
2675
|
+
const toolCallText = streamingBuffer ||
|
|
2676
|
+
(typeof message.content === "string" ? message.content : "");
|
|
2677
|
+
if (!streamingCommitted && toolCallText) {
|
|
2678
|
+
messages.push(sanitizeMessage({ role: "assistant", content: toolCallText }));
|
|
491
2679
|
streamingCommitted = true;
|
|
492
2680
|
}
|
|
493
2681
|
for (const call of result.toolCalls) {
|
|
@@ -525,6 +2713,7 @@ async function runLlmDeck(ctx) {
|
|
|
525
2713
|
actionCallId: call.id,
|
|
526
2714
|
name: call.name,
|
|
527
2715
|
args: call.args,
|
|
2716
|
+
toolKind: "internal",
|
|
528
2717
|
parentActionCallId: actionCallId,
|
|
529
2718
|
});
|
|
530
2719
|
const toolContent = JSON.stringify(call.args ?? {});
|
|
@@ -554,6 +2743,7 @@ async function runLlmDeck(ctx) {
|
|
|
554
2743
|
actionCallId: call.id,
|
|
555
2744
|
name: call.name,
|
|
556
2745
|
result: respondEnvelope,
|
|
2746
|
+
toolKind: "internal",
|
|
557
2747
|
parentActionCallId: actionCallId,
|
|
558
2748
|
});
|
|
559
2749
|
continue;
|
|
@@ -580,6 +2770,7 @@ async function runLlmDeck(ctx) {
|
|
|
580
2770
|
actionCallId: call.id,
|
|
581
2771
|
name: call.name,
|
|
582
2772
|
args: call.args,
|
|
2773
|
+
toolKind: "internal",
|
|
583
2774
|
parentActionCallId: actionCallId,
|
|
584
2775
|
});
|
|
585
2776
|
const toolContent = JSON.stringify(call.args ?? {});
|
|
@@ -619,10 +2810,23 @@ async function runLlmDeck(ctx) {
|
|
|
619
2810
|
actionCallId: call.id,
|
|
620
2811
|
name: call.name,
|
|
621
2812
|
result: signal,
|
|
2813
|
+
toolKind: "internal",
|
|
622
2814
|
parentActionCallId: actionCallId,
|
|
623
2815
|
});
|
|
624
2816
|
continue;
|
|
625
2817
|
}
|
|
2818
|
+
const actionRef = deck.actionDecks.find((a) => a.name === call.name);
|
|
2819
|
+
const toolKind = actionRef ? "action" : "external";
|
|
2820
|
+
const actionPermissions = (0, permissions_js_1.resolveEffectivePermissions)({
|
|
2821
|
+
baseDir: path.dirname(deck.path),
|
|
2822
|
+
parent: ctx.permissions,
|
|
2823
|
+
reference: actionRef?.permissions
|
|
2824
|
+
? {
|
|
2825
|
+
baseDir: path.dirname(deck.path),
|
|
2826
|
+
permissions: actionRef.permissions,
|
|
2827
|
+
}
|
|
2828
|
+
: undefined,
|
|
2829
|
+
});
|
|
626
2830
|
ctx.trace?.({
|
|
627
2831
|
type: "action.start",
|
|
628
2832
|
runId,
|
|
@@ -630,6 +2834,7 @@ async function runLlmDeck(ctx) {
|
|
|
630
2834
|
name: call.name,
|
|
631
2835
|
path: call.name,
|
|
632
2836
|
parentActionCallId: actionCallId,
|
|
2837
|
+
permissions: actionPermissions.trace,
|
|
633
2838
|
});
|
|
634
2839
|
ctx.trace?.({
|
|
635
2840
|
type: "tool.call",
|
|
@@ -637,6 +2842,7 @@ async function runLlmDeck(ctx) {
|
|
|
637
2842
|
actionCallId: call.id,
|
|
638
2843
|
name: call.name,
|
|
639
2844
|
args: call.args,
|
|
2845
|
+
toolKind,
|
|
640
2846
|
parentActionCallId: actionCallId,
|
|
641
2847
|
});
|
|
642
2848
|
const toolResult = await handleToolCall(call, {
|
|
@@ -655,6 +2861,16 @@ async function runLlmDeck(ctx) {
|
|
|
655
2861
|
runStartedAt: start,
|
|
656
2862
|
inputProvided: true,
|
|
657
2863
|
idle: idleController,
|
|
2864
|
+
responsesMode: ctx.responsesMode,
|
|
2865
|
+
permissions: ctx.permissions,
|
|
2866
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2867
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2868
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2869
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2870
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2871
|
+
workerSandbox: ctx.workerSandbox,
|
|
2872
|
+
signal: ctx.signal,
|
|
2873
|
+
onTool: ctx.onTool,
|
|
658
2874
|
});
|
|
659
2875
|
ctx.trace?.({
|
|
660
2876
|
type: "tool.result",
|
|
@@ -662,6 +2878,7 @@ async function runLlmDeck(ctx) {
|
|
|
662
2878
|
actionCallId: call.id,
|
|
663
2879
|
name: call.name,
|
|
664
2880
|
result: toolResult.toolContent,
|
|
2881
|
+
toolKind,
|
|
665
2882
|
parentActionCallId: actionCallId,
|
|
666
2883
|
});
|
|
667
2884
|
appendedMessages.push({
|
|
@@ -699,6 +2916,7 @@ async function runLlmDeck(ctx) {
|
|
|
699
2916
|
idleController.touch();
|
|
700
2917
|
}
|
|
701
2918
|
if (ctx.onStateUpdate) {
|
|
2919
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
702
2920
|
const state = computeState(result.updatedState);
|
|
703
2921
|
ctx.onStateUpdate(state);
|
|
704
2922
|
}
|
|
@@ -724,6 +2942,12 @@ async function runLlmDeck(ctx) {
|
|
|
724
2942
|
}
|
|
725
2943
|
continue;
|
|
726
2944
|
}
|
|
2945
|
+
if (!respondEnabled &&
|
|
2946
|
+
result.finishReason === "stop" &&
|
|
2947
|
+
(message.content === null || message.content === undefined) &&
|
|
2948
|
+
(!result.toolCalls || result.toolCalls.length === 0)) {
|
|
2949
|
+
message = { ...message, content: "" };
|
|
2950
|
+
}
|
|
727
2951
|
if (result.finishReason === "tool_calls") {
|
|
728
2952
|
throw new Error("Model requested tool_calls but provided none");
|
|
729
2953
|
}
|
|
@@ -733,6 +2957,7 @@ async function runLlmDeck(ctx) {
|
|
|
733
2957
|
}
|
|
734
2958
|
if (message.content !== null && message.content !== undefined) {
|
|
735
2959
|
messages.push(sanitizeMessage(message));
|
|
2960
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
736
2961
|
if (ctx.onStateUpdate) {
|
|
737
2962
|
const state = computeState(result.updatedState);
|
|
738
2963
|
ctx.onStateUpdate(state);
|
|
@@ -775,23 +3000,11 @@ async function runLlmDeck(ctx) {
|
|
|
775
3000
|
throw new Error("Model did not complete within guardrails");
|
|
776
3001
|
}
|
|
777
3002
|
async function handleToolCall(call, ctx) {
|
|
778
|
-
|
|
3003
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
779
3004
|
const source = {
|
|
780
3005
|
deckPath: ctx.parentDeck.path,
|
|
781
|
-
actionName:
|
|
3006
|
+
actionName: call.name,
|
|
782
3007
|
};
|
|
783
|
-
if (!action) {
|
|
784
|
-
return {
|
|
785
|
-
toolContent: JSON.stringify({
|
|
786
|
-
runId: ctx.runId,
|
|
787
|
-
actionCallId: call.id,
|
|
788
|
-
parentActionCallId: ctx.parentActionCallId,
|
|
789
|
-
source,
|
|
790
|
-
status: 404,
|
|
791
|
-
message: "unknown action",
|
|
792
|
-
}),
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
3008
|
const baseComplete = (payload) => JSON.stringify({
|
|
796
3009
|
runId: ctx.runId,
|
|
797
3010
|
actionCallId: call.id,
|
|
@@ -805,6 +3018,480 @@ async function handleToolCall(call, ctx) {
|
|
|
805
3018
|
});
|
|
806
3019
|
const extraMessages = [];
|
|
807
3020
|
const started = performance.now();
|
|
3021
|
+
const runBuiltinTool = async () => {
|
|
3022
|
+
if (!isBuiltinTool(call.name))
|
|
3023
|
+
return null;
|
|
3024
|
+
const deny = (message) => ({
|
|
3025
|
+
toolContent: baseComplete({
|
|
3026
|
+
status: 403,
|
|
3027
|
+
code: "permission_denied",
|
|
3028
|
+
message,
|
|
3029
|
+
}),
|
|
3030
|
+
});
|
|
3031
|
+
if (call.name === BUILTIN_TOOL_READ_FILE) {
|
|
3032
|
+
let targetPath;
|
|
3033
|
+
try {
|
|
3034
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3035
|
+
}
|
|
3036
|
+
catch (err) {
|
|
3037
|
+
return {
|
|
3038
|
+
toolContent: baseComplete({
|
|
3039
|
+
status: 400,
|
|
3040
|
+
code: "invalid_input",
|
|
3041
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3042
|
+
}),
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
|
|
3046
|
+
return deny(`read_file denied for ${targetPath}`);
|
|
3047
|
+
}
|
|
3048
|
+
const text = await dntShim.Deno.readTextFile(targetPath);
|
|
3049
|
+
const lines = text.split(/\r?\n/);
|
|
3050
|
+
const { startLine, endLine } = parseLineRange(call.args);
|
|
3051
|
+
const sliced = lines.slice(startLine - 1, endLine).join("\n");
|
|
3052
|
+
return {
|
|
3053
|
+
toolContent: baseComplete({
|
|
3054
|
+
status: 200,
|
|
3055
|
+
payload: {
|
|
3056
|
+
path: targetPath,
|
|
3057
|
+
start_line: startLine,
|
|
3058
|
+
end_line: endLine,
|
|
3059
|
+
total_lines: lines.length,
|
|
3060
|
+
content: sliced,
|
|
3061
|
+
},
|
|
3062
|
+
}),
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
if (call.name === BUILTIN_TOOL_LIST_DIR) {
|
|
3066
|
+
let targetPath;
|
|
3067
|
+
try {
|
|
3068
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3069
|
+
}
|
|
3070
|
+
catch (err) {
|
|
3071
|
+
return {
|
|
3072
|
+
toolContent: baseComplete({
|
|
3073
|
+
status: 400,
|
|
3074
|
+
code: "invalid_input",
|
|
3075
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3076
|
+
}),
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
|
|
3080
|
+
return deny(`list_dir denied for ${targetPath}`);
|
|
3081
|
+
}
|
|
3082
|
+
const recursive = Boolean(call.args.recursive);
|
|
3083
|
+
const maxEntries = parseToolLimit(call.args.max_entries, 200, 2000);
|
|
3084
|
+
const out = [];
|
|
3085
|
+
const pending = [targetPath];
|
|
3086
|
+
while (pending.length > 0 && out.length < maxEntries) {
|
|
3087
|
+
const current = pending.pop();
|
|
3088
|
+
for await (const entry of dntShim.Deno.readDir(current)) {
|
|
3089
|
+
if (out.length >= maxEntries)
|
|
3090
|
+
break;
|
|
3091
|
+
const entryPath = path.join(current, entry.name);
|
|
3092
|
+
if (!(0, permissions_js_1.canReadPath)(ctx.permissions, entryPath))
|
|
3093
|
+
continue;
|
|
3094
|
+
const type = entry.isDirectory
|
|
3095
|
+
? "dir"
|
|
3096
|
+
: entry.isSymlink
|
|
3097
|
+
? "symlink"
|
|
3098
|
+
: "file";
|
|
3099
|
+
out.push({ path: entryPath, type });
|
|
3100
|
+
if (recursive && entry.isDirectory) {
|
|
3101
|
+
pending.push(entryPath);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
return {
|
|
3106
|
+
toolContent: baseComplete({
|
|
3107
|
+
status: 200,
|
|
3108
|
+
payload: {
|
|
3109
|
+
path: targetPath,
|
|
3110
|
+
recursive,
|
|
3111
|
+
entries: out,
|
|
3112
|
+
truncated: out.length >= maxEntries,
|
|
3113
|
+
},
|
|
3114
|
+
}),
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
if (call.name === BUILTIN_TOOL_GREP_FILES) {
|
|
3118
|
+
let targetPath;
|
|
3119
|
+
try {
|
|
3120
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3121
|
+
}
|
|
3122
|
+
catch (err) {
|
|
3123
|
+
return {
|
|
3124
|
+
toolContent: baseComplete({
|
|
3125
|
+
status: 400,
|
|
3126
|
+
code: "invalid_input",
|
|
3127
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3128
|
+
}),
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
|
|
3132
|
+
return deny(`grep_files denied for ${targetPath}`);
|
|
3133
|
+
}
|
|
3134
|
+
const query = typeof call.args.query === "string" ? call.args.query : "";
|
|
3135
|
+
if (!query) {
|
|
3136
|
+
return {
|
|
3137
|
+
toolContent: baseComplete({
|
|
3138
|
+
status: 400,
|
|
3139
|
+
code: "invalid_input",
|
|
3140
|
+
message: "query is required",
|
|
3141
|
+
}),
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
let re;
|
|
3145
|
+
try {
|
|
3146
|
+
re = new RegExp(query, "g");
|
|
3147
|
+
}
|
|
3148
|
+
catch (err) {
|
|
3149
|
+
return {
|
|
3150
|
+
toolContent: baseComplete({
|
|
3151
|
+
status: 400,
|
|
3152
|
+
code: "invalid_regex",
|
|
3153
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3154
|
+
}),
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
const maxMatches = parseToolLimit(call.args.max_matches, 200, 2000);
|
|
3158
|
+
const matches = [];
|
|
3159
|
+
const pending = [targetPath];
|
|
3160
|
+
while (pending.length > 0 && matches.length < maxMatches) {
|
|
3161
|
+
const current = pending.pop();
|
|
3162
|
+
const stat = await dntShim.Deno.stat(current);
|
|
3163
|
+
if (stat.isDirectory) {
|
|
3164
|
+
for await (const entry of dntShim.Deno.readDir(current)) {
|
|
3165
|
+
const entryPath = path.join(current, entry.name);
|
|
3166
|
+
if (!(0, permissions_js_1.canReadPath)(ctx.permissions, entryPath))
|
|
3167
|
+
continue;
|
|
3168
|
+
if (entry.isDirectory) {
|
|
3169
|
+
pending.push(entryPath);
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
if (!entry.isFile)
|
|
3173
|
+
continue;
|
|
3174
|
+
const text = await dntShim.Deno.readTextFile(entryPath).catch(() => null);
|
|
3175
|
+
if (text === null)
|
|
3176
|
+
continue;
|
|
3177
|
+
const lines = text.split(/\r?\n/);
|
|
3178
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3179
|
+
re.lastIndex = 0;
|
|
3180
|
+
if (!re.test(lines[i]))
|
|
3181
|
+
continue;
|
|
3182
|
+
matches.push({ path: entryPath, line: i + 1, text: lines[i] });
|
|
3183
|
+
if (matches.length >= maxMatches)
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3186
|
+
if (matches.length >= maxMatches)
|
|
3187
|
+
break;
|
|
3188
|
+
}
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
if (!stat.isFile)
|
|
3192
|
+
continue;
|
|
3193
|
+
const text = await dntShim.Deno.readTextFile(current).catch(() => null);
|
|
3194
|
+
if (text === null)
|
|
3195
|
+
continue;
|
|
3196
|
+
const lines = text.split(/\r?\n/);
|
|
3197
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3198
|
+
re.lastIndex = 0;
|
|
3199
|
+
if (!re.test(lines[i]))
|
|
3200
|
+
continue;
|
|
3201
|
+
matches.push({ path: current, line: i + 1, text: lines[i] });
|
|
3202
|
+
if (matches.length >= maxMatches)
|
|
3203
|
+
break;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
return {
|
|
3207
|
+
toolContent: baseComplete({
|
|
3208
|
+
status: 200,
|
|
3209
|
+
payload: {
|
|
3210
|
+
path: targetPath,
|
|
3211
|
+
query,
|
|
3212
|
+
matches,
|
|
3213
|
+
truncated: matches.length >= maxMatches,
|
|
3214
|
+
},
|
|
3215
|
+
}),
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
if (call.name === BUILTIN_TOOL_APPLY_PATCH) {
|
|
3219
|
+
let targetPath;
|
|
3220
|
+
try {
|
|
3221
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3222
|
+
}
|
|
3223
|
+
catch (err) {
|
|
3224
|
+
return {
|
|
3225
|
+
toolContent: baseComplete({
|
|
3226
|
+
status: 400,
|
|
3227
|
+
code: "invalid_input",
|
|
3228
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3229
|
+
}),
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
if (!(0, permissions_js_1.canWritePath)(ctx.permissions, targetPath)) {
|
|
3233
|
+
return deny(`apply_patch denied for ${targetPath}`);
|
|
3234
|
+
}
|
|
3235
|
+
const rawEdits = Array.isArray(call.args.edits) ? call.args.edits : [];
|
|
3236
|
+
const edits = rawEdits.flatMap((entry) => {
|
|
3237
|
+
if (!entry || typeof entry !== "object")
|
|
3238
|
+
return [];
|
|
3239
|
+
const rec = entry;
|
|
3240
|
+
if (typeof rec.old_text !== "string" || typeof rec.new_text !== "string") {
|
|
3241
|
+
return [];
|
|
3242
|
+
}
|
|
3243
|
+
return [{
|
|
3244
|
+
oldText: rec.old_text,
|
|
3245
|
+
newText: rec.new_text,
|
|
3246
|
+
replaceAll: Boolean(rec.replace_all),
|
|
3247
|
+
}];
|
|
3248
|
+
});
|
|
3249
|
+
if (edits.length === 0) {
|
|
3250
|
+
return {
|
|
3251
|
+
toolContent: baseComplete({
|
|
3252
|
+
status: 400,
|
|
3253
|
+
code: "invalid_input",
|
|
3254
|
+
message: "edits must include at least one old_text/new_text pair",
|
|
3255
|
+
}),
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
const createIfMissing = Boolean(call.args.create_if_missing);
|
|
3259
|
+
let existing = "";
|
|
3260
|
+
let created = false;
|
|
3261
|
+
try {
|
|
3262
|
+
if (!(0, permissions_js_1.canReadPath)(ctx.permissions, targetPath)) {
|
|
3263
|
+
return deny(`apply_patch read denied for ${targetPath}`);
|
|
3264
|
+
}
|
|
3265
|
+
existing = await dntShim.Deno.readTextFile(targetPath);
|
|
3266
|
+
}
|
|
3267
|
+
catch (err) {
|
|
3268
|
+
if (err instanceof dntShim.Deno.errors.NotFound) {
|
|
3269
|
+
if (!createIfMissing) {
|
|
3270
|
+
return {
|
|
3271
|
+
toolContent: baseComplete({
|
|
3272
|
+
status: 404,
|
|
3273
|
+
code: "not_found",
|
|
3274
|
+
message: `file not found: ${targetPath}`,
|
|
3275
|
+
}),
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
created = true;
|
|
3279
|
+
existing = "";
|
|
3280
|
+
}
|
|
3281
|
+
else {
|
|
3282
|
+
throw err;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
const patched = applySimplePatch(existing, edits);
|
|
3286
|
+
if (!created && patched.applied === 0) {
|
|
3287
|
+
return {
|
|
3288
|
+
toolContent: baseComplete({
|
|
3289
|
+
status: 409,
|
|
3290
|
+
code: "no_changes",
|
|
3291
|
+
message: `No edit targets were found in ${targetPath}`,
|
|
3292
|
+
}),
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
if (created) {
|
|
3296
|
+
const parentDir = path.dirname(targetPath);
|
|
3297
|
+
if (parentDir && parentDir !== "." && parentDir !== targetPath) {
|
|
3298
|
+
await dntShim.Deno.mkdir(parentDir, { recursive: true });
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
try {
|
|
3302
|
+
await dntShim.Deno.writeTextFile(targetPath, patched.next);
|
|
3303
|
+
}
|
|
3304
|
+
catch (err) {
|
|
3305
|
+
if (err instanceof dntShim.Deno.errors.NotFound) {
|
|
3306
|
+
return {
|
|
3307
|
+
toolContent: baseComplete({
|
|
3308
|
+
status: 404,
|
|
3309
|
+
code: "not_found",
|
|
3310
|
+
message: `path not found: ${targetPath}`,
|
|
3311
|
+
}),
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
return {
|
|
3315
|
+
toolContent: baseComplete({
|
|
3316
|
+
status: 500,
|
|
3317
|
+
code: "write_failed",
|
|
3318
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3319
|
+
}),
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
return {
|
|
3323
|
+
toolContent: baseComplete({
|
|
3324
|
+
status: 200,
|
|
3325
|
+
payload: {
|
|
3326
|
+
path: targetPath,
|
|
3327
|
+
applied: patched.applied,
|
|
3328
|
+
created,
|
|
3329
|
+
},
|
|
3330
|
+
}),
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
if (call.name === BUILTIN_TOOL_EXEC) {
|
|
3334
|
+
const command = typeof call.args.command === "string"
|
|
3335
|
+
? call.args.command
|
|
3336
|
+
: "";
|
|
3337
|
+
if (!command) {
|
|
3338
|
+
return {
|
|
3339
|
+
toolContent: baseComplete({
|
|
3340
|
+
status: 400,
|
|
3341
|
+
code: "invalid_input",
|
|
3342
|
+
message: "command is required",
|
|
3343
|
+
}),
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
if (!(0, permissions_js_1.canRunCommand)(ctx.permissions, command) &&
|
|
3347
|
+
!(0, permissions_js_1.canRunPath)(ctx.permissions, command)) {
|
|
3348
|
+
return deny(`exec denied for command ${command}`);
|
|
3349
|
+
}
|
|
3350
|
+
const args = toStringArray(call.args.args);
|
|
3351
|
+
const cwd = typeof call.args.cwd === "string"
|
|
3352
|
+
? path.resolve(ctx.permissions.baseDir, call.args.cwd)
|
|
3353
|
+
: ctx.permissions.baseDir;
|
|
3354
|
+
const timeoutMs = parseToolLimit(call.args.timeout_ms, 5000, 30000);
|
|
3355
|
+
const remainingMs = Math.max(1, Math.min(timeoutMs, Math.floor(ctx.runDeadlineMs - performance.now())));
|
|
3356
|
+
const controller = new AbortController();
|
|
3357
|
+
const onAbort = () => controller.abort();
|
|
3358
|
+
if (ctx.signal?.aborted) {
|
|
3359
|
+
controller.abort();
|
|
3360
|
+
}
|
|
3361
|
+
else if (ctx.signal) {
|
|
3362
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
3363
|
+
}
|
|
3364
|
+
const timeoutId = setTimeout(() => controller.abort(), remainingMs);
|
|
3365
|
+
try {
|
|
3366
|
+
const output = await (0, runtime_exec_host_js_1.executeBuiltinCommand)({
|
|
3367
|
+
command,
|
|
3368
|
+
args,
|
|
3369
|
+
cwd,
|
|
3370
|
+
signal: controller.signal,
|
|
3371
|
+
});
|
|
3372
|
+
const stdout = new TextDecoder().decode(output.stdout).slice(0, 65536);
|
|
3373
|
+
const stderr = new TextDecoder().decode(output.stderr).slice(0, 65536);
|
|
3374
|
+
return {
|
|
3375
|
+
toolContent: baseComplete({
|
|
3376
|
+
status: 200,
|
|
3377
|
+
payload: {
|
|
3378
|
+
command,
|
|
3379
|
+
args,
|
|
3380
|
+
cwd,
|
|
3381
|
+
code: output.code,
|
|
3382
|
+
success: output.success,
|
|
3383
|
+
stdout,
|
|
3384
|
+
stderr,
|
|
3385
|
+
},
|
|
3386
|
+
}),
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
catch (err) {
|
|
3390
|
+
if (err instanceof runtime_exec_host_js_1.ExecToolUnsupportedHostError) {
|
|
3391
|
+
return {
|
|
3392
|
+
toolContent: baseComplete({
|
|
3393
|
+
status: 501,
|
|
3394
|
+
code: err.code,
|
|
3395
|
+
message: err.message,
|
|
3396
|
+
}),
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
return {
|
|
3400
|
+
toolContent: baseComplete({
|
|
3401
|
+
status: 500,
|
|
3402
|
+
code: "exec_failed",
|
|
3403
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3404
|
+
}),
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
finally {
|
|
3408
|
+
clearTimeout(timeoutId);
|
|
3409
|
+
if (ctx.signal) {
|
|
3410
|
+
ctx.signal.removeEventListener("abort", onAbort);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
return null;
|
|
3415
|
+
};
|
|
3416
|
+
const builtinResult = await runBuiltinTool();
|
|
3417
|
+
if (builtinResult) {
|
|
3418
|
+
return builtinResult;
|
|
3419
|
+
}
|
|
3420
|
+
const action = ctx.parentDeck.actionDecks.find((a) => a.name === call.name);
|
|
3421
|
+
if (!action) {
|
|
3422
|
+
const externalTool = ctx.parentDeck.tools.find((tool) => tool.name === call.name);
|
|
3423
|
+
if (!externalTool) {
|
|
3424
|
+
return {
|
|
3425
|
+
toolContent: JSON.stringify({
|
|
3426
|
+
runId: ctx.runId,
|
|
3427
|
+
actionCallId: call.id,
|
|
3428
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3429
|
+
source,
|
|
3430
|
+
status: 404,
|
|
3431
|
+
message: "unknown action",
|
|
3432
|
+
}),
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
let externalInput = call.args;
|
|
3436
|
+
if (externalTool.inputSchema) {
|
|
3437
|
+
try {
|
|
3438
|
+
externalInput = (0, schema_js_1.validateWithSchema)(externalTool.inputSchema, call.args);
|
|
3439
|
+
}
|
|
3440
|
+
catch (err) {
|
|
3441
|
+
return {
|
|
3442
|
+
toolContent: baseComplete({
|
|
3443
|
+
status: 400,
|
|
3444
|
+
code: "invalid_input",
|
|
3445
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3446
|
+
}),
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (!ctx.onTool) {
|
|
3451
|
+
return {
|
|
3452
|
+
toolContent: baseComplete({
|
|
3453
|
+
status: 500,
|
|
3454
|
+
code: "missing_on_tool",
|
|
3455
|
+
message: `External tool ${call.name} requires runtime onTool handler`,
|
|
3456
|
+
}),
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
try {
|
|
3460
|
+
const result = await ctx.onTool({
|
|
3461
|
+
name: call.name,
|
|
3462
|
+
args: externalInput,
|
|
3463
|
+
runId: ctx.runId,
|
|
3464
|
+
actionCallId: call.id,
|
|
3465
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3466
|
+
deckPath: ctx.parentDeck.path,
|
|
3467
|
+
});
|
|
3468
|
+
return { toolContent: baseComplete(normalizeChildResult(result)) };
|
|
3469
|
+
}
|
|
3470
|
+
catch (err) {
|
|
3471
|
+
return {
|
|
3472
|
+
toolContent: baseComplete({
|
|
3473
|
+
status: 500,
|
|
3474
|
+
code: "tool_handler_error",
|
|
3475
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3476
|
+
}),
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
let actionInput = call.args;
|
|
3481
|
+
if (action.contextSchema) {
|
|
3482
|
+
try {
|
|
3483
|
+
actionInput = (0, schema_js_1.validateWithSchema)(action.contextSchema, call.args);
|
|
3484
|
+
}
|
|
3485
|
+
catch (err) {
|
|
3486
|
+
return {
|
|
3487
|
+
toolContent: baseComplete({
|
|
3488
|
+
status: 400,
|
|
3489
|
+
code: "invalid_input",
|
|
3490
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3491
|
+
}),
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
808
3495
|
const busyCfg = ctx.parentDeck.handlers?.onBusy ??
|
|
809
3496
|
ctx.parentDeck.handlers?.onInterval;
|
|
810
3497
|
const busyDelay = busyCfg?.delayMs ?? constants_js_1.DEFAULT_STATUS_DELAY_MS;
|
|
@@ -818,7 +3505,7 @@ async function handleToolCall(call, ctx) {
|
|
|
818
3505
|
try {
|
|
819
3506
|
const result = await runDeck({
|
|
820
3507
|
path: action.path,
|
|
821
|
-
input:
|
|
3508
|
+
input: actionInput,
|
|
822
3509
|
modelProvider: ctx.modelProvider,
|
|
823
3510
|
isRoot: false,
|
|
824
3511
|
guardrails: ctx.guardrails,
|
|
@@ -830,7 +3517,19 @@ async function handleToolCall(call, ctx) {
|
|
|
830
3517
|
trace: ctx.trace,
|
|
831
3518
|
stream: ctx.stream,
|
|
832
3519
|
onStreamText: ctx.onStreamText,
|
|
3520
|
+
responsesMode: ctx.responsesMode,
|
|
833
3521
|
initialUserMessage: undefined,
|
|
3522
|
+
parentPermissions: ctx.permissions,
|
|
3523
|
+
referencePermissions: action.permissions,
|
|
3524
|
+
referencePermissionsBaseDir: path.dirname(ctx.parentDeck.path),
|
|
3525
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
3526
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
3527
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
3528
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
3529
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
3530
|
+
workerSandbox: ctx.workerSandbox,
|
|
3531
|
+
signal: ctx.signal,
|
|
3532
|
+
onTool: ctx.onTool,
|
|
834
3533
|
});
|
|
835
3534
|
return { ok: true, result };
|
|
836
3535
|
}
|
|
@@ -862,7 +3561,17 @@ async function handleToolCall(call, ctx) {
|
|
|
862
3561
|
trace: ctx.trace,
|
|
863
3562
|
stream: ctx.stream,
|
|
864
3563
|
onStreamText: ctx.onStreamText,
|
|
3564
|
+
responsesMode: ctx.responsesMode,
|
|
865
3565
|
initialUserMessage: undefined,
|
|
3566
|
+
permissions: ctx.permissions,
|
|
3567
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
3568
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
3569
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
3570
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
3571
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
3572
|
+
workerSandbox: ctx.workerSandbox,
|
|
3573
|
+
signal: ctx.signal,
|
|
3574
|
+
onTool: ctx.onTool,
|
|
866
3575
|
});
|
|
867
3576
|
if (envelope.length) {
|
|
868
3577
|
extraMessages.push(...envelope.map(sanitizeMessage));
|
|
@@ -920,6 +3629,9 @@ async function handleToolCall(call, ctx) {
|
|
|
920
3629
|
throw childResult.error;
|
|
921
3630
|
}
|
|
922
3631
|
const normalized = normalizeChildResult(childResult.result);
|
|
3632
|
+
if (action.responseSchema) {
|
|
3633
|
+
normalized.payload = (0, schema_js_1.validateWithSchema)(action.responseSchema, normalized.payload);
|
|
3634
|
+
}
|
|
923
3635
|
const toolContent = baseComplete(normalized);
|
|
924
3636
|
if (busyCfg?.path) {
|
|
925
3637
|
const elapsedFromAction = performance.now() - started;
|
|
@@ -941,7 +3653,17 @@ async function handleToolCall(call, ctx) {
|
|
|
941
3653
|
trace: ctx.trace,
|
|
942
3654
|
stream: ctx.stream,
|
|
943
3655
|
onStreamText: ctx.onStreamText,
|
|
3656
|
+
responsesMode: ctx.responsesMode,
|
|
944
3657
|
initialUserMessage: undefined,
|
|
3658
|
+
permissions: ctx.permissions,
|
|
3659
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
3660
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
3661
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
3662
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
3663
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
3664
|
+
workerSandbox: ctx.workerSandbox,
|
|
3665
|
+
signal: ctx.signal,
|
|
3666
|
+
onTool: ctx.onTool,
|
|
945
3667
|
});
|
|
946
3668
|
if (envelope.length) {
|
|
947
3669
|
extraMessages.push(...envelope.map(sanitizeMessage));
|
|
@@ -991,6 +3713,7 @@ function normalizeChildResult(result) {
|
|
|
991
3713
|
}
|
|
992
3714
|
async function runBusyHandler(args) {
|
|
993
3715
|
try {
|
|
3716
|
+
ensureRunActive(args.runDeadlineMs, args.signal);
|
|
994
3717
|
const input = {
|
|
995
3718
|
kind: "busy",
|
|
996
3719
|
label: args.action.label ?? args.parentDeck.label,
|
|
@@ -1015,8 +3738,18 @@ async function runBusyHandler(args) {
|
|
|
1015
3738
|
trace: args.trace,
|
|
1016
3739
|
stream: args.stream,
|
|
1017
3740
|
onStreamText: args.onStreamText,
|
|
3741
|
+
responsesMode: args.responsesMode,
|
|
1018
3742
|
initialUserMessage: args.initialUserMessage,
|
|
1019
3743
|
inputProvided: true,
|
|
3744
|
+
parentPermissions: args.permissions,
|
|
3745
|
+
workspacePermissions: args.workspacePermissions,
|
|
3746
|
+
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
3747
|
+
sessionPermissions: args.sessionPermissions,
|
|
3748
|
+
sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
|
|
3749
|
+
runDeadlineMs: args.runDeadlineMs,
|
|
3750
|
+
workerSandbox: args.workerSandbox,
|
|
3751
|
+
signal: args.signal,
|
|
3752
|
+
onTool: args.onTool,
|
|
1020
3753
|
});
|
|
1021
3754
|
const elapsedMs = Math.floor(args.elapsedMs);
|
|
1022
3755
|
let message;
|
|
@@ -1033,7 +3766,7 @@ async function runBusyHandler(args) {
|
|
|
1033
3766
|
}
|
|
1034
3767
|
if (!message)
|
|
1035
3768
|
return [];
|
|
1036
|
-
if (args.onStreamText) {
|
|
3769
|
+
if (args.onStreamText && !args.signal?.aborted) {
|
|
1037
3770
|
args.onStreamText(`${message}\n`);
|
|
1038
3771
|
}
|
|
1039
3772
|
else {
|
|
@@ -1093,6 +3826,16 @@ function createIdleController(args) {
|
|
|
1093
3826
|
trace: args.trace,
|
|
1094
3827
|
stream: args.stream,
|
|
1095
3828
|
onStreamText: args.onStreamText,
|
|
3829
|
+
responsesMode: args.responsesMode,
|
|
3830
|
+
permissions: args.permissions,
|
|
3831
|
+
workspacePermissions: args.workspacePermissions,
|
|
3832
|
+
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
3833
|
+
sessionPermissions: args.sessionPermissions,
|
|
3834
|
+
sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
|
|
3835
|
+
runDeadlineMs: args.runDeadlineMs,
|
|
3836
|
+
workerSandbox: args.workerSandbox,
|
|
3837
|
+
signal: args.signal,
|
|
3838
|
+
onTool: args.onTool,
|
|
1096
3839
|
});
|
|
1097
3840
|
if (envelope.length)
|
|
1098
3841
|
args.pushMessages(envelope.map(sanitizeMessage));
|
|
@@ -1132,6 +3875,7 @@ function createIdleController(args) {
|
|
|
1132
3875
|
}
|
|
1133
3876
|
async function runIdleHandler(args) {
|
|
1134
3877
|
try {
|
|
3878
|
+
ensureRunActive(args.runDeadlineMs, args.signal);
|
|
1135
3879
|
const input = {
|
|
1136
3880
|
kind: "idle",
|
|
1137
3881
|
label: args.deck.label,
|
|
@@ -1155,8 +3899,18 @@ async function runIdleHandler(args) {
|
|
|
1155
3899
|
trace: args.trace,
|
|
1156
3900
|
stream: args.stream,
|
|
1157
3901
|
onStreamText: args.onStreamText,
|
|
3902
|
+
responsesMode: args.responsesMode,
|
|
1158
3903
|
initialUserMessage: undefined,
|
|
1159
3904
|
inputProvided: true,
|
|
3905
|
+
parentPermissions: args.permissions,
|
|
3906
|
+
workspacePermissions: args.workspacePermissions,
|
|
3907
|
+
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
3908
|
+
sessionPermissions: args.sessionPermissions,
|
|
3909
|
+
sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
|
|
3910
|
+
runDeadlineMs: args.runDeadlineMs,
|
|
3911
|
+
workerSandbox: args.workerSandbox,
|
|
3912
|
+
signal: args.signal,
|
|
3913
|
+
onTool: args.onTool,
|
|
1160
3914
|
});
|
|
1161
3915
|
const elapsedMs = Math.floor(args.elapsedMs);
|
|
1162
3916
|
let message;
|
|
@@ -1173,7 +3927,7 @@ async function runIdleHandler(args) {
|
|
|
1173
3927
|
}
|
|
1174
3928
|
if (!message)
|
|
1175
3929
|
return [];
|
|
1176
|
-
if (args.onStreamText) {
|
|
3930
|
+
if (args.onStreamText && !args.signal?.aborted) {
|
|
1177
3931
|
args.onStreamText(`${message}\n`);
|
|
1178
3932
|
}
|
|
1179
3933
|
else {
|
|
@@ -1192,6 +3946,7 @@ async function maybeHandleError(args) {
|
|
|
1192
3946
|
const handlerPath = args.ctx.parentDeck.handlers?.onError?.path;
|
|
1193
3947
|
if (!handlerPath)
|
|
1194
3948
|
return undefined;
|
|
3949
|
+
ensureRunActive(args.ctx.runDeadlineMs, args.ctx.signal);
|
|
1195
3950
|
const message = args.err instanceof Error
|
|
1196
3951
|
? args.err.message
|
|
1197
3952
|
: String(args.err);
|
|
@@ -1220,8 +3975,18 @@ async function maybeHandleError(args) {
|
|
|
1220
3975
|
trace: args.ctx.trace,
|
|
1221
3976
|
stream: args.ctx.stream,
|
|
1222
3977
|
onStreamText: args.ctx.onStreamText,
|
|
3978
|
+
responsesMode: args.ctx.responsesMode,
|
|
1223
3979
|
initialUserMessage: undefined,
|
|
1224
3980
|
inputProvided: true,
|
|
3981
|
+
parentPermissions: args.ctx.permissions,
|
|
3982
|
+
workspacePermissions: args.ctx.workspacePermissions,
|
|
3983
|
+
workspacePermissionsBaseDir: args.ctx.workspacePermissionsBaseDir,
|
|
3984
|
+
sessionPermissions: args.ctx.sessionPermissions,
|
|
3985
|
+
sessionPermissionsBaseDir: args.ctx.sessionPermissionsBaseDir,
|
|
3986
|
+
runDeadlineMs: args.ctx.runDeadlineMs,
|
|
3987
|
+
workerSandbox: args.ctx.workerSandbox,
|
|
3988
|
+
signal: args.ctx.signal,
|
|
3989
|
+
onTool: args.ctx.onTool,
|
|
1225
3990
|
});
|
|
1226
3991
|
const parsed = typeof handlerOutput === "object" && handlerOutput !== null
|
|
1227
3992
|
? handlerOutput
|
|
@@ -1345,8 +4110,173 @@ function sanitizeMessage(msg) {
|
|
|
1345
4110
|
: undefined;
|
|
1346
4111
|
return { ...msg, tool_calls: toolCalls };
|
|
1347
4112
|
}
|
|
1348
|
-
|
|
4113
|
+
function resolveToolPath(baseDir, rawPath) {
|
|
4114
|
+
if (typeof rawPath !== "string" || rawPath.trim().length === 0) {
|
|
4115
|
+
throw new Error("path is required");
|
|
4116
|
+
}
|
|
4117
|
+
return path.resolve(baseDir, rawPath);
|
|
4118
|
+
}
|
|
4119
|
+
function parseLineRange(args) {
|
|
4120
|
+
const startLine = Number.isInteger(args.start_line)
|
|
4121
|
+
? Math.max(1, Number(args.start_line))
|
|
4122
|
+
: 1;
|
|
4123
|
+
const endLine = Number.isInteger(args.end_line)
|
|
4124
|
+
? Math.max(startLine, Number(args.end_line))
|
|
4125
|
+
: startLine + 399;
|
|
4126
|
+
return { startLine, endLine };
|
|
4127
|
+
}
|
|
4128
|
+
function parseToolLimit(value, fallback, max) {
|
|
4129
|
+
if (!Number.isInteger(value))
|
|
4130
|
+
return fallback;
|
|
4131
|
+
return Math.min(max, Math.max(1, Number(value)));
|
|
4132
|
+
}
|
|
4133
|
+
function toStringArray(value) {
|
|
4134
|
+
if (!Array.isArray(value))
|
|
4135
|
+
return [];
|
|
4136
|
+
return value.filter((entry) => typeof entry === "string");
|
|
4137
|
+
}
|
|
4138
|
+
function hasAnyScope(scope) {
|
|
4139
|
+
return scope.all || scope.values.size > 0;
|
|
4140
|
+
}
|
|
4141
|
+
function hasAnyRunScope(scope) {
|
|
4142
|
+
return scope.all || scope.paths.size > 0 || scope.commands.size > 0;
|
|
4143
|
+
}
|
|
4144
|
+
function isBuiltinTool(name) {
|
|
4145
|
+
return BUILTIN_TOOL_NAMES.has(name);
|
|
4146
|
+
}
|
|
4147
|
+
function applySimplePatch(content, edits) {
|
|
4148
|
+
let next = content;
|
|
4149
|
+
let applied = 0;
|
|
4150
|
+
for (const edit of edits) {
|
|
4151
|
+
const oldText = edit.oldText ?? "";
|
|
4152
|
+
const newText = edit.newText ?? "";
|
|
4153
|
+
if (!oldText)
|
|
4154
|
+
continue;
|
|
4155
|
+
if (edit.replaceAll) {
|
|
4156
|
+
if (!next.includes(oldText))
|
|
4157
|
+
continue;
|
|
4158
|
+
next = next.split(oldText).join(newText);
|
|
4159
|
+
applied++;
|
|
4160
|
+
continue;
|
|
4161
|
+
}
|
|
4162
|
+
const idx = next.indexOf(oldText);
|
|
4163
|
+
if (idx === -1)
|
|
4164
|
+
continue;
|
|
4165
|
+
next = `${next.slice(0, idx)}${newText}${next.slice(idx + oldText.length)}`;
|
|
4166
|
+
applied++;
|
|
4167
|
+
}
|
|
4168
|
+
return { next, applied };
|
|
4169
|
+
}
|
|
4170
|
+
async function buildToolDefs(deck, permissions) {
|
|
1349
4171
|
const defs = [];
|
|
4172
|
+
const addBuiltinTools = () => {
|
|
4173
|
+
if (hasAnyScope(permissions.read)) {
|
|
4174
|
+
defs.push({
|
|
4175
|
+
type: "function",
|
|
4176
|
+
function: {
|
|
4177
|
+
name: BUILTIN_TOOL_READ_FILE,
|
|
4178
|
+
description: "Read a UTF-8 text file.",
|
|
4179
|
+
parameters: {
|
|
4180
|
+
type: "object",
|
|
4181
|
+
properties: {
|
|
4182
|
+
path: { type: "string" },
|
|
4183
|
+
start_line: { type: "number" },
|
|
4184
|
+
end_line: { type: "number" },
|
|
4185
|
+
},
|
|
4186
|
+
required: ["path"],
|
|
4187
|
+
additionalProperties: false,
|
|
4188
|
+
},
|
|
4189
|
+
},
|
|
4190
|
+
}, {
|
|
4191
|
+
type: "function",
|
|
4192
|
+
function: {
|
|
4193
|
+
name: BUILTIN_TOOL_LIST_DIR,
|
|
4194
|
+
description: "List directory entries.",
|
|
4195
|
+
parameters: {
|
|
4196
|
+
type: "object",
|
|
4197
|
+
properties: {
|
|
4198
|
+
path: { type: "string" },
|
|
4199
|
+
recursive: { type: "boolean" },
|
|
4200
|
+
max_entries: { type: "number" },
|
|
4201
|
+
},
|
|
4202
|
+
required: ["path"],
|
|
4203
|
+
additionalProperties: false,
|
|
4204
|
+
},
|
|
4205
|
+
},
|
|
4206
|
+
}, {
|
|
4207
|
+
type: "function",
|
|
4208
|
+
function: {
|
|
4209
|
+
name: BUILTIN_TOOL_GREP_FILES,
|
|
4210
|
+
description: "Search text files using a regular expression.",
|
|
4211
|
+
parameters: {
|
|
4212
|
+
type: "object",
|
|
4213
|
+
properties: {
|
|
4214
|
+
path: { type: "string" },
|
|
4215
|
+
query: { type: "string" },
|
|
4216
|
+
max_matches: { type: "number" },
|
|
4217
|
+
},
|
|
4218
|
+
required: ["path", "query"],
|
|
4219
|
+
additionalProperties: false,
|
|
4220
|
+
},
|
|
4221
|
+
},
|
|
4222
|
+
});
|
|
4223
|
+
}
|
|
4224
|
+
if (hasAnyScope(permissions.write)) {
|
|
4225
|
+
defs.push({
|
|
4226
|
+
type: "function",
|
|
4227
|
+
function: {
|
|
4228
|
+
name: BUILTIN_TOOL_APPLY_PATCH,
|
|
4229
|
+
description: "Apply text replacements to a file using old/new edit pairs.",
|
|
4230
|
+
parameters: {
|
|
4231
|
+
type: "object",
|
|
4232
|
+
properties: {
|
|
4233
|
+
path: { type: "string" },
|
|
4234
|
+
create_if_missing: { type: "boolean" },
|
|
4235
|
+
edits: {
|
|
4236
|
+
type: "array",
|
|
4237
|
+
items: {
|
|
4238
|
+
type: "object",
|
|
4239
|
+
properties: {
|
|
4240
|
+
old_text: { type: "string" },
|
|
4241
|
+
new_text: { type: "string" },
|
|
4242
|
+
replace_all: { type: "boolean" },
|
|
4243
|
+
},
|
|
4244
|
+
required: ["old_text", "new_text"],
|
|
4245
|
+
additionalProperties: false,
|
|
4246
|
+
},
|
|
4247
|
+
},
|
|
4248
|
+
},
|
|
4249
|
+
required: ["path", "edits"],
|
|
4250
|
+
additionalProperties: false,
|
|
4251
|
+
},
|
|
4252
|
+
},
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
if (hasAnyRunScope(permissions.run)) {
|
|
4256
|
+
defs.push({
|
|
4257
|
+
type: "function",
|
|
4258
|
+
function: {
|
|
4259
|
+
name: BUILTIN_TOOL_EXEC,
|
|
4260
|
+
description: "Run an allowed command with optional args.",
|
|
4261
|
+
parameters: {
|
|
4262
|
+
type: "object",
|
|
4263
|
+
properties: {
|
|
4264
|
+
command: { type: "string" },
|
|
4265
|
+
args: {
|
|
4266
|
+
type: "array",
|
|
4267
|
+
items: { type: "string" },
|
|
4268
|
+
},
|
|
4269
|
+
cwd: { type: "string" },
|
|
4270
|
+
timeout_ms: { type: "number" },
|
|
4271
|
+
},
|
|
4272
|
+
required: ["command"],
|
|
4273
|
+
additionalProperties: false,
|
|
4274
|
+
},
|
|
4275
|
+
},
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
addBuiltinTools();
|
|
1350
4280
|
if (deck.allowEnd) {
|
|
1351
4281
|
defs.push({
|
|
1352
4282
|
type: "function",
|
|
@@ -1388,9 +4318,15 @@ async function buildToolDefs(deck) {
|
|
|
1388
4318
|
});
|
|
1389
4319
|
}
|
|
1390
4320
|
for (const action of deck.actionDecks) {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
4321
|
+
if (isBuiltinTool(action.name)) {
|
|
4322
|
+
throw new Error(`Action name ${action.name} conflicts with a built-in tool name`);
|
|
4323
|
+
}
|
|
4324
|
+
let schema = action.contextSchema;
|
|
4325
|
+
if (!schema) {
|
|
4326
|
+
const child = await (0, loader_js_1.loadDeck)(action.path, deck.path);
|
|
4327
|
+
ensureSchemaPresence(child, false);
|
|
4328
|
+
schema = resolveContextSchema(child);
|
|
4329
|
+
}
|
|
1394
4330
|
const params = (0, schema_js_1.toJsonSchema)(schema);
|
|
1395
4331
|
defs.push({
|
|
1396
4332
|
type: "function",
|
|
@@ -1401,5 +4337,23 @@ async function buildToolDefs(deck) {
|
|
|
1401
4337
|
},
|
|
1402
4338
|
});
|
|
1403
4339
|
}
|
|
4340
|
+
const actionNames = new Set(deck.actionDecks.map((action) => action.name));
|
|
4341
|
+
for (const external of deck.tools) {
|
|
4342
|
+
if (actionNames.has(external.name))
|
|
4343
|
+
continue;
|
|
4344
|
+
if (isBuiltinTool(external.name)) {
|
|
4345
|
+
throw new Error(`External tool name ${external.name} conflicts with a built-in tool name`);
|
|
4346
|
+
}
|
|
4347
|
+
defs.push({
|
|
4348
|
+
type: "function",
|
|
4349
|
+
function: {
|
|
4350
|
+
name: external.name,
|
|
4351
|
+
description: external.description,
|
|
4352
|
+
parameters: external.inputSchema
|
|
4353
|
+
? (0, schema_js_1.toJsonSchema)(external.inputSchema)
|
|
4354
|
+
: { type: "object", additionalProperties: true },
|
|
4355
|
+
},
|
|
4356
|
+
});
|
|
4357
|
+
}
|
|
1404
4358
|
return defs;
|
|
1405
4359
|
}
|