agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
131 lines (130 loc) • 8.38 kB
TypeScript
/**
* agentCoreCodeRunner — AWS Bedrock AgentCore Code Interpreter behind the
* {@link CodeRunner} port (peer-dep `@aws-sdk/client-bedrock-agentcore`).
*
* import { agentCoreCodeRunner } from 'agentfootprint/providers';
* const runner = agentCoreCodeRunner({ region: 'us-east-1', identifier: 'aws.codeinterpreter.v1' });
*
* A REAL sandbox, which is the point: `localCodeRunner` gives you process
* isolation on your own machine and says so; this gives you a managed,
* network-and-filesystem-isolated environment, and the tool code is identical
* across the swap.
*
* ── The operations, verified against the SDK ────────────────────────────────
* Pinned in `test/adapters/aws/awsCommandPin.ts`. The three commands were
* verified against a real install of `@aws-sdk/client-bedrock-agentcore`
* 3.1108.0, and the four `InvokeCodeInterpreter` tool shapes below against
* 3.1159.0:
*
* • `StartCodeInterpreterSessionCommand`
* in `{ codeInterpreterIdentifier, name?, sessionTimeoutSeconds? }`
* out `{ codeInterpreterIdentifier, sessionId, createdAt }`
* • `InvokeCodeInterpreterCommand` — `{ codeInterpreterIdentifier, sessionId,
* name, arguments }`, one service tool per call:
* `executeCode` `{ code, language? }` → `execute`
* `writeFiles` `{ content: [{ path, text } | { path, blob }] }` → `stageInputs`
* `readFiles` `{ paths: [path] }` → `readFile`
* `executeCommand` `{ command }` → `runCommand`
* out `{ sessionId, stream }` — an **event stream**, not a body
* • `StopCodeInterpreterSessionCommand`
* in `{ codeInterpreterIdentifier, sessionId }` ← the session ID, not a URI
*
* **`Invoke` answers with an `AsyncIterable`** whose members are `{ result }`
* or a modelled EXCEPTION, and `./agentcoreClient.ts` drains it: exception
* members raise BY NAME, result members fold into one answer — and nothing
* the stream did not carry is filled in.
*
* ── Results say only what the service reported ──────────────────────────────
* `CodeResult.ok` is `true` or `false` only when the service said which: an
* exit status, or its own `isError` verdict. When it reported neither, `ok`
* is ABSENT — unknown. (It used to be `true`: success from silence.) A
* command's `exitCode` is the reported one or absent, never read off `isError`.
*
* ── Files in and out, exactly and bounded ───────────────────────────────────
* `stageInputs` writes TEXT as text and BYTES as a blob — never base64 inside
* text with a decode step — in ONE `writeFiles` call, refused before it is
* sent when its body would pass AgentCore's 100 MB per-call limit. AgentCore
* takes no environment, so `AF_STAGED_INPUTS` reaches code by a setup
* execution before EVERY run (confirmed by the line it prints; the runtime is
* mutable by the code it runs, so an earlier setup proves nothing now) and
* reaches a command as an `export` line ahead of it. `readFile` reads ONE path
* and pairs the file to it by the URI the service returned — one `file:///`
* form, read one way, a raw query or fragment refused — failing closed on a
* missing or different one; text or bytes is whichever field the service
* filled, and a file over `maxBytes` is refused, never cut.
*
* ── Commands are opt-in ─────────────────────────────────────────────────────
* `runCommand` runs arbitrary shell, so a session has it only when the runner
* is built with `commands: true`. The sandbox's network is whatever the code
* interpreter resource was created with — the built-in `aws.codeinterpreter.v1`
* runs with AWS's most restrictive defaults (Sandbox mode: limited network
* access, Amazon S3 included); a custom interpreter may be Public (internet)
* or VPC. This adapter can neither see nor change that.
*
* ── A session that is gone says so, once ────────────────────────────────────
* A service error naming the session as "not active" becomes
* {@link SessionExpiredError} on every verb, and so does a
* `ResourceNotFoundException` on a verb that addresses nothing but the session
* (code, a command, a write). On a READ, "not found" may mean the file, so it
* is `CodeSandboxError` (`'file-not-returned'`), naming both possibilities —
* never offered as proof the session is gone.
*
* ── How this talks to the SDK (the 9.4.0 law) ───────────────────────────────
* Through `client.send(new SomeCommand(input))`, never a method on the client.
* A bare `@aws-sdk/client-*` **Client is command-based**: its prototype carries
* `send` and `destroy` and nothing else. Three adapters have shipped that bug
* in this package.
*
* ── Credentials ─────────────────────────────────────────────────────────────
* Standard AWS credential resolution (the SDK's own chain). A tool holding a
* long-lived session must NOT cache a `Credential` object from `ctx.credential`
* past the call that produced it — a session outliving a run outlives its
* token. Re-resolve per execute through `ctx.credentials`.
*
* Pattern: Adapter (GoF) + lazy peer-dep load — the SDK is required only when
* `start()` first runs (or never, if you inject `_client` / `_sdk`).
*/
import type { CodeRunner } from '../../provider-contract/types.js';
import { type AgentCoreCodeClientLike, type BedrockAgentCoreCodeSdkModule } from './agentcoreClient.js';
export type { AgentCoreCodeClientLike, AgentCoreFileContent, AgentCoreInvokeAnswer, AgentCoreInvokeInput, AgentCoreInvokeTarget, AgentCoreResource, BedrockAgentCoreCodeSdkModule, } from './agentcoreClient.js';
export interface AgentCoreCodeRunnerOptions {
readonly region?: string;
/**
* The code-interpreter resource to run in — AWS's built-in
* (`'aws.codeinterpreter.v1'`) or your own custom interpreter's identifier.
* Its network mode (Sandbox, Public or VPC) is set on the resource, not here.
*/
readonly identifier: string;
/**
* Session TTL in seconds. AgentCore terminates the session past it regardless
* of activity. Service default 900 (15 min); max 28,800 (8 h).
*
* **This is why "already gone" is a normal outcome, not an incident**: the
* far side reaps on its own schedule, so a `Stop` may arrive after AWS has
* already collected the session. `stop()` treats that as done, and every
* other verb raises `SessionExpiredError`.
*/
readonly sessionTimeoutSeconds?: number;
/** Default language when a call does not name one. `'python'` (the service
* accepts `javascript`, `python`, `typescript`). */
readonly language?: string;
/** Per-stream output ceiling, in characters. Default 8000; anything cut is
* REPORTED on `CodeResult.truncated`, never dropped quietly. */
readonly maxOutputChars?: number;
/**
* Give each session `runCommand` — one shell command through AgentCore's
* `executeCommand`. Default `false`, and then the member is ABSENT
* (`canRunCodeCommands` says no): it runs arbitrary shell, so a runner offers
* it only when built to. Build one runner per agent that should have it.
*/
readonly commands?: boolean;
/** Stable id (default `'agentcore-code-runner'`). Rides the session events. */
readonly id?: string;
/** Test seam — inject a client implementing {@link AgentCoreCodeClientLike}.
* Bypasses the SDK entirely; the field mapping is then yours. */
readonly _client?: AgentCoreCodeClientLike;
/** @internal Test injection — the AWS SDK module, to exercise the real shim
* (`send(new Command(...))`) with a fake SDK. */
readonly _sdk?: BedrockAgentCoreCodeSdkModule;
}
export declare function agentCoreCodeRunner(options: AgentCoreCodeRunnerOptions): CodeRunner;