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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentCoreCodeRunner — AWS Bedrock AgentCore Code Interpreter behind the
|
|
3
|
+
* {@link CodeRunner} port (peer-dep `@aws-sdk/client-bedrock-agentcore`).
|
|
4
|
+
*
|
|
5
|
+
* import { agentCoreCodeRunner } from 'agentfootprint/providers';
|
|
6
|
+
* const runner = agentCoreCodeRunner({ region: 'us-east-1', identifier: 'aws.codeinterpreter.v1' });
|
|
7
|
+
*
|
|
8
|
+
* A REAL sandbox, which is the point: `localCodeRunner` gives you process
|
|
9
|
+
* isolation on your own machine and says so; this gives you a managed,
|
|
10
|
+
* network-and-filesystem-isolated environment, and the tool code is identical
|
|
11
|
+
* across the swap.
|
|
12
|
+
*
|
|
13
|
+
* ── The three operations, verified against the SDK ──────────────────────────
|
|
14
|
+
* Pinned in `test/adapters/aws/awsCommandPin.ts`, and verified against a real
|
|
15
|
+
* install of `@aws-sdk/client-bedrock-agentcore` 3.1108.0:
|
|
16
|
+
*
|
|
17
|
+
* • `StartCodeInterpreterSessionCommand`
|
|
18
|
+
* in `{ codeInterpreterIdentifier, name?, sessionTimeoutSeconds? }`
|
|
19
|
+
* out `{ codeInterpreterIdentifier, sessionId, createdAt }`
|
|
20
|
+
* • `InvokeCodeInterpreterCommand`
|
|
21
|
+
* in `{ codeInterpreterIdentifier, sessionId, name: 'executeCode',
|
|
22
|
+
* arguments: { code, language } }`
|
|
23
|
+
* out `{ sessionId, stream }` — an **event stream**, not a body (below)
|
|
24
|
+
* • `StopCodeInterpreterSessionCommand`
|
|
25
|
+
* in `{ codeInterpreterIdentifier, sessionId }` ← the session ID, not a URI
|
|
26
|
+
* out `{ codeInterpreterIdentifier, sessionId, lastUpdatedAt }`
|
|
27
|
+
*
|
|
28
|
+
* **`Invoke` answers with an `AsyncIterable`.** `InvokeCodeInterpreterResponse`
|
|
29
|
+
* is `{ sessionId?, stream?: AsyncIterable<CodeInterpreterStreamOutput> }`, and
|
|
30
|
+
* every member of that union is either `{ result }` or a modelled EXCEPTION —
|
|
31
|
+
* `accessDeniedException`, `throttlingException`, `validationException`, and
|
|
32
|
+
* four more. An adapter that read a `body` field would find `undefined` and
|
|
33
|
+
* report an empty success on every call, and one that iterated only for
|
|
34
|
+
* `result` would treat an AccessDenied as "the code printed nothing." So this
|
|
35
|
+
* drains the stream, raises the exception members BY NAME, and folds the
|
|
36
|
+
* result members into one `CodeResult`.
|
|
37
|
+
*
|
|
38
|
+
* The payload lands twice: `structuredContent` carries `{ stdout, stderr,
|
|
39
|
+
* exitCode, executionTime }` and `content[]` carries typed blocks. The
|
|
40
|
+
* structured half is preferred because it is typed; the text blocks are the
|
|
41
|
+
* fallback for a response that only filled the other one.
|
|
42
|
+
*
|
|
43
|
+
* ── How this talks to the SDK (the 9.4.0 law) ───────────────────────────────
|
|
44
|
+
* Through `client.send(new SomeCommand(input))`, never a method on the client.
|
|
45
|
+
* A bare `@aws-sdk/client-*` **Client is command-based**: its prototype carries
|
|
46
|
+
* `send` and `destroy` and nothing else — verified again for this adapter
|
|
47
|
+
* (`BedrockAgentCoreClient.prototype.startCodeInterpreterSession` is
|
|
48
|
+
* `undefined`; the shortcut exists only on the aggregated `BedrockAgentCore`).
|
|
49
|
+
* Three adapters have shipped that bug in this package.
|
|
50
|
+
*
|
|
51
|
+
* ── Credentials ─────────────────────────────────────────────────────────────
|
|
52
|
+
* Standard AWS credential resolution (the SDK's own chain). A tool holding a
|
|
53
|
+
* long-lived session must NOT cache a `Credential` object from `ctx.credential`
|
|
54
|
+
* past the call that produced it — a session outliving a run outlives its
|
|
55
|
+
* token. Re-resolve per execute through `ctx.credentials`.
|
|
56
|
+
*
|
|
57
|
+
* Pattern: Adapter (GoF) + lazy peer-dep load — the SDK is required only when
|
|
58
|
+
* `start()` first runs (or never, if you inject `_client` / `_sdk`).
|
|
59
|
+
*/
|
|
60
|
+
import { lazyRequire } from '../../lib/lazyRequire.js';
|
|
61
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 8_000;
|
|
62
|
+
/** The service's own name for "run this code". Not a name we chose. */
|
|
63
|
+
const EXECUTE_CODE = 'executeCode';
|
|
64
|
+
export function agentCoreCodeRunner(options) {
|
|
65
|
+
const id = options.id ?? 'agentcore-code-runner';
|
|
66
|
+
const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
67
|
+
let client;
|
|
68
|
+
const resolve = () => {
|
|
69
|
+
client ??= options._client ?? createCodeClient(options);
|
|
70
|
+
return client;
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
id,
|
|
74
|
+
async start(req) {
|
|
75
|
+
const api = resolve();
|
|
76
|
+
const started = await api.startSession({
|
|
77
|
+
codeInterpreterIdentifier: options.identifier,
|
|
78
|
+
// The isolation key names the session on AWS's side too, so an operator
|
|
79
|
+
// reading the console sees the same partition the runtime enforces. It
|
|
80
|
+
// is a HASH-free label AWS documents as non-unique; the key is already
|
|
81
|
+
// the caller's own composition, never widened here.
|
|
82
|
+
name: sessionName(req.key),
|
|
83
|
+
...(options.sessionTimeoutSeconds !== undefined && {
|
|
84
|
+
sessionTimeoutSeconds: options.sessionTimeoutSeconds,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
const sessionId = started.sessionId;
|
|
88
|
+
if (!sessionId) {
|
|
89
|
+
throw new Error('agentCoreCodeRunner: StartCodeInterpreterSession returned no sessionId. ' +
|
|
90
|
+
'Nothing can be invoked or stopped without one, so this is raised rather than ' +
|
|
91
|
+
'carried forward as an unusable session.');
|
|
92
|
+
}
|
|
93
|
+
let stopped = false;
|
|
94
|
+
return {
|
|
95
|
+
id: sessionId,
|
|
96
|
+
async execute(exec) {
|
|
97
|
+
if (stopped) {
|
|
98
|
+
throw new Error(`agentCoreCodeRunner: session ${sessionId} was stopped; open a new one to run more code.`);
|
|
99
|
+
}
|
|
100
|
+
const answer = await api.invoke({
|
|
101
|
+
codeInterpreterIdentifier: options.identifier,
|
|
102
|
+
sessionId,
|
|
103
|
+
name: EXECUTE_CODE,
|
|
104
|
+
arguments: {
|
|
105
|
+
code: exec.code,
|
|
106
|
+
...(exec.language ?? req.language ?? options.language
|
|
107
|
+
? { language: exec.language ?? req.language ?? options.language ?? 'python' }
|
|
108
|
+
: {}),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const stdout = cut(answer.stdout, maxOutputChars);
|
|
112
|
+
const stderr = cut(answer.stderr, maxOutputChars);
|
|
113
|
+
const truncated = {
|
|
114
|
+
...(stdout.cut && { stdout: true }),
|
|
115
|
+
...(stderr.cut && { stderr: true }),
|
|
116
|
+
...((stdout.cut || stderr.cut) && {
|
|
117
|
+
ofChars: Math.max(answer.stdout.length, answer.stderr.length),
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
// `isError` is the service's own verdict; a non-zero exit is ours.
|
|
122
|
+
ok: answer.isError !== true && (answer.exitCode ?? 0) === 0,
|
|
123
|
+
stdout: stdout.text,
|
|
124
|
+
stderr: stderr.text,
|
|
125
|
+
...(answer.exitCode !== undefined && { exitCode: answer.exitCode }),
|
|
126
|
+
...(Object.keys(truncated).length > 0 && { truncated }),
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
async stop() {
|
|
130
|
+
if (stopped)
|
|
131
|
+
return;
|
|
132
|
+
stopped = true;
|
|
133
|
+
try {
|
|
134
|
+
await api.stopSession({
|
|
135
|
+
codeInterpreterIdentifier: options.identifier,
|
|
136
|
+
sessionId,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
// "Already gone" is the EXPECTED shape here, not an incident: the
|
|
141
|
+
// session TTL is enforced by AWS, so a run that idled past it finds
|
|
142
|
+
// the session collected. Anything else is re-raised, and the tier
|
|
143
|
+
// reports it on `tools.session_close_failed`.
|
|
144
|
+
if (!isAlreadyGone(err))
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** A session label from the isolation key — AWS's `name` is a human handle. */
|
|
153
|
+
function sessionName(key) {
|
|
154
|
+
// Keep it recognisable and within the service's naming charset.
|
|
155
|
+
return `af-${key.replace(/[^A-Za-z0-9_-]/g, '-')}`.slice(0, 100);
|
|
156
|
+
}
|
|
157
|
+
function isAlreadyGone(err) {
|
|
158
|
+
const name = err instanceof Error ? err.name : '';
|
|
159
|
+
return name === 'ResourceNotFoundException' || name === 'ValidationException';
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Map {@link AgentCoreCodeClientLike} onto the real SDK commands.
|
|
163
|
+
*
|
|
164
|
+
* Two things this function owns and nothing else does: the COMMAND FORM (see
|
|
165
|
+
* the module header), and the DRAIN of `InvokeCodeInterpreterResponse.stream`.
|
|
166
|
+
*/
|
|
167
|
+
function createCodeClient(options) {
|
|
168
|
+
let mod;
|
|
169
|
+
if (options._sdk) {
|
|
170
|
+
mod = options._sdk;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
try {
|
|
174
|
+
// Lazy peer-dep: only loaded when no _client/_sdk is injected and start() runs.
|
|
175
|
+
mod = lazyRequire('@aws-sdk/client-bedrock-agentcore');
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
throw new Error('agentCoreCodeRunner requires the `@aws-sdk/client-bedrock-agentcore` peer dependency.\n' +
|
|
179
|
+
' Install: npm install @aws-sdk/client-bedrock-agentcore\n' +
|
|
180
|
+
' Or pass `_client` for a pre-built or mock client.\n' +
|
|
181
|
+
' For a local dev loop with no AWS at all, use `localCodeRunner()`.');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!mod.BedrockAgentCoreClient) {
|
|
185
|
+
throw new Error('agentCoreCodeRunner: `@aws-sdk/client-bedrock-agentcore` is installed but ' +
|
|
186
|
+
'`BedrockAgentCoreClient` was not found. Update the SDK.');
|
|
187
|
+
}
|
|
188
|
+
const sdk = new mod.BedrockAgentCoreClient({ ...(options.region && { region: options.region }) });
|
|
189
|
+
const send = async (Ctor, name, input) => {
|
|
190
|
+
if (!Ctor) {
|
|
191
|
+
throw new Error(`agentCoreCodeRunner: \`@aws-sdk/client-bedrock-agentcore\` is missing ${name}. ` +
|
|
192
|
+
'Upgrade the SDK, or pass `_client` with your own mapping.');
|
|
193
|
+
}
|
|
194
|
+
return sdk.send(new Ctor(input));
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
async startSession(input) {
|
|
198
|
+
const r = (await send(mod.StartCodeInterpreterSessionCommand, 'StartCodeInterpreterSessionCommand', input));
|
|
199
|
+
return { ...(r?.sessionId !== undefined && { sessionId: r.sessionId }) };
|
|
200
|
+
},
|
|
201
|
+
async invoke(input) {
|
|
202
|
+
const r = (await send(mod.InvokeCodeInterpreterCommand, 'InvokeCodeInterpreterCommand', input));
|
|
203
|
+
return drainStream(r?.stream);
|
|
204
|
+
},
|
|
205
|
+
async stopSession(input) {
|
|
206
|
+
await send(mod.StopCodeInterpreterSessionCommand, 'StopCodeInterpreterSessionCommand', input);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** Every modelled error member of the stream union, in SDK spelling. */
|
|
211
|
+
const STREAM_ERROR_MEMBERS = [
|
|
212
|
+
'accessDeniedException',
|
|
213
|
+
'conflictException',
|
|
214
|
+
'internalServerException',
|
|
215
|
+
'resourceNotFoundException',
|
|
216
|
+
'serviceQuotaExceededException',
|
|
217
|
+
'throttlingException',
|
|
218
|
+
'validationException',
|
|
219
|
+
];
|
|
220
|
+
/**
|
|
221
|
+
* Drain the event stream into one answer.
|
|
222
|
+
*
|
|
223
|
+
* An ERROR MEMBER RAISES. This is the honesty line for this adapter: the SDK
|
|
224
|
+
* models seven failure shapes as ordinary members of the stream union, so an
|
|
225
|
+
* `AccessDeniedException` arrives as a value, not a rejection. Folding it in
|
|
226
|
+
* as empty output would report a clean run in which the code "printed
|
|
227
|
+
* nothing" — a silent success, at the exact moment an operator most needs the
|
|
228
|
+
* word "AccessDenied".
|
|
229
|
+
*/
|
|
230
|
+
async function drainStream(stream) {
|
|
231
|
+
if (!stream) {
|
|
232
|
+
throw new Error('agentCoreCodeRunner: InvokeCodeInterpreter returned no stream. The response carries ' +
|
|
233
|
+
'its payload as an event stream (`response.stream`), so an absent one is a broken ' +
|
|
234
|
+
'call, not an empty result.');
|
|
235
|
+
}
|
|
236
|
+
let stdout = '';
|
|
237
|
+
let stderr = '';
|
|
238
|
+
let exitCode;
|
|
239
|
+
let isError = false;
|
|
240
|
+
for await (const event of stream) {
|
|
241
|
+
for (const member of STREAM_ERROR_MEMBERS) {
|
|
242
|
+
const raised = event[member];
|
|
243
|
+
if (raised) {
|
|
244
|
+
const error = new Error(`agentCoreCodeRunner: ${member} from InvokeCodeInterpreter — ` +
|
|
245
|
+
`${raised.message ?? 'no message supplied'}`);
|
|
246
|
+
error.name = member.charAt(0).toUpperCase() + member.slice(1);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const result = event.result;
|
|
251
|
+
if (!result)
|
|
252
|
+
continue;
|
|
253
|
+
if (result.isError === true)
|
|
254
|
+
isError = true;
|
|
255
|
+
const structured = result.structuredContent;
|
|
256
|
+
if (structured) {
|
|
257
|
+
if (structured.stdout !== undefined)
|
|
258
|
+
stdout += structured.stdout;
|
|
259
|
+
if (structured.stderr !== undefined)
|
|
260
|
+
stderr += structured.stderr;
|
|
261
|
+
if (structured.exitCode !== undefined)
|
|
262
|
+
exitCode = structured.exitCode;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// Fallback: a response that filled only the typed content blocks. Text
|
|
266
|
+
// blocks are the readable half; a binary/resource block is described by
|
|
267
|
+
// name rather than inlined, because inlining it is the bug this port exists
|
|
268
|
+
// to prevent.
|
|
269
|
+
for (const block of result.content ?? []) {
|
|
270
|
+
if (block.text !== undefined)
|
|
271
|
+
stdout += block.text;
|
|
272
|
+
else if (block.name !== undefined)
|
|
273
|
+
stdout += `[${block.type ?? 'resource'}: ${block.name}]`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
stdout,
|
|
279
|
+
stderr,
|
|
280
|
+
...(exitCode !== undefined && { exitCode }),
|
|
281
|
+
...(isError && { isError }),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/** Cut to `max` characters, saying whether it cut. */
|
|
285
|
+
function cut(text, max) {
|
|
286
|
+
if (text.length <= max)
|
|
287
|
+
return { text, cut: false };
|
|
288
|
+
return { text: text.slice(0, max), cut: true };
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=agentcore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentcore.js","sourceRoot":"","sources":["../../../../src/adapters/code/agentcore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localCodeRunner — run code in a child process, on this machine.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Adapter (GoF) over `node:child_process`.
|
|
5
|
+
* Role: the dev default behind the {@link CodeRunner} port. `agentCoreCodeRunner`
|
|
6
|
+
* is the production swap; the tool code does not change.
|
|
7
|
+
* Emits: nothing. The tool that owns the session emits.
|
|
8
|
+
*
|
|
9
|
+
* ── ISOLATION, NOT A SANDBOX. Read this before deploying it. ────────────────
|
|
10
|
+
* The name is `localCodeRunner`, never `sandboxedCodeRunner`, and the
|
|
11
|
+
* difference is not modesty — it is the whole security posture.
|
|
12
|
+
*
|
|
13
|
+
* What a child process DOES give you:
|
|
14
|
+
* • a separate process and heap — a crash or an OOM takes the child, not you;
|
|
15
|
+
* • kill on timeout, so runaway code has a ceiling;
|
|
16
|
+
* • no inherited stdin, so nothing can block waiting for a terminal;
|
|
17
|
+
* • an environment ALLOWLIST — `process.env` is not inherited (only `PATH`,
|
|
18
|
+
* so the interpreter can be found), so an `AWS_SECRET_ACCESS_KEY` in your
|
|
19
|
+
* shell is not in the model's reach;
|
|
20
|
+
* • a working directory, by convention.
|
|
21
|
+
*
|
|
22
|
+
* What it does NOT give you, at all:
|
|
23
|
+
* • a filesystem jail — the code can read and write anywhere this process can;
|
|
24
|
+
* • a network jail — it can call out;
|
|
25
|
+
* • CPU or memory limits beyond the timeout;
|
|
26
|
+
* • any protection from code that deletes something outside `cwd`.
|
|
27
|
+
*
|
|
28
|
+
* So: a development loop, a trusted-input pipeline, a machine you would be
|
|
29
|
+
* relaxed about a shell script running on. NOT arbitrary model-written code
|
|
30
|
+
* from an untrusted user, on a host with anything on it. For that, run a real
|
|
31
|
+
* sandbox behind the same port — `agentCoreCodeRunner`, a gVisor container, a
|
|
32
|
+
* Firecracker VM — and keep the tool identical.
|
|
33
|
+
*
|
|
34
|
+
* **In-process `eval` / `node:vm` is refused outright.** Node's own
|
|
35
|
+
* documentation says `vm` is not a security mechanism ("do not use it to run
|
|
36
|
+
* untrusted code"), so shipping it as one would be theater: the same code
|
|
37
|
+
* reaching the same globals, wearing a word that makes a reader stop checking.
|
|
38
|
+
* A subprocess is genuinely a boundary; it is just a smaller one than the word
|
|
39
|
+
* "sandbox" implies. The library teaches refusals for things that are WRONG and
|
|
40
|
+
* honest names for things that are merely LIMITED — running local code in a dev
|
|
41
|
+
* loop is not wrong, and calling it a sandbox is.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const agent = Agent.create({ provider })
|
|
45
|
+
* .tool(codeRunnerTool({ runner: localCodeRunner() }))
|
|
46
|
+
* .build();
|
|
47
|
+
*/
|
|
48
|
+
import type { CodeRunner } from '../types.js';
|
|
49
|
+
export interface LocalCodeRunnerOptions {
|
|
50
|
+
/**
|
|
51
|
+
* How to run a snippet, as `[command, ...args]` — the code is appended as the
|
|
52
|
+
* final argument. Defaults by language: `['node', '-e']` for javascript,
|
|
53
|
+
* `['python3', '-c']` for python.
|
|
54
|
+
*/
|
|
55
|
+
readonly command?: readonly string[];
|
|
56
|
+
/** Working directory for the child. Defaults to `process.cwd()`. */
|
|
57
|
+
readonly cwd?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The child's environment. An ALLOWLIST, never a merge: `process.env` is NOT
|
|
60
|
+
* inherited, so a credential sitting in this process's environment is not
|
|
61
|
+
* handed to model-written code by default. Pass exactly what the code needs.
|
|
62
|
+
*
|
|
63
|
+
* ONE exception, stated because an unstated one is a hole: `PATH` is passed
|
|
64
|
+
* through, because without it the operating system cannot find the
|
|
65
|
+
* interpreter and nothing runs at all. `PATH` names directories, not secrets.
|
|
66
|
+
* Set `env: { PATH: '/usr/bin' }` to narrow it, or `env: { PATH: '' }` to
|
|
67
|
+
* refuse even that — anything you supply here WINS over the inherited value.
|
|
68
|
+
*/
|
|
69
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
70
|
+
/** Per-execution ceiling; the child is killed past it. Default 30s. */
|
|
71
|
+
readonly timeoutMs?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Per-stream output ceiling, in characters. Default 8000.
|
|
74
|
+
*
|
|
75
|
+
* Cutting is allowed; cutting SILENTLY is not — anything cut is reported on
|
|
76
|
+
* `CodeResult.truncated`, and `codeRunnerTool` renders that as a visible
|
|
77
|
+
* marker in the tool result. The model has to know the table it is about to
|
|
78
|
+
* reason over is a fragment.
|
|
79
|
+
*/
|
|
80
|
+
readonly maxOutputChars?: number;
|
|
81
|
+
/** Stable id (default `'local-code-runner'`). Rides the session events. */
|
|
82
|
+
readonly id?: string;
|
|
83
|
+
/** @internal Test seam — the `node:child_process` module. */
|
|
84
|
+
readonly _childProcess?: ChildProcessModuleLike;
|
|
85
|
+
}
|
|
86
|
+
/** The slice of `node:child_process` this adapter touches. */
|
|
87
|
+
export interface ChildProcessModuleLike {
|
|
88
|
+
readonly execFile?: (file: string, args: readonly string[], options: {
|
|
89
|
+
cwd?: string;
|
|
90
|
+
env?: Record<string, string>;
|
|
91
|
+
timeout?: number;
|
|
92
|
+
maxBuffer?: number;
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
}, callback: (error: (Error & {
|
|
95
|
+
code?: number | string;
|
|
96
|
+
killed?: boolean;
|
|
97
|
+
}) | null, stdout: string, stderr: string) => void) => unknown;
|
|
98
|
+
}
|
|
99
|
+
export declare function localCodeRunner(options?: LocalCodeRunnerOptions): CodeRunner;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* localCodeRunner — run code in a child process, on this machine.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Adapter (GoF) over `node:child_process`.
|
|
5
|
+
* Role: the dev default behind the {@link CodeRunner} port. `agentCoreCodeRunner`
|
|
6
|
+
* is the production swap; the tool code does not change.
|
|
7
|
+
* Emits: nothing. The tool that owns the session emits.
|
|
8
|
+
*
|
|
9
|
+
* ── ISOLATION, NOT A SANDBOX. Read this before deploying it. ────────────────
|
|
10
|
+
* The name is `localCodeRunner`, never `sandboxedCodeRunner`, and the
|
|
11
|
+
* difference is not modesty — it is the whole security posture.
|
|
12
|
+
*
|
|
13
|
+
* What a child process DOES give you:
|
|
14
|
+
* • a separate process and heap — a crash or an OOM takes the child, not you;
|
|
15
|
+
* • kill on timeout, so runaway code has a ceiling;
|
|
16
|
+
* • no inherited stdin, so nothing can block waiting for a terminal;
|
|
17
|
+
* • an environment ALLOWLIST — `process.env` is not inherited (only `PATH`,
|
|
18
|
+
* so the interpreter can be found), so an `AWS_SECRET_ACCESS_KEY` in your
|
|
19
|
+
* shell is not in the model's reach;
|
|
20
|
+
* • a working directory, by convention.
|
|
21
|
+
*
|
|
22
|
+
* What it does NOT give you, at all:
|
|
23
|
+
* • a filesystem jail — the code can read and write anywhere this process can;
|
|
24
|
+
* • a network jail — it can call out;
|
|
25
|
+
* • CPU or memory limits beyond the timeout;
|
|
26
|
+
* • any protection from code that deletes something outside `cwd`.
|
|
27
|
+
*
|
|
28
|
+
* So: a development loop, a trusted-input pipeline, a machine you would be
|
|
29
|
+
* relaxed about a shell script running on. NOT arbitrary model-written code
|
|
30
|
+
* from an untrusted user, on a host with anything on it. For that, run a real
|
|
31
|
+
* sandbox behind the same port — `agentCoreCodeRunner`, a gVisor container, a
|
|
32
|
+
* Firecracker VM — and keep the tool identical.
|
|
33
|
+
*
|
|
34
|
+
* **In-process `eval` / `node:vm` is refused outright.** Node's own
|
|
35
|
+
* documentation says `vm` is not a security mechanism ("do not use it to run
|
|
36
|
+
* untrusted code"), so shipping it as one would be theater: the same code
|
|
37
|
+
* reaching the same globals, wearing a word that makes a reader stop checking.
|
|
38
|
+
* A subprocess is genuinely a boundary; it is just a smaller one than the word
|
|
39
|
+
* "sandbox" implies. The library teaches refusals for things that are WRONG and
|
|
40
|
+
* honest names for things that are merely LIMITED — running local code in a dev
|
|
41
|
+
* loop is not wrong, and calling it a sandbox is.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const agent = Agent.create({ provider })
|
|
45
|
+
* .tool(codeRunnerTool({ runner: localCodeRunner() }))
|
|
46
|
+
* .build();
|
|
47
|
+
*/
|
|
48
|
+
import { lazyRequire } from '../../lib/lazyRequire.js';
|
|
49
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
50
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 8_000;
|
|
51
|
+
/** How each supported language is invoked when no `command` was given. */
|
|
52
|
+
const DEFAULT_COMMANDS = {
|
|
53
|
+
javascript: ['node', '-e'],
|
|
54
|
+
js: ['node', '-e'],
|
|
55
|
+
node: ['node', '-e'],
|
|
56
|
+
typescript: ['npx', 'tsx', '-e'],
|
|
57
|
+
ts: ['npx', 'tsx', '-e'],
|
|
58
|
+
python: ['python3', '-c'],
|
|
59
|
+
python3: ['python3', '-c'],
|
|
60
|
+
py: ['python3', '-c'],
|
|
61
|
+
};
|
|
62
|
+
export function localCodeRunner(options = {}) {
|
|
63
|
+
const id = options.id ?? 'local-code-runner';
|
|
64
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
65
|
+
const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
66
|
+
return {
|
|
67
|
+
id,
|
|
68
|
+
start(req) {
|
|
69
|
+
// A local session is a NAME and a working directory, not a live handle:
|
|
70
|
+
// each execution is its own process. Saying so matters — a consumer who
|
|
71
|
+
// assumed variables carry between calls (they do on AgentCore, whose
|
|
72
|
+
// session is a live kernel) would silently get different semantics.
|
|
73
|
+
// `codeRunnerTool` states this in the tool description it hands the model.
|
|
74
|
+
const sessionId = `local:${req.key}`;
|
|
75
|
+
let stopped = false;
|
|
76
|
+
return Promise.resolve({
|
|
77
|
+
id: sessionId,
|
|
78
|
+
async execute(exec) {
|
|
79
|
+
if (stopped) {
|
|
80
|
+
throw new Error(`localCodeRunner: session ${sessionId} was stopped; open a new one to run more code.`);
|
|
81
|
+
}
|
|
82
|
+
const language = exec.language ?? req.language ?? 'javascript';
|
|
83
|
+
const command = options.command ?? DEFAULT_COMMANDS[language.toLowerCase()];
|
|
84
|
+
if (!command || command.length === 0) {
|
|
85
|
+
throw new Error(`localCodeRunner: no command for language '${language}'. ` +
|
|
86
|
+
`Known: ${Object.keys(DEFAULT_COMMANDS).join(', ')}. ` +
|
|
87
|
+
"Pass `command: ['your-interpreter', '-c']` for anything else.");
|
|
88
|
+
}
|
|
89
|
+
const [file, ...args] = command;
|
|
90
|
+
const raw = await runChild(resolveChildProcess(options), file, [...args, exec.code], {
|
|
91
|
+
cwd: options.cwd ?? process.cwd(),
|
|
92
|
+
// ALLOWLIST. `process.env` is deliberately NOT spread in — only
|
|
93
|
+
// PATH, so the OS can find the interpreter, and it is overridable.
|
|
94
|
+
env: { PATH: process.env.PATH ?? '', ...(options.env ?? {}) },
|
|
95
|
+
timeoutMs: exec.timeoutMs ?? timeoutMs,
|
|
96
|
+
...(exec.signal && { signal: exec.signal }),
|
|
97
|
+
});
|
|
98
|
+
const stdout = cut(raw.stdout, maxOutputChars);
|
|
99
|
+
const stderr = cut(raw.stderr, maxOutputChars);
|
|
100
|
+
const truncated = {
|
|
101
|
+
...(stdout.cut && { stdout: true }),
|
|
102
|
+
...(stderr.cut && { stderr: true }),
|
|
103
|
+
...((stdout.cut || stderr.cut) && {
|
|
104
|
+
ofChars: Math.max(raw.stdout.length, raw.stderr.length),
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
ok: raw.exitCode === 0,
|
|
109
|
+
stdout: stdout.text,
|
|
110
|
+
stderr: stderr.text,
|
|
111
|
+
...(raw.exitCode !== undefined && { exitCode: raw.exitCode }),
|
|
112
|
+
// Present IFF something was actually cut — an empty object here
|
|
113
|
+
// would read as "truncation happened, magnitude unknown".
|
|
114
|
+
...(Object.keys(truncated).length > 0 && { truncated }),
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
stop() {
|
|
118
|
+
// Nothing to release: each execution already ended with its process.
|
|
119
|
+
// Idempotent, and it refuses later executions rather than silently
|
|
120
|
+
// starting a fresh process under a session the caller closed.
|
|
121
|
+
stopped = true;
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function resolveChildProcess(options) {
|
|
129
|
+
if (options._childProcess)
|
|
130
|
+
return options._childProcess;
|
|
131
|
+
try {
|
|
132
|
+
return lazyRequire('node:child_process');
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new Error('localCodeRunner needs `node:child_process`, which this runtime does not provide ' +
|
|
136
|
+
'(a browser bundle, or a restricted worker). Use a remote runner — ' +
|
|
137
|
+
'`agentCoreCodeRunner` — or supply your own `CodeRunner`.');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* One child process, promisified.
|
|
142
|
+
*
|
|
143
|
+
* A NON-ZERO EXIT IS NOT A REJECTION. Code that fails is a normal answer here —
|
|
144
|
+
* the model wrote it, it needs to read the traceback, and turning that into a
|
|
145
|
+
* thrown tool error would replace the one thing that teaches the next attempt
|
|
146
|
+
* with `error: true`. What DOES reject is the runner failing to run at all
|
|
147
|
+
* (interpreter missing, timeout, abort), because that is not the code's answer.
|
|
148
|
+
*/
|
|
149
|
+
async function runChild(mod, file, args, opts) {
|
|
150
|
+
const execFile = mod.execFile;
|
|
151
|
+
if (typeof execFile !== 'function') {
|
|
152
|
+
throw new Error('localCodeRunner: `node:child_process` resolved but `execFile` is missing.');
|
|
153
|
+
}
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
execFile(file, args, {
|
|
156
|
+
cwd: opts.cwd,
|
|
157
|
+
env: opts.env,
|
|
158
|
+
timeout: opts.timeoutMs,
|
|
159
|
+
// Generous, because the CUT is ours to make and to report — letting the
|
|
160
|
+
// runtime's own buffer overflow would surface as an error whose message
|
|
161
|
+
// says nothing about how much was lost.
|
|
162
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
163
|
+
...(opts.signal && { signal: opts.signal }),
|
|
164
|
+
}, (error, stdout, stderr) => {
|
|
165
|
+
if (!error) {
|
|
166
|
+
resolve({ stdout: String(stdout ?? ''), stderr: String(stderr ?? ''), exitCode: 0 });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (error.killed === true) {
|
|
170
|
+
reject(new Error(`localCodeRunner: the code did not finish within ${opts.timeoutMs}ms and the ` +
|
|
171
|
+
'process was killed. Raise `timeoutMs`, or have the code do less per call.'));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (typeof error.code === 'string') {
|
|
175
|
+
// ENOENT and friends — the interpreter itself is not here. Naming it
|
|
176
|
+
// beats handing the model a traceback it cannot act on.
|
|
177
|
+
reject(new Error(`localCodeRunner: could not run '${file}' (${error.code}). ` +
|
|
178
|
+
'Install the interpreter, or pass `command` pointing at one.'));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// A real non-zero exit: the code ran and failed. That IS the result.
|
|
182
|
+
resolve({
|
|
183
|
+
stdout: String(stdout ?? ''),
|
|
184
|
+
stderr: String(stderr ?? '') || error.message,
|
|
185
|
+
exitCode: typeof error.code === 'number' ? error.code : 1,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/** Cut to `max` characters, saying whether it cut. */
|
|
191
|
+
function cut(text, max) {
|
|
192
|
+
if (text.length <= max)
|
|
193
|
+
return { text, cut: false };
|
|
194
|
+
return { text: text.slice(0, max), cut: true };
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../../../src/adapters/code/local.ts"],"names":[],"mappings":"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"}
|