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,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* toolSessions — the end-signal a tool can be handed, and the tier that fires it.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Registrar + Template Method for the firing matrix.
|
|
6
|
+
* Role: core primitive. `ToolExecutionContext.onTeardown` registers here;
|
|
7
|
+
* `Agent.run`'s terminals, `RunnerBase.closeToolSessions` and
|
|
8
|
+
* `RunnerBase.shutdown` fire it.
|
|
9
|
+
* Emits: nothing itself. `register()` ANSWERS what it did (started/reused) so
|
|
10
|
+
* the caller — still inside a stage — can emit with a real
|
|
11
|
+
* `runtimeStageId`, and teardown REPORTS (see {@link ToolSessionReport})
|
|
12
|
+
* so the runner can emit the two that fire after the last stage.
|
|
13
|
+
*
|
|
14
|
+
* ── The gap this closes ─────────────────────────────────────────────────────
|
|
15
|
+
* A session-based tool service (a managed code interpreter, a headless browser)
|
|
16
|
+
* is Start → Invoke ×N → Stop. Before 9.7.0 a `Tool` had nowhere to hold the
|
|
17
|
+
* middle of that: `ToolExecutionContext` carried no run or session identity, and
|
|
18
|
+
* nothing in the framework ever said "this is over". So a tool either paid
|
|
19
|
+
* start-up on every call, or held the session in a module-level map — which, in
|
|
20
|
+
* a standing agent serving many people from one process, hands ONE live sandbox
|
|
21
|
+
* to whoever calls next. That is not a leak of memory; it is a leak of a
|
|
22
|
+
* filesystem, an environment and half-run state, across users.
|
|
23
|
+
*
|
|
24
|
+
* ── Why the registration lives on the CONTEXT ───────────────────────────────
|
|
25
|
+
* Not `Tool.dispose()`: a `Tool` is a singleton, built once and shared by every
|
|
26
|
+
* run and every session, so "dispose the tool" cannot mean "dispose this
|
|
27
|
+
* caller's session".
|
|
28
|
+
* Not a lifecycle PORT the consumer wires: that makes the common case (one tool
|
|
29
|
+
* that happens to hold a session) a wiring exercise, and the tool that knows the
|
|
30
|
+
* key is the one that cannot reach the port.
|
|
31
|
+
* `ctx.onTeardown(cleanup, { scope, key })` is the only seam where the key and
|
|
32
|
+
* the resource are both in hand at the same instant.
|
|
33
|
+
*
|
|
34
|
+
* ── The seven laws ──────────────────────────────────────────────────────────
|
|
35
|
+
* 1. **At most once, ever** per registration — the `stopped`-flag mechanism
|
|
36
|
+
* `strategies/lifecycle.ts` uses for strategies, applied one layer down.
|
|
37
|
+
* 2. **Idempotent by `(tool, scope, key)`, first wins.** A second registration
|
|
38
|
+
* under the same key does not replace the first — the first is the one
|
|
39
|
+
* holding the live handle; replacing it would drop that handle on the
|
|
40
|
+
* floor. The repeat is a TOUCH: it refreshes liveness (that is how the
|
|
41
|
+
* idle sweep and the LRU learn a session is still in use) and answers
|
|
42
|
+
* `'reused'`.
|
|
43
|
+
* 3. **Reverse order of registration**, settled with `Promise.allSettled`.
|
|
44
|
+
* 4. **Bounded** by `timeoutMs` (default 5s). Teardown is on the SIGTERM path;
|
|
45
|
+
* an unbounded `stop()` turns a container stop into a wait for SIGKILL.
|
|
46
|
+
* 5. **Never throws into the run** — but **never silent** either. Every
|
|
47
|
+
* failure, and every timeout, is reported. "Swallowed AND silent" is what
|
|
48
|
+
* separates a passive recorder from a resource that did not get released.
|
|
49
|
+
* 6. **Tolerates "already gone."** The far side reaps idle sessions on its own
|
|
50
|
+
* schedule; a `Stop` on a session AWS already collected is a no-op, not an
|
|
51
|
+
* incident. It still reports, so the difference is visible.
|
|
52
|
+
* 7. **Nothing live is ever persisted.** A checkpoint carries what survives
|
|
53
|
+
* `structuredClone`; a session handle does not. A resumed run re-opens.
|
|
54
|
+
*
|
|
55
|
+
* Zero-cost when unused: no tool registers → the runner never builds a tier →
|
|
56
|
+
* the terminal path is one `undefined` check.
|
|
57
|
+
*/
|
|
58
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
|
+
exports.ToolTeardownTimeoutError = exports.ToolSessionTier = exports.TOOL_SESSION_MAX_LIVE = exports.TOOL_SESSION_IDLE_MS = exports.TOOL_TEARDOWN_TIMEOUT_MS = exports.hashSessionKey = exports.toolSessionKey = void 0;
|
|
60
|
+
const fnv1a_js_1 = require("../lib/fnv1a.js");
|
|
61
|
+
const lazyRequire_js_1 = require("../lib/lazyRequire.js");
|
|
62
|
+
/**
|
|
63
|
+
* Derive the isolation key a tool should hold a session under.
|
|
64
|
+
*
|
|
65
|
+
* ONE implementation, exported, because the derivation is the security
|
|
66
|
+
* boundary. Returns `undefined` when the facts the scope needs are absent —
|
|
67
|
+
* which is a refusal to guess, not a failure: the caller decides whether to
|
|
68
|
+
* narrow the scope loudly or refuse the call.
|
|
69
|
+
*
|
|
70
|
+
* ```
|
|
71
|
+
* session → t=<tenant|_>/p=<principal|_>/s=<sessionId> requires sessionId
|
|
72
|
+
* run → t=<tenant|_>/p=<principal|_>/r=<runId> requires runId
|
|
73
|
+
* call → c=<toolCallId> always available
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* **`sessionId` alone must never key a live session.** The hosting port says
|
|
77
|
+
* why in its own words: a `sessionId` "is not identity and must never be
|
|
78
|
+
* trusted as identity on its own: anyone who can reach the host can put any
|
|
79
|
+
* string here, including someone else's." A code interpreter keyed on
|
|
80
|
+
* `sessionId` alone hands a live sandbox — files, environment, half-run state —
|
|
81
|
+
* to anyone who guesses one. Tenant and principal are in the key whenever they
|
|
82
|
+
* exist; a deployment that has no principal is thereby STATING it is
|
|
83
|
+
* single-principal rather than quietly assuming it.
|
|
84
|
+
*
|
|
85
|
+
* `'shutdown'` is not a key scope: it is when everything goes, not a thing to
|
|
86
|
+
* hold one session under. Ask for it and you get `undefined`.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const key = toolSessionKey(ctx, 'run');
|
|
90
|
+
* if (!key) throw new Error("run_code: scope 'run' needs a run …");
|
|
91
|
+
*/
|
|
92
|
+
function toolSessionKey(ctx, scope) {
|
|
93
|
+
if (scope === 'call')
|
|
94
|
+
return `c=${ctx.toolCallId}`;
|
|
95
|
+
if (scope === 'shutdown')
|
|
96
|
+
return undefined;
|
|
97
|
+
const tenant = ctx.identity?.tenant || '_';
|
|
98
|
+
const principal = ctx.identity?.principal || '_';
|
|
99
|
+
const prefix = `t=${tenant}/p=${principal}`;
|
|
100
|
+
if (scope === 'session') {
|
|
101
|
+
return ctx.sessionId ? `${prefix}/s=${ctx.sessionId}` : undefined;
|
|
102
|
+
}
|
|
103
|
+
return ctx.runId ? `${prefix}/r=${ctx.runId}` : undefined;
|
|
104
|
+
}
|
|
105
|
+
exports.toolSessionKey = toolSessionKey;
|
|
106
|
+
/**
|
|
107
|
+
* A short, stable digest of an isolation key.
|
|
108
|
+
*
|
|
109
|
+
* The key carries tenant, principal and the hosting `sessionId`. Publishing it
|
|
110
|
+
* on the event wire would put a user identifier into every exporter's payload —
|
|
111
|
+
* so the wire carries this instead, which is enough to JOIN two rows and not
|
|
112
|
+
* enough to name whose they are. `meta.sessionId` already carries the session
|
|
113
|
+
* legitimately (9.4.0); the payload does not repeat it.
|
|
114
|
+
*
|
|
115
|
+
* SHA-256, first 12 hex chars, wherever `node:crypto` resolves. In a browser
|
|
116
|
+
* bundle, where it does not, this falls back to the package's non-cryptographic
|
|
117
|
+
* FNV-1a digest — stated here rather than implied, because a fallback nobody
|
|
118
|
+
* documented is how "hashed" comes to mean less than a reader assumed.
|
|
119
|
+
*/
|
|
120
|
+
function hashSessionKey(key) {
|
|
121
|
+
const crypto = loadCrypto();
|
|
122
|
+
if (crypto)
|
|
123
|
+
return crypto.createHash('sha256').update(key).digest('hex').slice(0, 12);
|
|
124
|
+
return (0, fnv1a_js_1.fnv1a)(key);
|
|
125
|
+
}
|
|
126
|
+
exports.hashSessionKey = hashSessionKey;
|
|
127
|
+
let cryptoModule;
|
|
128
|
+
function loadCrypto() {
|
|
129
|
+
if (cryptoModule !== undefined)
|
|
130
|
+
return cryptoModule ?? undefined;
|
|
131
|
+
try {
|
|
132
|
+
const mod = (0, lazyRequire_js_1.lazyRequire)('node:crypto');
|
|
133
|
+
cryptoModule = typeof mod?.createHash === 'function' ? mod : null;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
cryptoModule = null;
|
|
137
|
+
}
|
|
138
|
+
return cryptoModule ?? undefined;
|
|
139
|
+
}
|
|
140
|
+
/** Defaults, named so a test and a docstring cannot drift from the code. */
|
|
141
|
+
exports.TOOL_TEARDOWN_TIMEOUT_MS = 5_000;
|
|
142
|
+
/** A session untouched this long is swept on the tier's next interaction. */
|
|
143
|
+
exports.TOOL_SESSION_IDLE_MS = 900_000;
|
|
144
|
+
/** How many live registrations one tier holds before it evicts the coldest. */
|
|
145
|
+
exports.TOOL_SESSION_MAX_LIVE = 64;
|
|
146
|
+
/**
|
|
147
|
+
* The teardown tier for one runner.
|
|
148
|
+
*
|
|
149
|
+
* Holds nothing until a tool registers, and the runner builds one only when
|
|
150
|
+
* that happens — so an agent whose tools hold no sessions pays a single
|
|
151
|
+
* `undefined` check at each terminal and nothing else.
|
|
152
|
+
*/
|
|
153
|
+
class ToolSessionTier {
|
|
154
|
+
registrations = new Map();
|
|
155
|
+
timeoutMs;
|
|
156
|
+
idleMs;
|
|
157
|
+
maxLive;
|
|
158
|
+
report;
|
|
159
|
+
now;
|
|
160
|
+
seq = 0;
|
|
161
|
+
constructor(options = {}) {
|
|
162
|
+
this.timeoutMs = options.timeoutMs ?? exports.TOOL_TEARDOWN_TIMEOUT_MS;
|
|
163
|
+
this.idleMs = options.idleMs ?? exports.TOOL_SESSION_IDLE_MS;
|
|
164
|
+
this.maxLive = options.maxLive ?? exports.TOOL_SESSION_MAX_LIVE;
|
|
165
|
+
this.report = options.report ?? (() => { });
|
|
166
|
+
this.now = options.now ?? (() => Date.now());
|
|
167
|
+
}
|
|
168
|
+
/** Live registrations. Diagnostics and tests. */
|
|
169
|
+
liveCount() {
|
|
170
|
+
return this.registrations.size;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve once every teardown this tier started IN THE BACKGROUND has
|
|
174
|
+
* settled.
|
|
175
|
+
*
|
|
176
|
+
* Two firings have no caller to await them: the idle sweep and the LRU
|
|
177
|
+
* eviction, both of which are triggered by a SYNCHRONOUS `register()` inside
|
|
178
|
+
* somebody else's `tool.execute`. They cannot be awaited there without making
|
|
179
|
+
* a cleanup's latency the tool's latency, so they run detached — and this is
|
|
180
|
+
* how a shutdown path, or a test, joins them.
|
|
181
|
+
*/
|
|
182
|
+
async settled() {
|
|
183
|
+
while (this.background.size > 0) {
|
|
184
|
+
await Promise.allSettled([...this.background]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Fires nobody is awaiting — see {@link settled}. */
|
|
188
|
+
background = new Set();
|
|
189
|
+
/** Start a detached fire and keep it joinable. */
|
|
190
|
+
detach(work) {
|
|
191
|
+
this.background.add(work);
|
|
192
|
+
void work.finally(() => this.background.delete(work));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Register a cleanup, or TOUCH the one already holding this key (law 2).
|
|
196
|
+
*
|
|
197
|
+
* Synchronous and total: it never throws, because it runs inside somebody
|
|
198
|
+
* else's `tool.execute`. Scope support is judged one layer up, where
|
|
199
|
+
* `teardownScopes` is known.
|
|
200
|
+
*
|
|
201
|
+
* @returns `'started'` for a new session, `'reused'` when this call joined
|
|
202
|
+
* one already held, with `calls` counting how many have now shared it. The
|
|
203
|
+
* CALLER announces it, because the caller is the one still inside a stage
|
|
204
|
+
* and able to stamp a real `runtimeStageId` — see {@link RegisterOutcome}.
|
|
205
|
+
*/
|
|
206
|
+
register(origin, cleanup, options = {}) {
|
|
207
|
+
this.sweepIdle();
|
|
208
|
+
const scope = options.scope ?? 'run';
|
|
209
|
+
const key = options.key ?? `${scope}:${origin.tool}`;
|
|
210
|
+
const mapKey = `${origin.tool}${scope}${key}`;
|
|
211
|
+
const existing = this.registrations.get(mapKey);
|
|
212
|
+
if (existing) {
|
|
213
|
+
// First wins — it is the one holding the live handle. The repeat is what
|
|
214
|
+
// tells the sweep and the LRU that this session is still being used.
|
|
215
|
+
existing.touchedMs = this.now();
|
|
216
|
+
existing.calls += 1;
|
|
217
|
+
return { outcome: 'reused', keyHash: existing.keyHash, calls: existing.calls };
|
|
218
|
+
}
|
|
219
|
+
const nowMs = this.now();
|
|
220
|
+
const registration = {
|
|
221
|
+
id: ++this.seq,
|
|
222
|
+
tool: origin.tool,
|
|
223
|
+
scope,
|
|
224
|
+
key,
|
|
225
|
+
keyHash: hashSessionKey(key),
|
|
226
|
+
origin,
|
|
227
|
+
cleanup,
|
|
228
|
+
...(options.runnerId !== undefined && { runnerId: options.runnerId }),
|
|
229
|
+
...(options.label !== undefined && { label: options.label }),
|
|
230
|
+
startedMs: nowMs,
|
|
231
|
+
touchedMs: nowMs,
|
|
232
|
+
calls: 1,
|
|
233
|
+
fired: false,
|
|
234
|
+
};
|
|
235
|
+
this.registrations.set(mapKey, registration);
|
|
236
|
+
this.evictOverflow();
|
|
237
|
+
return { outcome: 'started', keyHash: registration.keyHash, calls: 1 };
|
|
238
|
+
}
|
|
239
|
+
/** Fire every `'call'` registration this tool call opened. */
|
|
240
|
+
fireCall(toolCallId) {
|
|
241
|
+
return this.fire((r) => r.scope === 'call' && r.origin.toolCallId === toolCallId, 'call-end').then(() => undefined);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Fire `'run'`-scoped registrations at a run terminal.
|
|
245
|
+
*
|
|
246
|
+
* Called from `Agent.run`/`resume` at a terminal that is NOT a pause — and
|
|
247
|
+
* deliberately not from a `finally`, which also runs on the two pause shapes.
|
|
248
|
+
*
|
|
249
|
+
* **`runId` is optional, and the Agent omits it.** A `'run'` scope means
|
|
250
|
+
* "release this when the TURN that opened it ends", and a turn is not a
|
|
251
|
+
* runId: a pause and its resume are one turn across two runs (`resume()`
|
|
252
|
+
* builds a fresh executor with a fresh id). Filtering on the id would leave
|
|
253
|
+
* every session a paused turn had opened alive forever — the failure would
|
|
254
|
+
* look like nothing at all, because the run answered fine.
|
|
255
|
+
*
|
|
256
|
+
* Firing all of them is exactly right under the runner's own
|
|
257
|
+
* ONE-IN-FLIGHT-RUN-PER-AGENT invariant: at a terminal there is no other turn
|
|
258
|
+
* whose sessions these could be. An ABANDONED pause is the interesting case
|
|
259
|
+
* and it lands the right way round too — the next completed turn releases
|
|
260
|
+
* what the abandoned one left holding.
|
|
261
|
+
*
|
|
262
|
+
* Pass an id where a caller really does mean one specific run.
|
|
263
|
+
*/
|
|
264
|
+
fireRun(runId) {
|
|
265
|
+
return this.fire((r) => r.scope === 'run' && (runId === undefined || r.origin.runId === runId), 'run-end').then(() => undefined);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Fire the `'session'` registrations for one hosting session — or, with no
|
|
269
|
+
* `sessionId`, every `'session'` registration there is.
|
|
270
|
+
*
|
|
271
|
+
* Answers how many it closed, so a composition root can log a number instead
|
|
272
|
+
* of hoping.
|
|
273
|
+
*/
|
|
274
|
+
fireSession(sessionId, reason = 'session-end') {
|
|
275
|
+
return this.fire((r) => r.scope === 'session' && (sessionId === undefined || r.origin.sessionId === sessionId), reason);
|
|
276
|
+
}
|
|
277
|
+
/** Fire EVERYTHING, whatever scope it asked for. The backstop. */
|
|
278
|
+
fireShutdown() {
|
|
279
|
+
return this.fire(() => true, 'shutdown');
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Sweep sessions nobody has touched for `idleMs`.
|
|
283
|
+
*
|
|
284
|
+
* LAZY, on the next tier interaction — never a timer. A library that installs
|
|
285
|
+
* an interval keeps the host process alive, which is the same reason
|
|
286
|
+
* `shutdownOn` refuses to grab signals unless asked.
|
|
287
|
+
*/
|
|
288
|
+
sweepIdle() {
|
|
289
|
+
if (this.registrations.size === 0)
|
|
290
|
+
return;
|
|
291
|
+
const cutoff = this.now() - this.idleMs;
|
|
292
|
+
const stale = [...this.registrations.values()].filter((r) => r.touchedMs <= cutoff);
|
|
293
|
+
if (stale.length === 0)
|
|
294
|
+
return;
|
|
295
|
+
this.detach(this.fire((r) => stale.includes(r), 'idle'));
|
|
296
|
+
}
|
|
297
|
+
evictOverflow() {
|
|
298
|
+
if (this.registrations.size <= this.maxLive)
|
|
299
|
+
return;
|
|
300
|
+
const surplus = this.registrations.size - this.maxLive;
|
|
301
|
+
const coldest = [...this.registrations.values()]
|
|
302
|
+
.sort((a, b) => a.touchedMs - b.touchedMs || a.id - b.id)
|
|
303
|
+
.slice(0, surplus);
|
|
304
|
+
this.detach(this.fire((r) => coldest.includes(r), 'evicted'));
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The one firing implementation: select, claim (law 1), reverse (law 3),
|
|
308
|
+
* settle everything (law 5).
|
|
309
|
+
*
|
|
310
|
+
* Selection and claiming happen SYNCHRONOUSLY before the first `await`, so
|
|
311
|
+
* two overlapping fires — a shutdown racing an idle sweep — cannot both take
|
|
312
|
+
* the same registration.
|
|
313
|
+
*/
|
|
314
|
+
async fire(match, reason) {
|
|
315
|
+
const claimed = [];
|
|
316
|
+
for (const [mapKey, registration] of this.registrations) {
|
|
317
|
+
if (registration.fired || !match(registration))
|
|
318
|
+
continue;
|
|
319
|
+
registration.fired = true;
|
|
320
|
+
this.registrations.delete(mapKey);
|
|
321
|
+
claimed.push(registration);
|
|
322
|
+
}
|
|
323
|
+
if (claimed.length === 0)
|
|
324
|
+
return 0;
|
|
325
|
+
claimed.sort((a, b) => b.id - a.id); // reverse registration order
|
|
326
|
+
await Promise.allSettled(claimed.map((registration) => this.run(registration, reason)));
|
|
327
|
+
return claimed.length;
|
|
328
|
+
}
|
|
329
|
+
/** One cleanup, bounded, reported either way. Never rethrows. */
|
|
330
|
+
async run(registration, reason) {
|
|
331
|
+
const startedAt = this.now();
|
|
332
|
+
const base = {
|
|
333
|
+
tool: registration.tool,
|
|
334
|
+
scope: registration.scope,
|
|
335
|
+
keyHash: registration.keyHash,
|
|
336
|
+
...(registration.runnerId !== undefined && { runnerId: registration.runnerId }),
|
|
337
|
+
...(registration.label !== undefined && { label: registration.label }),
|
|
338
|
+
reason,
|
|
339
|
+
};
|
|
340
|
+
try {
|
|
341
|
+
await withTimeout(async () => registration.cleanup(), this.timeoutMs, `${registration.tool} teardown`);
|
|
342
|
+
this.report({
|
|
343
|
+
...base,
|
|
344
|
+
kind: 'closed',
|
|
345
|
+
durationMs: this.now() - startedAt,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
// Law 5: swallowed at the caller, never silent on the wire.
|
|
350
|
+
this.report({
|
|
351
|
+
...base,
|
|
352
|
+
kind: 'close-failed',
|
|
353
|
+
durationMs: this.now() - startedAt,
|
|
354
|
+
error: err instanceof Error ? err.message : String(err),
|
|
355
|
+
...(err instanceof Error && { errorClass: err.constructor?.name ?? err.name }),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
exports.ToolSessionTier = ToolSessionTier;
|
|
361
|
+
/** A teardown that outran its budget. Named so an alert can route on it. */
|
|
362
|
+
class ToolTeardownTimeoutError extends Error {
|
|
363
|
+
timeoutMs;
|
|
364
|
+
constructor(what, timeoutMs) {
|
|
365
|
+
super(`${what} did not finish within ${timeoutMs}ms and was abandoned. The cleanup may still ` +
|
|
366
|
+
'be running; the runner stopped waiting so a shutdown cannot hang on a vendor call.');
|
|
367
|
+
this.name = 'ToolTeardownTimeoutError';
|
|
368
|
+
this.timeoutMs = timeoutMs;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
exports.ToolTeardownTimeoutError = ToolTeardownTimeoutError;
|
|
372
|
+
/**
|
|
373
|
+
* Run `work`, giving up after `timeoutMs`.
|
|
374
|
+
*
|
|
375
|
+
* The abandoned promise is deliberately NOT cancelled — there is nothing to
|
|
376
|
+
* cancel a vendor's in-flight `Stop` with, and pretending otherwise would be
|
|
377
|
+
* worse than saying we stopped waiting. The timer is unref'd where the runtime
|
|
378
|
+
* allows it, so a pending teardown can never be the reason a process stays up.
|
|
379
|
+
*/
|
|
380
|
+
async function withTimeout(work, timeoutMs, what) {
|
|
381
|
+
let timer;
|
|
382
|
+
try {
|
|
383
|
+
await Promise.race([
|
|
384
|
+
work(),
|
|
385
|
+
new Promise((_resolve, reject) => {
|
|
386
|
+
timer = setTimeout(() => reject(new ToolTeardownTimeoutError(what, timeoutMs)), timeoutMs);
|
|
387
|
+
timer.unref?.();
|
|
388
|
+
}),
|
|
389
|
+
]);
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
if (timer !== undefined)
|
|
393
|
+
clearTimeout(timer);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=toolSessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolSessions.js","sourceRoot":"","sources":["../../src/core/toolSessions.ts"],"names":[],"mappings":"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"}
|
package/dist/core/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,6CAAwC;
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,6CAAwC;AAoLxC;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAEjD;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,IAAa;IAC/C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,yDAAyD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAClF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,GAAG,EAAE;YACd,CAAC,CAAC,SAAS,IAAI,CAAC,MAAM,iBAAiB;YACvC,CAAC,CAAC,mFAAmF,CAAC;QAC1F,MAAM,IAAI,KAAK,CACb,aAAa,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,MAAM,IAAI;YAC/C,sFAAsF;YACtF,wEAAwE;YACxE,0DAA0D,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAlBD,kDAkBC;AAED;;;;;;GAMG;AACH,SAAgB,qBAAqB,CAAC,IAAa;IACjD,IAAI,CAAC,IAAA,uBAAS,GAAE;QAAE,OAAO;IACzB,IAAI,CAAC;QACH,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,4BAA6B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AARD,sDAQC;AAED,SAAgB,UAAU,CACxB,OAA0C;IAE1C,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO;QACL,MAAM,EAAE;YACN,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;SACvE;QACD,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QAC9C,uEAAuE;QACvE,2DAA2D;QAC3D,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAwB,EAAE,CAAC;QACnF,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC;AAhBD,gCAgBC"}
|
package/dist/doors/providers.js
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* plus the MCP client/server bridge.
|
|
13
13
|
* • Thinking handlers — per-vendor parsing of extended-thinking blocks,
|
|
14
14
|
* which is a provider concern wearing another hat.
|
|
15
|
+
* • Code runners — `localCodeRunner` (process isolation, on this
|
|
16
|
+
* machine, and it says so) and `agentCoreCodeRunner`
|
|
17
|
+
* (a real managed sandbox). Both are backends behind
|
|
18
|
+
* the `CodeRunner` port; `codeRunnerTool` on the main
|
|
19
|
+
* barrel is the tool that holds one.
|
|
15
20
|
*
|
|
16
21
|
* Each vendor SDK is lazy-required, so importing this door loads no vendor
|
|
17
22
|
* code — only calling a factory does.
|
|
@@ -36,8 +41,16 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
36
41
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
42
|
};
|
|
38
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.agentCoreCodeRunner = exports.localCodeRunner = void 0;
|
|
39
45
|
__exportStar(require("../llm-providers.js"), exports);
|
|
40
46
|
__exportStar(require("../embedders/index.js"), exports);
|
|
41
47
|
__exportStar(require("../tool-providers/index.js"), exports);
|
|
42
48
|
__exportStar(require("../thinking/index.js"), exports);
|
|
49
|
+
// Code runners — the backends behind the `CodeRunner` port (9.7.0). The port
|
|
50
|
+
// types themselves live on the main barrel with `codeRunnerTool`, alongside the
|
|
51
|
+
// other adapter ports.
|
|
52
|
+
var local_js_1 = require("../adapters/code/local.js");
|
|
53
|
+
Object.defineProperty(exports, "localCodeRunner", { enumerable: true, get: function () { return local_js_1.localCodeRunner; } });
|
|
54
|
+
var agentcore_js_1 = require("../adapters/code/agentcore.js");
|
|
55
|
+
Object.defineProperty(exports, "agentCoreCodeRunner", { enumerable: true, get: function () { return agentcore_js_1.agentCoreCodeRunner; } });
|
|
43
56
|
//# sourceMappingURL=providers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/doors/providers.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/doors/providers.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;;;;;;;;;;;;;;;AAEH,sDAAoC;AACpC,wDAAsC;AACtC,6DAA2C;AAC3C,uDAAqC;AAErC,6EAA6E;AAC7E,gFAAgF;AAChF,uBAAuB;AACvB,sDAAyF;AAAhF,2GAAA,eAAe,OAAA;AACxB,8DAMuC;AALrC,mHAAA,mBAAmB,OAAA"}
|
|
@@ -0,0 +1,133 @@
|
|
|
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 type { CodeRunner } from '../types.js';
|
|
61
|
+
export interface AgentCoreCodeRunnerOptions {
|
|
62
|
+
readonly region?: string;
|
|
63
|
+
/**
|
|
64
|
+
* The code-interpreter resource to run in — AWS's built-in
|
|
65
|
+
* (`'aws.codeinterpreter.v1'`) or your own custom interpreter's identifier.
|
|
66
|
+
*/
|
|
67
|
+
readonly identifier: string;
|
|
68
|
+
/**
|
|
69
|
+
* Session TTL in seconds. AgentCore terminates the session past it regardless
|
|
70
|
+
* of activity. Service default 900 (15 min); max 28,800 (8 h).
|
|
71
|
+
*
|
|
72
|
+
* **This is why "already gone" is a normal outcome, not an incident**: the
|
|
73
|
+
* far side reaps on its own schedule, so a `Stop` may arrive after AWS has
|
|
74
|
+
* already collected the session. `stop()` treats that as done.
|
|
75
|
+
*/
|
|
76
|
+
readonly sessionTimeoutSeconds?: number;
|
|
77
|
+
/** Default language when a call does not name one. `'python'` (the service
|
|
78
|
+
* accepts `javascript`, `python`, `typescript`). */
|
|
79
|
+
readonly language?: string;
|
|
80
|
+
/** Per-stream output ceiling, in characters. Default 8000; anything cut is
|
|
81
|
+
* REPORTED on `CodeResult.truncated`, never dropped quietly. */
|
|
82
|
+
readonly maxOutputChars?: number;
|
|
83
|
+
/** Stable id (default `'agentcore-code-runner'`). Rides the session events. */
|
|
84
|
+
readonly id?: string;
|
|
85
|
+
/** Test seam — inject a client implementing {@link AgentCoreCodeClientLike}.
|
|
86
|
+
* Bypasses the SDK entirely; the field mapping below is then yours. */
|
|
87
|
+
readonly _client?: AgentCoreCodeClientLike;
|
|
88
|
+
/** @internal Test injection — the AWS SDK module, to exercise the real shim
|
|
89
|
+
* (`send(new Command(...))`) with a fake SDK. */
|
|
90
|
+
readonly _sdk?: BedrockAgentCoreCodeSdkModule;
|
|
91
|
+
}
|
|
92
|
+
/** The operation-semantic surface this adapter calls. */
|
|
93
|
+
export interface AgentCoreCodeClientLike {
|
|
94
|
+
startSession(input: {
|
|
95
|
+
readonly codeInterpreterIdentifier: string;
|
|
96
|
+
readonly name?: string;
|
|
97
|
+
readonly sessionTimeoutSeconds?: number;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
readonly sessionId?: string;
|
|
100
|
+
}>;
|
|
101
|
+
invoke(input: {
|
|
102
|
+
readonly codeInterpreterIdentifier: string;
|
|
103
|
+
readonly sessionId: string;
|
|
104
|
+
readonly name: string;
|
|
105
|
+
readonly arguments: {
|
|
106
|
+
readonly code: string;
|
|
107
|
+
readonly language?: string;
|
|
108
|
+
};
|
|
109
|
+
}): Promise<AgentCoreInvokeAnswer>;
|
|
110
|
+
stopSession(input: {
|
|
111
|
+
readonly codeInterpreterIdentifier: string;
|
|
112
|
+
readonly sessionId: string;
|
|
113
|
+
}): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
/** What `invoke` reports back, already drained of its event stream. */
|
|
116
|
+
export interface AgentCoreInvokeAnswer {
|
|
117
|
+
readonly stdout: string;
|
|
118
|
+
readonly stderr: string;
|
|
119
|
+
readonly exitCode?: number;
|
|
120
|
+
readonly isError?: boolean;
|
|
121
|
+
}
|
|
122
|
+
/** The slice of `@aws-sdk/client-bedrock-agentcore` this shim touches. */
|
|
123
|
+
export interface BedrockAgentCoreCodeSdkModule {
|
|
124
|
+
readonly BedrockAgentCoreClient?: new (config: {
|
|
125
|
+
region?: string;
|
|
126
|
+
}) => {
|
|
127
|
+
send(cmd: unknown): Promise<unknown>;
|
|
128
|
+
};
|
|
129
|
+
readonly StartCodeInterpreterSessionCommand?: new (input: unknown) => unknown;
|
|
130
|
+
readonly InvokeCodeInterpreterCommand?: new (input: unknown) => unknown;
|
|
131
|
+
readonly StopCodeInterpreterSessionCommand?: new (input: unknown) => unknown;
|
|
132
|
+
}
|
|
133
|
+
export declare function agentCoreCodeRunner(options: AgentCoreCodeRunnerOptions): CodeRunner;
|