@celestea/tools 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/args.d.ts +15 -0
- package/dist/args.js +55 -0
- package/dist/attachments/image-header.d.ts +28 -0
- package/dist/attachments/image-header.js +148 -0
- package/dist/attachments/store.d.ts +56 -0
- package/dist/attachments/store.js +148 -0
- package/dist/browser/cdp.d.ts +162 -0
- package/dist/browser/cdp.js +269 -0
- package/dist/browser/launch.d.ts +92 -0
- package/dist/browser/launch.js +232 -0
- package/dist/browser/memory-guard.d.ts +55 -0
- package/dist/browser/memory-guard.js +210 -0
- package/dist/browser/session.d.ts +146 -0
- package/dist/browser/session.js +371 -0
- package/dist/browser/snapshot.d.ts +69 -0
- package/dist/browser/snapshot.js +163 -0
- package/dist/browser/types.d.ts +51 -0
- package/dist/browser/types.js +8 -0
- package/dist/builtin.d.ts +54 -0
- package/dist/builtin.js +77 -0
- package/dist/desc.d.ts +20 -0
- package/dist/desc.js +22 -0
- package/dist/disclosure.d.ts +113 -0
- package/dist/disclosure.js +141 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +33 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +60 -0
- package/dist/exposure.d.ts +145 -0
- package/dist/exposure.js +244 -0
- package/dist/fn-tool.d.ts +14 -0
- package/dist/fn-tool.js +14 -0
- package/dist/fs/file-io.d.ts +78 -0
- package/dist/fs/file-io.js +239 -0
- package/dist/guard/path-guard.d.ts +144 -0
- package/dist/guard/path-guard.js +289 -0
- package/dist/guard/paths.d.ts +35 -0
- package/dist/guard/paths.js +100 -0
- package/dist/http/errors.d.ts +24 -0
- package/dist/http/errors.js +64 -0
- package/dist/http/headers.d.ts +19 -0
- package/dist/http/headers.js +62 -0
- package/dist/http/redirects.d.ts +31 -0
- package/dist/http/redirects.js +76 -0
- package/dist/http/ssrf.d.ts +105 -0
- package/dist/http/ssrf.js +272 -0
- package/dist/http/transport.d.ts +50 -0
- package/dist/http/transport.js +130 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +129 -0
- package/dist/memory/log.d.ts +75 -0
- package/dist/memory/log.js +157 -0
- package/dist/memory/store.d.ts +47 -0
- package/dist/memory/store.js +61 -0
- package/dist/platform/exec.d.ts +79 -0
- package/dist/platform/exec.js +218 -0
- package/dist/platform/index.d.ts +12 -0
- package/dist/platform/index.js +12 -0
- package/dist/platform/paths.d.ts +51 -0
- package/dist/platform/paths.js +60 -0
- package/dist/platform/quote.d.ts +72 -0
- package/dist/platform/quote.js +102 -0
- package/dist/plugin.d.ts +96 -0
- package/dist/plugin.js +101 -0
- package/dist/process/buffers.d.ts +33 -0
- package/dist/process/buffers.js +86 -0
- package/dist/process/registry.d.ts +98 -0
- package/dist/process/registry.js +282 -0
- package/dist/registry.d.ts +52 -0
- package/dist/registry.js +161 -0
- package/dist/run-code/broker.d.ts +68 -0
- package/dist/run-code/broker.js +465 -0
- package/dist/run-code/limits.d.ts +69 -0
- package/dist/run-code/limits.js +88 -0
- package/dist/run-code/lines.d.ts +69 -0
- package/dist/run-code/lines.js +199 -0
- package/dist/run-code/sdk-ts.d.ts +34 -0
- package/dist/run-code/sdk-ts.js +276 -0
- package/dist/run-code/sdk.d.ts +39 -0
- package/dist/run-code/sdk.js +294 -0
- package/dist/sandbox/async.d.ts +10 -0
- package/dist/sandbox/async.js +26 -0
- package/dist/sandbox/bwrap-argv.d.ts +62 -0
- package/dist/sandbox/bwrap-argv.js +113 -0
- package/dist/sandbox/bwrap.d.ts +94 -0
- package/dist/sandbox/bwrap.js +159 -0
- package/dist/sandbox/child.d.ts +38 -0
- package/dist/sandbox/child.js +98 -0
- package/dist/sandbox/config.d.ts +89 -0
- package/dist/sandbox/config.js +149 -0
- package/dist/sandbox/fake-sandbox.d.ts +57 -0
- package/dist/sandbox/fake-sandbox.js +110 -0
- package/dist/sandbox/launch.d.ts +51 -0
- package/dist/sandbox/launch.js +134 -0
- package/dist/sandbox/limits.d.ts +63 -0
- package/dist/sandbox/limits.js +113 -0
- package/dist/sandbox/probe.d.ts +46 -0
- package/dist/sandbox/probe.js +102 -0
- package/dist/sandbox/provider.d.ts +83 -0
- package/dist/sandbox/provider.js +126 -0
- package/dist/sandbox/rlimit.d.ts +60 -0
- package/dist/sandbox/rlimit.js +76 -0
- package/dist/sandbox/seccomp.d.ts +48 -0
- package/dist/sandbox/seccomp.js +115 -0
- package/dist/sandbox/userspace.d.ts +65 -0
- package/dist/sandbox/userspace.js +107 -0
- package/dist/sandbox/workdir.d.ts +13 -0
- package/dist/sandbox/workdir.js +44 -0
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +135 -0
- package/dist/testing/platform-gates.d.ts +54 -0
- package/dist/testing/platform-gates.js +62 -0
- package/dist/tool-failure.d.ts +13 -0
- package/dist/tool-failure.js +19 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +145 -0
- package/dist/tools/browser.d.ts +24 -0
- package/dist/tools/browser.js +132 -0
- package/dist/tools/http-request.d.ts +31 -0
- package/dist/tools/http-request.js +117 -0
- package/dist/tools/list-dir.d.ts +9 -0
- package/dist/tools/list-dir.js +45 -0
- package/dist/tools/load-skill.d.ts +37 -0
- package/dist/tools/load-skill.js +76 -0
- package/dist/tools/memory.d.ts +46 -0
- package/dist/tools/memory.js +131 -0
- package/dist/tools/process-control.d.ts +14 -0
- package/dist/tools/process-control.js +49 -0
- package/dist/tools/read-file.d.ts +11 -0
- package/dist/tools/read-file.js +81 -0
- package/dist/tools/read-image.d.ts +33 -0
- package/dist/tools/read-image.js +144 -0
- package/dist/tools/run-code.d.ts +48 -0
- package/dist/tools/run-code.js +115 -0
- package/dist/tools/run-shell.d.ts +22 -0
- package/dist/tools/run-shell.js +81 -0
- package/dist/tools/write-file.d.ts +8 -0
- package/dist/tools/write-file.js +31 -0
- package/package.json +28 -0
package/dist/registry.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ToolRegistryImpl` — the dispatch pipeline of the tool seam.
|
|
3
|
+
*
|
|
4
|
+
* One dispatch runs four stages, in this order (a hard pipeline, not a
|
|
5
|
+
* convention):
|
|
6
|
+
* 1. **schema** — `args` are validated against `tool.spec().parameters`
|
|
7
|
+
* (`toolargs: code=schema …`, the tool never runs);
|
|
8
|
+
* 2. **guard** — the guard chain runs in registration order and the FIRST
|
|
9
|
+
* non-`Allow` decision short-circuits (`denied: toolguard: …`) — a later
|
|
10
|
+
* `Allow` never un-denies an earlier `Deny`;
|
|
11
|
+
* 3. **execute** — through `executeWith` when the tool overrides it, else
|
|
12
|
+
* `execute(args)`;
|
|
13
|
+
* 4. **structure** — the result is a `ToolOutput`: canonical `value`, an
|
|
14
|
+
* optional authored `render`, captured `error`, and the guard `decision`
|
|
15
|
+
* as a first-class field.
|
|
16
|
+
*
|
|
17
|
+
* Errors are captured, never thrown across the seam (parity:
|
|
18
|
+
* `crates/tools/src/registry.rs`).
|
|
19
|
+
*
|
|
20
|
+
* **The verdict never lies (W738 P1)**: `decision` describes what the seam did
|
|
21
|
+
* with the call, so a call the seam REFUSED to run (unknown tool, schema
|
|
22
|
+
* rejection) is a `deny`, never an `allow` — an `allow` there would tell the
|
|
23
|
+
* caller (and the audit log) that a rejected call passed every check. A tool that
|
|
24
|
+
* did run and then failed keeps `allow`: the guards really did allow it and the
|
|
25
|
+
* verdict is not a success flag.
|
|
26
|
+
*/
|
|
27
|
+
import { GUARD_ERROR_PREFIX, TOOLARG_ERROR_PREFIX, contractError, errorText, quoteMessage } from "./errors.js";
|
|
28
|
+
import { validateArgs } from "./schema.js";
|
|
29
|
+
const ALLOW = { kind: "allow" };
|
|
30
|
+
export class ToolRegistryImpl {
|
|
31
|
+
tools = new Map();
|
|
32
|
+
guards = [];
|
|
33
|
+
register(tool) {
|
|
34
|
+
this.tools.set(tool.spec().name, tool);
|
|
35
|
+
}
|
|
36
|
+
addGuard(guard) {
|
|
37
|
+
this.guards.push(guard);
|
|
38
|
+
}
|
|
39
|
+
get(name) {
|
|
40
|
+
return this.tools.get(name);
|
|
41
|
+
}
|
|
42
|
+
/** Registered tool names, in registration order (`schemas()` sorts instead). */
|
|
43
|
+
names() {
|
|
44
|
+
return [...this.tools.keys()];
|
|
45
|
+
}
|
|
46
|
+
/** The guard chain, in evaluation order (diagnostics / compose assertions). */
|
|
47
|
+
guardChain() {
|
|
48
|
+
return this.guards;
|
|
49
|
+
}
|
|
50
|
+
schemas() {
|
|
51
|
+
return [...this.tools.values()].map((t) => t.spec()).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
52
|
+
}
|
|
53
|
+
async dispatch(input) {
|
|
54
|
+
const tool = this.tools.get(input.name);
|
|
55
|
+
if (tool === undefined)
|
|
56
|
+
return refused(input.call_id, `unknown tool: ${input.name}`);
|
|
57
|
+
const invalid = validateArgs(tool.spec().parameters, input.args);
|
|
58
|
+
if (invalid !== null) {
|
|
59
|
+
return refused(input.call_id, contractError(TOOLARG_ERROR_PREFIX, "schema", invalid.message));
|
|
60
|
+
}
|
|
61
|
+
const decision = await this.runGuards(input);
|
|
62
|
+
if (decision.kind === "deny")
|
|
63
|
+
return decisionFailure(input.call_id, "deny", decision.reason);
|
|
64
|
+
if (decision.kind === "ask")
|
|
65
|
+
return decisionFailure(input.call_id, "ask", decision.reason);
|
|
66
|
+
return this.runTool(tool, input);
|
|
67
|
+
}
|
|
68
|
+
async runTool(tool, input) {
|
|
69
|
+
try {
|
|
70
|
+
const outcome = tool.executeWith === undefined
|
|
71
|
+
? { value: await tool.execute(input.args), render: null }
|
|
72
|
+
: await tool.executeWith(input);
|
|
73
|
+
return {
|
|
74
|
+
call_id: input.call_id,
|
|
75
|
+
value: outcome.value,
|
|
76
|
+
render: outcome.render ?? humanRender(outcome.value),
|
|
77
|
+
error: null,
|
|
78
|
+
decision: ALLOW,
|
|
79
|
+
// W855 (B6): a tool-authored model face (e.g. read_file truncation).
|
|
80
|
+
...(outcome.surface === undefined ? {} : { surface: outcome.surface }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
return failure(input.call_id, errorText(e));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async runGuards(input) {
|
|
88
|
+
for (const guard of this.guards) {
|
|
89
|
+
let decision;
|
|
90
|
+
try {
|
|
91
|
+
decision = await guard.check(input);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
decision = { kind: "deny", reason: contractError(GUARD_ERROR_PREFIX, "guard_error", errorText(e)) };
|
|
95
|
+
}
|
|
96
|
+
if (decision.kind !== "allow")
|
|
97
|
+
return decision;
|
|
98
|
+
}
|
|
99
|
+
return ALLOW;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A call that ran and then failed: the guards allowed it, so the verdict is
|
|
104
|
+
* `allow` — `error` carries the failure (`decision` is not a success flag).
|
|
105
|
+
*/
|
|
106
|
+
function failure(callId, error) {
|
|
107
|
+
return { call_id: callId, value: null, render: null, error, decision: ALLOW };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A call the seam REFUSED before execution (unknown tool / invalid args): the
|
|
111
|
+
* verdict is a `deny` whose reason is the very error the caller sees, so
|
|
112
|
+
* "refused" can never be reported as "allowed" (W738 P1).
|
|
113
|
+
*/
|
|
114
|
+
function refused(callId, error) {
|
|
115
|
+
return { call_id: callId, value: null, render: null, error, decision: { kind: "deny", reason: error } };
|
|
116
|
+
}
|
|
117
|
+
function decisionFailure(callId, kind, reason) {
|
|
118
|
+
// parity: a Deny surfaces as `denied: <reason>`, an Ask as `ask: <reason>`.
|
|
119
|
+
return {
|
|
120
|
+
call_id: callId,
|
|
121
|
+
value: null,
|
|
122
|
+
render: null,
|
|
123
|
+
error: `${kind === "deny" ? "denied" : "ask"}: ${reason}`,
|
|
124
|
+
decision: kind === "deny" ? { kind: "deny", reason } : { kind: "ask", reason },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Registry seeded with tools + guards (guards run in the given order). */
|
|
128
|
+
export function createToolRegistry(tools = [], guards = []) {
|
|
129
|
+
const registry = new ToolRegistryImpl();
|
|
130
|
+
for (const tool of tools)
|
|
131
|
+
registry.register(tool);
|
|
132
|
+
for (const guard of guards)
|
|
133
|
+
registry.addGuard(guard);
|
|
134
|
+
return registry;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Best-effort human-readable rendering of a successful result (legacy
|
|
138
|
+
* `human_render`): a `{stdout, stderr, exit_code}` object condenses to a stream
|
|
139
|
+
* summary; plain text and everything else keep the generic value view (`null`).
|
|
140
|
+
*/
|
|
141
|
+
export function humanRender(value) {
|
|
142
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
143
|
+
return null;
|
|
144
|
+
const obj = value;
|
|
145
|
+
const isStreamShape = "stdout" in obj || "stderr" in obj || "exit_code" in obj;
|
|
146
|
+
if (!isStreamShape)
|
|
147
|
+
return null;
|
|
148
|
+
const lines = [];
|
|
149
|
+
if (obj["exit_code"] !== undefined && obj["exit_code"] !== null)
|
|
150
|
+
lines.push(`exit_code: ${String(obj["exit_code"])}`);
|
|
151
|
+
if (typeof obj["stdout"] === "string" && obj["stdout"] !== "")
|
|
152
|
+
lines.push(`stdout: ${obj["stdout"]}`);
|
|
153
|
+
if (typeof obj["stderr"] === "string" && obj["stderr"] !== "")
|
|
154
|
+
lines.push(`stderr: ${obj["stderr"]}`);
|
|
155
|
+
const rendered = lines.join("\n").trimEnd();
|
|
156
|
+
return rendered === "" ? null : rendered;
|
|
157
|
+
}
|
|
158
|
+
/** Stamp a guard denial reason with the contract prefix (compose-time helper). */
|
|
159
|
+
export function guardDenyReason(code, message) {
|
|
160
|
+
return contractError(GUARD_ERROR_PREFIX, code, quoteMessage(message));
|
|
161
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `run_code` parent broker (`crates/tools/src/run_code.rs:562-978`).
|
|
3
|
+
*
|
|
4
|
+
* One `run_code` call = one round trip. The assembled program — TypeScript by
|
|
5
|
+
* default since W774, Python on request — runs in the sandbox; its sub-calls
|
|
6
|
+
* arrive as one-line JSON on stdout and the parent answers on stdin after
|
|
7
|
+
* dispatching each one through the **same** registry pipeline (schema → guards →
|
|
8
|
+
* execute) the model itself would use. Only `main()`'s return value travels back
|
|
9
|
+
* as the tool result.
|
|
10
|
+
*
|
|
11
|
+
* The protocol is language-neutral (it is byte-identical for both SDKs), so the
|
|
12
|
+
* language only decides two things: the script file's extension and the
|
|
13
|
+
* interpreter that runs it.
|
|
14
|
+
*
|
|
15
|
+
* Invariants:
|
|
16
|
+
* - every limit is enforced here, never in the child: the 21st sub-call is
|
|
17
|
+
* refused before dispatch, the wall clock is enforced while waiting for a
|
|
18
|
+
* line, the sub-call output ledger is charged per reply;
|
|
19
|
+
* - every infrastructure failure is a structured `run_code: code=… msg="…"`
|
|
20
|
+
* (invalid_arg | registry | config | spawn | protocol | timeout | aborted);
|
|
21
|
+
* a program exception is that exception's text plus a bounded log tail;
|
|
22
|
+
* - the child is killed on timeout, on cancel and on protocol failure — never
|
|
23
|
+
* left behind — and its script file is removed on every exit path.
|
|
24
|
+
*/
|
|
25
|
+
import type { Sandbox, SessionEvent, ToolExecOutcome, ToolRegistry } from "@celestea/core";
|
|
26
|
+
import { type RunCodeConfig } from "./limits.js";
|
|
27
|
+
import { type RunCodeLanguage } from "./sdk.js";
|
|
28
|
+
/** Session-log sink for sub-call rows (legacy `Fn(SessionEvent)` sink). */
|
|
29
|
+
export type RunCodeEventSink = (event: SessionEvent) => void;
|
|
30
|
+
/** Everything one broker run needs (the tool binds the registry + call id). */
|
|
31
|
+
export interface BrokerContext {
|
|
32
|
+
sandbox: Sandbox;
|
|
33
|
+
registry: ToolRegistry;
|
|
34
|
+
events?: RunCodeEventSink;
|
|
35
|
+
config: RunCodeConfig;
|
|
36
|
+
/** The `run_code` call id: sub-call ids are `<parentId>:c<n>`. */
|
|
37
|
+
parentId: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Absolute interpreter path of a TypeScript program (W774): the Node that runs
|
|
41
|
+
* THIS process, which is the same binary the sandbox can see (`--ro-bind / /`
|
|
42
|
+
* mounts the host root read-only) and never depends on the child's PATH.
|
|
43
|
+
* `/usr/bin/node` is preferred because it is the host's system-wide install;
|
|
44
|
+
* `process.execPath` is the honest fallback (nvm/volta hosts).
|
|
45
|
+
*
|
|
46
|
+
* W885: the constant is kept for compatibility, but the interpreter actually
|
|
47
|
+
* used is resolved per run (`resolveInterpreter`), which checks the platform
|
|
48
|
+
* PATH first — `/usr/bin/node` is a POSIX convention that simply does not
|
|
49
|
+
* exist on Windows (W883 B9/B15).
|
|
50
|
+
*/
|
|
51
|
+
export declare const TS_PROGRAM_RUNTIME: string;
|
|
52
|
+
/** One full run_code round trip: the program's final value + its render. */
|
|
53
|
+
export declare function brokerRun(ctx: BrokerContext, args: unknown): Promise<ToolExecOutcome>;
|
|
54
|
+
/**
|
|
55
|
+
* The interpreter of one `run_code` program (W885).
|
|
56
|
+
*
|
|
57
|
+
* TypeScript runs under the Node that runs THIS process — the one binary the
|
|
58
|
+
* sandbox is guaranteed to see through `--ro-bind / /` — with the host's PATH
|
|
59
|
+
* consulted first ONLY when that yields a Node (`node` is `node.exe` on
|
|
60
|
+
* Windows, and `process.execPath` there is routinely `C:\\Program Files\\…`
|
|
61
|
+
* with a space in it). Python keeps the historical `python3` on POSIX and
|
|
62
|
+
* falls back to the names Windows actually ships (`python`, `py`).
|
|
63
|
+
*
|
|
64
|
+
* Anything `whichSync` cannot find becomes the bare name, so a missing
|
|
65
|
+
* interpreter still surfaces as the interpreter's own "not found" instead of a
|
|
66
|
+
* silent wrong one.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveInterpreter(language: RunCodeLanguage, platform?: string, env?: NodeJS.ProcessEnv): string;
|