UNPKG

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
/** * 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;