agentfootprint 9.6.0 → 9.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/adapters/code/agentcore.js +294 -0
- package/dist/adapters/code/agentcore.js.map +1 -0
- package/dist/adapters/code/local.js +200 -0
- package/dist/adapters/code/local.js.map +1 -0
- package/dist/core/Agent.js +132 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/RunnerBase.js +67 -0
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +90 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/codeRunnerTool.js +252 -0
- package/dist/core/codeRunnerTool.js.map +1 -0
- package/dist/core/toolSessions.js +396 -0
- package/dist/core/toolSessions.js.map +1 -0
- package/dist/core/tools.js.map +1 -1
- package/dist/doors/providers.js +13 -0
- package/dist/doors/providers.js.map +1 -1
- package/dist/esm/adapters/code/agentcore.d.ts +133 -0
- package/dist/esm/adapters/code/agentcore.js +290 -0
- package/dist/esm/adapters/code/agentcore.js.map +1 -0
- package/dist/esm/adapters/code/local.d.ts +99 -0
- package/dist/esm/adapters/code/local.js +196 -0
- package/dist/esm/adapters/code/local.js.map +1 -0
- package/dist/esm/adapters/types.d.ts +87 -0
- package/dist/esm/core/Agent.d.ts +65 -0
- package/dist/esm/core/Agent.js +133 -1
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/RunnerBase.d.ts +51 -0
- package/dist/esm/core/RunnerBase.js +67 -0
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +31 -0
- package/dist/esm/core/agent/stages/toolCalls.js +90 -0
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +23 -2
- package/dist/esm/core/codeRunnerTool.d.ts +120 -0
- package/dist/esm/core/codeRunnerTool.js +247 -0
- package/dist/esm/core/codeRunnerTool.js.map +1 -0
- package/dist/esm/core/toolSessions.d.ts +318 -0
- package/dist/esm/core/toolSessions.js +389 -0
- package/dist/esm/core/toolSessions.js.map +1 -0
- package/dist/esm/core/tools.d.ts +60 -0
- package/dist/esm/core/tools.js.map +1 -1
- package/dist/esm/doors/providers.d.ts +7 -0
- package/dist/esm/doors/providers.js +10 -0
- package/dist/esm/doors/providers.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +50 -0
- package/dist/esm/events/registry.d.ts +9 -1
- package/dist/esm/events/registry.js +8 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/mcp/mcpServe.js +37 -1
- package/dist/esm/lib/mcp/mcpServe.js.map +1 -1
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +15 -1
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/events/registry.js +8 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/mcp/mcpServe.js +37 -1
- package/dist/lib/mcp/mcpServe.js.map +1 -1
- package/dist/lib/trace-toolpack/traceToolpack.js +15 -1
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/types/adapters/code/agentcore.d.ts +134 -0
- package/dist/types/adapters/code/agentcore.d.ts.map +1 -0
- package/dist/types/adapters/code/local.d.ts +100 -0
- package/dist/types/adapters/code/local.d.ts.map +1 -0
- package/dist/types/adapters/types.d.ts +87 -0
- package/dist/types/adapters/types.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +65 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +51 -0
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +31 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +23 -2
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/codeRunnerTool.d.ts +121 -0
- package/dist/types/core/codeRunnerTool.d.ts.map +1 -0
- package/dist/types/core/toolSessions.d.ts +319 -0
- package/dist/types/core/toolSessions.d.ts.map +1 -0
- package/dist/types/core/tools.d.ts +60 -0
- package/dist/types/core/tools.d.ts.map +1 -1
- package/dist/types/doors/providers.d.ts +7 -0
- package/dist/types/doors/providers.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +50 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +9 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpServe.d.ts.map +1 -1
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codeRunnerTool — the tool that turns a {@link CodeRunner} into something an
|
|
3
|
+
* LLM can call, holding ONE session per isolation key.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Factory over `defineTool` + the 9.7.0 tool-session contract.
|
|
6
|
+
* Role: the first consumer of `ctx.onTeardown` — and the proof it works.
|
|
7
|
+
* Emits: nothing directly; the teardown tier reports
|
|
8
|
+
* `agentfootprint.tools.session_started` / `_reused` / `_closed` /
|
|
9
|
+
* `_close_failed`.
|
|
10
|
+
*
|
|
11
|
+
* ── The doctrine: summarize prose, compute data ─────────────────────────────
|
|
12
|
+
* A tool that hands the model 40,000 rows has not given it data; it has spent
|
|
13
|
+
* the window. The motivating failure is real and measured: a production request
|
|
14
|
+
* of 879,073 tokens, almost all of it one tool result pasted into the prompt.
|
|
15
|
+
* (Since 9.6.0 that shape at least fails by NAME —
|
|
16
|
+
* `ContextWindowExceededError` — instead of as a vendor 400. This is the other
|
|
17
|
+
* half: not failing better, but not needing to.)
|
|
18
|
+
*
|
|
19
|
+
* With a code runner the model writes an aggregation, the RUNNER holds the
|
|
20
|
+
* rows, and what comes back is the number. Prose gets summarized; data gets
|
|
21
|
+
* computed.
|
|
22
|
+
*
|
|
23
|
+
* ── Why the session must be keyed, and keyed WIDELY ENOUGH ──────────────────
|
|
24
|
+
* The session is the whole value: a code interpreter costs seconds to start and
|
|
25
|
+
* milliseconds to invoke. But a session holds a filesystem, an environment and
|
|
26
|
+
* half-run state, so the thing it is keyed on IS the isolation boundary. A
|
|
27
|
+
* standing agent serving many people from one process, holding one session in a
|
|
28
|
+
* module map, gives person B person A's files.
|
|
29
|
+
*
|
|
30
|
+
* So the key comes from `toolSessionKey(ctx, scope)` — one exported
|
|
31
|
+
* implementation, composing tenant + principal + (session | run). Never a bare
|
|
32
|
+
* `sessionId`: that is caller data, and anyone who can reach the host can put
|
|
33
|
+
* someone else's there.
|
|
34
|
+
*
|
|
35
|
+
* ── Degradation is REFUSED, never silent ────────────────────────────────────
|
|
36
|
+
* Ask for `scope: 'session'` at a door with no session and this throws, naming
|
|
37
|
+
* the door. It does not quietly fall back. Falling back to a WIDER key is the
|
|
38
|
+
* cross-binding bug itself; falling back to a NARROWER one is a silent 30×
|
|
39
|
+
* latency change nobody sees until the bill.
|
|
40
|
+
*
|
|
41
|
+
* @example a session per run, on a local dev machine
|
|
42
|
+
* const agent = Agent.create({ provider })
|
|
43
|
+
* .tool(codeRunnerTool({ runner: localCodeRunner() }))
|
|
44
|
+
* .build();
|
|
45
|
+
*
|
|
46
|
+
* @example a session per hosted conversation, on a real sandbox
|
|
47
|
+
* const runner = agentCoreCodeRunner({ region, identifier: 'aws.codeinterpreter.v1' });
|
|
48
|
+
* const agent = Agent.create({ provider })
|
|
49
|
+
* .tool(codeRunnerTool({ runner, scope: 'session', language: 'python' }))
|
|
50
|
+
* .build();
|
|
51
|
+
* // the composition root says when a session is over:
|
|
52
|
+
* conversation.onClose(() => void agent.closeToolSessions({ sessionId }));
|
|
53
|
+
*/
|
|
54
|
+
import type { CodeRunner, CodeSession } from '../adapters/types.js';
|
|
55
|
+
import type { CredentialNeed } from '../identity/types.js';
|
|
56
|
+
import type { CheckInDemand } from './checkin.js';
|
|
57
|
+
import { type Tool } from './tools.js';
|
|
58
|
+
import { type TeardownScope } from './toolSessions.js';
|
|
59
|
+
/** The scopes a code session can be held under. `'shutdown'` is not one: it is
|
|
60
|
+
* when everything goes, not a thing to key a session on. */
|
|
61
|
+
export type CodeRunnerToolScope = Extract<TeardownScope, 'call' | 'run' | 'session'>;
|
|
62
|
+
export interface CodeRunnerToolOptions {
|
|
63
|
+
/** The backend. `localCodeRunner()` for a dev loop, `agentCoreCodeRunner(...)`
|
|
64
|
+
* for a real sandbox — the tool is identical across the swap. */
|
|
65
|
+
readonly runner: CodeRunner;
|
|
66
|
+
/** Tool name the model sees. Default `'run_code'`. */
|
|
67
|
+
readonly name?: string;
|
|
68
|
+
/** Description the model sees. A sensible one is composed from `scope` +
|
|
69
|
+
* `language` when you do not pass one. */
|
|
70
|
+
readonly description?: string;
|
|
71
|
+
/**
|
|
72
|
+
* How long one session lives. Default `'run'` — a turn's worth of work shares
|
|
73
|
+
* one interpreter, and nothing outlives the turn.
|
|
74
|
+
*
|
|
75
|
+
* `'session'` keeps the interpreter across the turns of one hosted
|
|
76
|
+
* conversation (variables persist, files persist) and REQUIRES a
|
|
77
|
+
* session-bound run plus a composition root that calls
|
|
78
|
+
* `agent.closeToolSessions({ sessionId })`.
|
|
79
|
+
*
|
|
80
|
+
* `'call'` starts and stops per invocation — the safest and the slowest.
|
|
81
|
+
*/
|
|
82
|
+
readonly scope?: CodeRunnerToolScope;
|
|
83
|
+
/** Default language for the code the model writes. Default `'python'`. */
|
|
84
|
+
readonly language?: string;
|
|
85
|
+
/** Per-stream ceiling for what reaches the model, in characters. Default 4000.
|
|
86
|
+
* Anything cut is STATED in the result, never dropped quietly. */
|
|
87
|
+
readonly maxOutputChars?: number;
|
|
88
|
+
/** Per-execution ceiling handed to the runner. */
|
|
89
|
+
readonly timeoutMs?: number;
|
|
90
|
+
/** Demand a human check-in before code runs — `'always'`, or a predicate over
|
|
91
|
+
* the code string. A pause here does NOT tear the session down. */
|
|
92
|
+
readonly checkIn?: CheckInDemand<{
|
|
93
|
+
code: string;
|
|
94
|
+
}>;
|
|
95
|
+
/** A credential this tool needs (declare-and-push). Resolved before execute.
|
|
96
|
+
* Do NOT cache it past the call: a session outliving a run outlives its token. */
|
|
97
|
+
readonly needs?: CredentialNeed;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The per-tool session map, riding the `Tool` under a REGISTRY symbol.
|
|
101
|
+
*
|
|
102
|
+
* `Symbol.for`, not a unique symbol: this package ships CJS and ESM, and a tool
|
|
103
|
+
* built through one entry point must be readable through the other. The same
|
|
104
|
+
* move `INNER_RUN_RECORDS` makes for `flowchartAsTool({ keepRecord })` — and
|
|
105
|
+
* deliberately a DIFFERENT symbol, so one tool can carry both (spreading a tool
|
|
106
|
+
* preserves symbol keys, which is why `{...tool, [SYM]: store}` composes).
|
|
107
|
+
*
|
|
108
|
+
* Invisible to the LLM, invisible to `Tool`'s shape, reachable by a test and by
|
|
109
|
+
* whatever inspector comes next.
|
|
110
|
+
*/
|
|
111
|
+
export declare const TOOL_SESSIONS: unique symbol;
|
|
112
|
+
/** A `Tool` that holds live sessions, keyed by isolation key. */
|
|
113
|
+
export interface HoldsToolSessions {
|
|
114
|
+
readonly [TOOL_SESSIONS]: ReadonlyMap<string, CodeSession>;
|
|
115
|
+
}
|
|
116
|
+
/** Read the live-session map off a candidate, or `undefined` when it holds none. */
|
|
117
|
+
export declare function toolSessionsOf(candidate: unknown): ReadonlyMap<string, CodeSession> | undefined;
|
|
118
|
+
export declare function codeRunnerTool(options: CodeRunnerToolOptions): Tool<{
|
|
119
|
+
code: string;
|
|
120
|
+
}, string> & HoldsToolSessions;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codeRunnerTool — the tool that turns a {@link CodeRunner} into something an
|
|
3
|
+
* LLM can call, holding ONE session per isolation key.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Factory over `defineTool` + the 9.7.0 tool-session contract.
|
|
6
|
+
* Role: the first consumer of `ctx.onTeardown` — and the proof it works.
|
|
7
|
+
* Emits: nothing directly; the teardown tier reports
|
|
8
|
+
* `agentfootprint.tools.session_started` / `_reused` / `_closed` /
|
|
9
|
+
* `_close_failed`.
|
|
10
|
+
*
|
|
11
|
+
* ── The doctrine: summarize prose, compute data ─────────────────────────────
|
|
12
|
+
* A tool that hands the model 40,000 rows has not given it data; it has spent
|
|
13
|
+
* the window. The motivating failure is real and measured: a production request
|
|
14
|
+
* of 879,073 tokens, almost all of it one tool result pasted into the prompt.
|
|
15
|
+
* (Since 9.6.0 that shape at least fails by NAME —
|
|
16
|
+
* `ContextWindowExceededError` — instead of as a vendor 400. This is the other
|
|
17
|
+
* half: not failing better, but not needing to.)
|
|
18
|
+
*
|
|
19
|
+
* With a code runner the model writes an aggregation, the RUNNER holds the
|
|
20
|
+
* rows, and what comes back is the number. Prose gets summarized; data gets
|
|
21
|
+
* computed.
|
|
22
|
+
*
|
|
23
|
+
* ── Why the session must be keyed, and keyed WIDELY ENOUGH ──────────────────
|
|
24
|
+
* The session is the whole value: a code interpreter costs seconds to start and
|
|
25
|
+
* milliseconds to invoke. But a session holds a filesystem, an environment and
|
|
26
|
+
* half-run state, so the thing it is keyed on IS the isolation boundary. A
|
|
27
|
+
* standing agent serving many people from one process, holding one session in a
|
|
28
|
+
* module map, gives person B person A's files.
|
|
29
|
+
*
|
|
30
|
+
* So the key comes from `toolSessionKey(ctx, scope)` — one exported
|
|
31
|
+
* implementation, composing tenant + principal + (session | run). Never a bare
|
|
32
|
+
* `sessionId`: that is caller data, and anyone who can reach the host can put
|
|
33
|
+
* someone else's there.
|
|
34
|
+
*
|
|
35
|
+
* ── Degradation is REFUSED, never silent ────────────────────────────────────
|
|
36
|
+
* Ask for `scope: 'session'` at a door with no session and this throws, naming
|
|
37
|
+
* the door. It does not quietly fall back. Falling back to a WIDER key is the
|
|
38
|
+
* cross-binding bug itself; falling back to a NARROWER one is a silent 30×
|
|
39
|
+
* latency change nobody sees until the bill.
|
|
40
|
+
*
|
|
41
|
+
* @example a session per run, on a local dev machine
|
|
42
|
+
* const agent = Agent.create({ provider })
|
|
43
|
+
* .tool(codeRunnerTool({ runner: localCodeRunner() }))
|
|
44
|
+
* .build();
|
|
45
|
+
*
|
|
46
|
+
* @example a session per hosted conversation, on a real sandbox
|
|
47
|
+
* const runner = agentCoreCodeRunner({ region, identifier: 'aws.codeinterpreter.v1' });
|
|
48
|
+
* const agent = Agent.create({ provider })
|
|
49
|
+
* .tool(codeRunnerTool({ runner, scope: 'session', language: 'python' }))
|
|
50
|
+
* .build();
|
|
51
|
+
* // the composition root says when a session is over:
|
|
52
|
+
* conversation.onClose(() => void agent.closeToolSessions({ sessionId }));
|
|
53
|
+
*/
|
|
54
|
+
import { defineTool } from './tools.js';
|
|
55
|
+
import { toolSessionKey } from './toolSessions.js';
|
|
56
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 4_000;
|
|
57
|
+
/**
|
|
58
|
+
* The per-tool session map, riding the `Tool` under a REGISTRY symbol.
|
|
59
|
+
*
|
|
60
|
+
* `Symbol.for`, not a unique symbol: this package ships CJS and ESM, and a tool
|
|
61
|
+
* built through one entry point must be readable through the other. The same
|
|
62
|
+
* move `INNER_RUN_RECORDS` makes for `flowchartAsTool({ keepRecord })` — and
|
|
63
|
+
* deliberately a DIFFERENT symbol, so one tool can carry both (spreading a tool
|
|
64
|
+
* preserves symbol keys, which is why `{...tool, [SYM]: store}` composes).
|
|
65
|
+
*
|
|
66
|
+
* Invisible to the LLM, invisible to `Tool`'s shape, reachable by a test and by
|
|
67
|
+
* whatever inspector comes next.
|
|
68
|
+
*/
|
|
69
|
+
export const TOOL_SESSIONS = Symbol.for('agentfootprint.tools.sessions');
|
|
70
|
+
/** Read the live-session map off a candidate, or `undefined` when it holds none. */
|
|
71
|
+
export function toolSessionsOf(candidate) {
|
|
72
|
+
if (candidate === null || typeof candidate !== 'object')
|
|
73
|
+
return undefined;
|
|
74
|
+
const held = candidate[TOOL_SESSIONS];
|
|
75
|
+
return held instanceof Map ? held : undefined;
|
|
76
|
+
}
|
|
77
|
+
export function codeRunnerTool(options) {
|
|
78
|
+
const name = options.name ?? 'run_code';
|
|
79
|
+
const scope = options.scope ?? 'run';
|
|
80
|
+
const language = options.language ?? 'python';
|
|
81
|
+
const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
82
|
+
/**
|
|
83
|
+
* Live sessions by isolation key.
|
|
84
|
+
*
|
|
85
|
+
* A `Map` and not a closure variable, because two keys are two sandboxes: one
|
|
86
|
+
* variable would be exactly the cross-binding this whole feature exists to
|
|
87
|
+
* prevent. Entries are removed by the teardown the tool registers alongside
|
|
88
|
+
* them, so the map cannot outlive what it points at.
|
|
89
|
+
*/
|
|
90
|
+
const sessions = new Map();
|
|
91
|
+
/** In-flight starts, so two parallel tool calls under one key open ONE session. */
|
|
92
|
+
const starting = new Map();
|
|
93
|
+
const tool = defineTool({
|
|
94
|
+
name,
|
|
95
|
+
description: options.description ?? describe(scope, language),
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
code: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
description: `${language} source to execute. Print what you want back — stdout is the result. ` +
|
|
102
|
+
'Compute over big data here rather than asking for it to be pasted back to you.',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
required: ['code'],
|
|
106
|
+
},
|
|
107
|
+
...(options.checkIn !== undefined && { checkIn: options.checkIn }),
|
|
108
|
+
...(options.needs && { needs: options.needs }),
|
|
109
|
+
execute: async (args, ctx) => {
|
|
110
|
+
const key = requireKey(ctx, scope, name);
|
|
111
|
+
const session = await acquire(key, ctx);
|
|
112
|
+
const result = await session.execute({
|
|
113
|
+
code: args.code,
|
|
114
|
+
language,
|
|
115
|
+
...(options.timeoutMs !== undefined && { timeoutMs: options.timeoutMs }),
|
|
116
|
+
...(ctx.signal && { signal: ctx.signal }),
|
|
117
|
+
});
|
|
118
|
+
return render(result, maxOutputChars);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
/** Get the session for `key`, opening one if this is the first call. */
|
|
122
|
+
const acquire = async (key, ctx) => {
|
|
123
|
+
const live = sessions.get(key);
|
|
124
|
+
if (live) {
|
|
125
|
+
// A reuse re-registers under the SAME (tool, scope, key). The tier keeps
|
|
126
|
+
// the FIRST cleanup — it is the one holding this handle — and treats the
|
|
127
|
+
// repeat as a touch, which is how the idle sweep and the LRU bound learn
|
|
128
|
+
// this session is still in use. Registering only once would make a busy
|
|
129
|
+
// session look as cold as an abandoned one.
|
|
130
|
+
register(key, live, ctx);
|
|
131
|
+
return live;
|
|
132
|
+
}
|
|
133
|
+
// Two parallel tool calls in one iteration must not open two sandboxes.
|
|
134
|
+
const inFlight = starting.get(key);
|
|
135
|
+
if (inFlight)
|
|
136
|
+
return inFlight;
|
|
137
|
+
const opening = options.runner
|
|
138
|
+
.start({ key, language, ...(ctx.signal && { signal: ctx.signal }) })
|
|
139
|
+
.then((opened) => {
|
|
140
|
+
sessions.set(key, opened);
|
|
141
|
+
register(key, opened, ctx);
|
|
142
|
+
return opened;
|
|
143
|
+
})
|
|
144
|
+
.finally(() => {
|
|
145
|
+
starting.delete(key);
|
|
146
|
+
});
|
|
147
|
+
starting.set(key, opening);
|
|
148
|
+
return opening;
|
|
149
|
+
};
|
|
150
|
+
const register = (key, session, ctx) => {
|
|
151
|
+
ctx.onTeardown?.(async () => {
|
|
152
|
+
// Drop the entry FIRST: if `stop()` throws, the map must not keep
|
|
153
|
+
// handing out a session the runtime has already given up on.
|
|
154
|
+
sessions.delete(key);
|
|
155
|
+
await session.stop();
|
|
156
|
+
}, { scope, key, runnerId: options.runner.id, label: language });
|
|
157
|
+
};
|
|
158
|
+
// The live map rides the Tool under a registry symbol — see TOOL_SESSIONS.
|
|
159
|
+
return { ...tool, [TOOL_SESSIONS]: sessions };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Derive the isolation key, or REFUSE by name.
|
|
163
|
+
*
|
|
164
|
+
* The three refusals are the security surface of this tool, so each says what
|
|
165
|
+
* is missing, why it matters, and the one-line fix. None of them degrades
|
|
166
|
+
* quietly: a fallback to a wider key is the cross-binding bug, and a fallback
|
|
167
|
+
* to a narrower one silently multiplies latency and cost.
|
|
168
|
+
*/
|
|
169
|
+
function requireKey(ctx, scope, name) {
|
|
170
|
+
const supported = ctx.teardownScopes;
|
|
171
|
+
if (supported !== undefined && !supported.includes(scope)) {
|
|
172
|
+
throw new Error(`${name}: this tool holds a '${scope}'-scoped code session, and the door it is running ` +
|
|
173
|
+
`behind honours ${supported.length === 0 ? 'no teardown scopes' : supported.join(', ')}. ` +
|
|
174
|
+
'A session nothing will ever close is a sandbox left running. Either run this tool ' +
|
|
175
|
+
`inside an Agent, or build it with scope: '${supported[0] ?? 'call'}'.`);
|
|
176
|
+
}
|
|
177
|
+
const key = toolSessionKey(ctx, scope);
|
|
178
|
+
if (key)
|
|
179
|
+
return key;
|
|
180
|
+
if (scope === 'session') {
|
|
181
|
+
throw new Error(`${name}: scope 'session' needs a hosting session, and this run has no sessionId. ` +
|
|
182
|
+
'Pass one — `agent.run({ message, sessionId })`, which `standingAgent` does from ' +
|
|
183
|
+
"`HostRequest.sessionId` — or build this tool with scope: 'run'. It is not silently " +
|
|
184
|
+
'narrowed, because a session-scoped interpreter and a run-scoped one differ by about ' +
|
|
185
|
+
'30x in start-up cost, and by everything in what persists between turns.');
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`${name}: scope 'run' needs a run, and this call has no runId — it is being served ` +
|
|
188
|
+
'outside an Agent (over `mcpServe`, or from a script). A served call is one call, not a ' +
|
|
189
|
+
"turn, so nothing would ever end the session. Build this tool with scope: 'call' for " +
|
|
190
|
+
'that door.');
|
|
191
|
+
}
|
|
192
|
+
/** The description the model reads when the caller did not write one. */
|
|
193
|
+
function describe(scope, language) {
|
|
194
|
+
const persistence = scope === 'call'
|
|
195
|
+
? 'Each call runs in a fresh environment — nothing carries over, so include everything you need.'
|
|
196
|
+
: scope === 'run'
|
|
197
|
+
? 'State persists across calls within this turn: variables and files you create stay available.'
|
|
198
|
+
: 'State persists across the whole conversation: variables and files you create stay available in later turns.';
|
|
199
|
+
return (`Execute ${language} code and return its output. ${persistence} ` +
|
|
200
|
+
'Use this to COMPUTE over data rather than asking for the data itself — fetch, filter, ' +
|
|
201
|
+
'aggregate and print the answer, so a large result never has to travel through this ' +
|
|
202
|
+
'conversation. Print what you want to see; stdout is what you get back.');
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Render a `CodeResult` for the model.
|
|
206
|
+
*
|
|
207
|
+
* TRUNCATION IS ALWAYS STATED. A slice the model cannot see is a silent
|
|
208
|
+
* success: it goes on to reason over a fragment of a table believing it has the
|
|
209
|
+
* table. The marker is deliberately plain text in the result body, not
|
|
210
|
+
* metadata, because the result body is the only channel the model reads.
|
|
211
|
+
*/
|
|
212
|
+
function render(result, maxOutputChars) {
|
|
213
|
+
const parts = [];
|
|
214
|
+
const stdout = clip(result.stdout, maxOutputChars, result.truncated?.stdout === true);
|
|
215
|
+
const stderr = clip(result.stderr, maxOutputChars, result.truncated?.stderr === true);
|
|
216
|
+
if (stdout.text.length > 0)
|
|
217
|
+
parts.push(stdout.text);
|
|
218
|
+
if (stderr.text.length > 0)
|
|
219
|
+
parts.push(`stderr:\n${stderr.text}`);
|
|
220
|
+
if (!result.ok) {
|
|
221
|
+
parts.push(`[exit ${result.exitCode ?? 'non-zero'}] the code did not complete successfully — read ` +
|
|
222
|
+
'stderr above and fix it.');
|
|
223
|
+
}
|
|
224
|
+
for (const artifact of result.artifacts ?? []) {
|
|
225
|
+
parts.push(`[artifact: ${artifact.name}, ${artifact.bytes} bytes${artifact.uri ? `, ${artifact.uri}` : ''}]`);
|
|
226
|
+
}
|
|
227
|
+
if (parts.length === 0) {
|
|
228
|
+
// "Nothing was printed" and "this returned nothing" are different facts and
|
|
229
|
+
// only one of them tells the model what to do next.
|
|
230
|
+
parts.push('(the code ran and printed nothing — print the value you want returned)');
|
|
231
|
+
}
|
|
232
|
+
return parts.join('\n');
|
|
233
|
+
}
|
|
234
|
+
/** Clip to the tool's ceiling, and SAY SO — including a cut the runner made. */
|
|
235
|
+
function clip(text, max, alreadyCutUpstream) {
|
|
236
|
+
const cutHere = text.length > max;
|
|
237
|
+
const shown = cutHere ? text.slice(0, max) : text;
|
|
238
|
+
if (!cutHere && !alreadyCutUpstream)
|
|
239
|
+
return { text: shown };
|
|
240
|
+
const of = cutHere ? text.length : undefined;
|
|
241
|
+
return {
|
|
242
|
+
text: `${shown}\n[truncated: showing ${shown.length}${of !== undefined ? ` of ${of}` : ''} ` +
|
|
243
|
+
'characters. Re-run printing a summary, a slice, or an aggregate instead of the whole ' +
|
|
244
|
+
'value.]',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=codeRunnerTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codeRunnerTool.js","sourceRoot":"","sources":["../../../src/core/codeRunnerTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAKH,OAAO,EAAE,UAAU,EAAwC,MAAM,YAAY,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAsB,MAAM,mBAAmB,CAAC;AA0CvE,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,aAAa,GAAkB,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AAOxF,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,SAAkB;IAC/C,IAAI,SAAS,KAAK,IAAI,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1E,MAAM,IAAI,GAAI,SAAwC,CAAC,aAAa,CAAC,CAAC;IACtE,OAAO,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAA8B;IAE9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC;IACxC,MAAM,KAAK,GAAwB,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC;IAC9C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;IAE1E;;;;;;;OAOG;IACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,mFAAmF;IACnF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgC,CAAC;IAEzD,MAAM,IAAI,GAAG,UAAU,CAA2B;QAChD,IAAI;QACJ,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;QAC7D,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,GAAG,QAAQ,uEAAuE;wBAClF,gFAAgF;iBACnF;aACF;YACD,QAAQ,EAAE,CAAC,MAAM,CAAC;SACnB;QACD,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QAClE,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QAC9C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAmB,EAAE;YAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;gBACnC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ;gBACR,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;gBACxE,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;aAC1C,CAAC,CAAC;YACH,OAAO,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACxC,CAAC;KACF,CAAC,CAAC;IAEH,wEAAwE;IACxE,MAAM,OAAO,GAAG,KAAK,EAAE,GAAW,EAAE,GAAyB,EAAwB,EAAE;QACrF,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,yEAAyE;YACzE,yEAAyE;YACzE,yEAAyE;YACzE,wEAAwE;YACxE,4CAA4C;YAC5C,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,wEAAwE;QACxE,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM;aAC3B,KAAK,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;aACnE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;YAC3B,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;QACL,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3B,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,OAAoB,EAAE,GAAyB,EAAQ,EAAE;QACtF,GAAG,CAAC,UAAU,EAAE,CACd,KAAK,IAAI,EAAE;YACT,kEAAkE;YAClE,6DAA6D;YAC7D,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACrB,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC,EACD,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAC7D,CAAC;IACJ,CAAC,CAAC;IAEF,2EAA2E;IAC3E,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,GAAyB,EAAE,KAA0B,EAAE,IAAY;IACrF,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,CAAC;IACrC,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,wBAAwB,KAAK,oDAAoD;YACtF,kBAAkB,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC1F,oFAAoF;YACpF,6CAA6C,SAAS,CAAC,CAAC,CAAC,IAAI,MAAM,IAAI,CAC1E,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IACpB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,4EAA4E;YACjF,kFAAkF;YAClF,qFAAqF;YACrF,sFAAsF;YACtF,yEAAyE,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,6EAA6E;QAClF,yFAAyF;QACzF,sFAAsF;QACtF,YAAY,CACf,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,SAAS,QAAQ,CAAC,KAA0B,EAAE,QAAgB;IAC5D,MAAM,WAAW,GACf,KAAK,KAAK,MAAM;QACd,CAAC,CAAC,+FAA+F;QACjG,CAAC,CAAC,KAAK,KAAK,KAAK;YACjB,CAAC,CAAC,8FAA8F;YAChG,CAAC,CAAC,6GAA6G,CAAC;IACpH,OAAO,CACL,WAAW,QAAQ,gCAAgC,WAAW,GAAG;QACjE,wFAAwF;QACxF,qFAAqF;QACrF,wEAAwE,CACzE,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,MAAM,CAAC,MAAkB,EAAE,cAAsB;IACxD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC;IACtF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC;IACtF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,SAAS,MAAM,CAAC,QAAQ,IAAI,UAAU,kDAAkD;YACtF,0BAA0B,CAC7B,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QAC9C,KAAK,CAAC,IAAI,CACR,cAAc,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,SAC5C,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EACvC,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,4EAA4E;QAC5E,oDAAoD;QACpD,KAAK,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,gFAAgF;AAChF,SAAS,IAAI,CAAC,IAAY,EAAE,GAAW,EAAE,kBAA2B;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClD,IAAI,CAAC,OAAO,IAAI,CAAC,kBAAkB;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC5D,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7C,OAAO;QACL,IAAI,EACF,GAAG,KAAK,yBAAyB,KAAK,CAAC,MAAM,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG;YACtF,uFAAuF;YACvF,SAAS;KACZ,CAAC;AACJ,CAAC"}
|