agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
109 lines (108 loc) • 6.07 kB
TypeScript
/**
* adapters/hosting/agentCoreA2A — an agentfootprint agent as an AWS Bedrock
* **AgentCore A2A** server, callable by other agents.
*
* ── The split, again ─────────────────────────────────────────────────────────
* Two different things are needed, and only one is Amazon's:
*
* • The **A2A protocol** — JSON-RPC 2.0, `message/send`, artifacts, the agent
* card. An open protocol several runtimes speak, so it lives in
* `a2aWire.ts` with no vendor in it.
* • The **container contract** — port 9000 (not 8080, and not 8000: each
* protocol gets its own), the agent mounted at `/`, `GET /ping` answering
* `{"status":"Healthy"}`, the session id arriving in
* `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id`, and a table of JSON-RPC
* error codes that are this runtime's exceptions rather than A2A's. That
* IS Amazon's, and it is what this file supplies.
*
* ── One deviation you must know about ────────────────────────────────────────
* The A2A specification delivers JSON-RPC errors over HTTP 200. **AgentCore
* does not** — it returns the real status code (409, 404, …) with the JSON-RPC
* error body. That is a fact about the platform in front of your container, so
* a CLIENT must parse the error body even on a non-2xx response; see
* `agentCoreA2AErrorCode` for the table this host mirrors on the way out.
*
* ── What is NOT provided ─────────────────────────────────────────────────────
* `message/stream` and the task lifecycle. The agent card this host serves
* therefore declares `streaming: false` unless you overrule it, because a card
* that advertises streaming to a wire that cannot stream is a caller left
* waiting. AWS's own sample card says `true`; ours says what is true of ours.
*
* @example An agent other agents can call
* import { agentCoreA2AHost, memorySessions, standingAgent } from 'agentfootprint/hosting';
*
* const handle = await standingAgent({
* agent,
* sessions: memorySessions(),
* host: agentCoreA2AHost({
* card: { name: 'triage', description: 'Triages SAN alerts.', version: '1.0.0' },
* }),
* });
* process.on('SIGTERM', () => void handle.close());
*/
/// <reference types="node" />
import type { IncomingMessage, Server, ServerResponse } from 'node:http';
import { type CrossSiteOptions } from '../../hosting/doorGuard.js';
import { type HttpHost } from '../../hosting/httpHost.js';
import { type A2AAgentCard } from './a2aWire.js';
/** The port an AgentCore A2A server is expected on — its own, not HTTP's or MCP's. */
export declare const DEFAULT_AGENTCORE_A2A_PORT = 9000;
/** A2A mounts the agent at the root. */
export declare const AGENTCORE_A2A_INVOKE_PATH = "/";
/** The runtime's health path for every protocol. */
export declare const AGENTCORE_PING_PATH = "/ping";
/** Where the platform puts the caller's session. */
export declare const AGENTCORE_SESSION_HEADER = "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id";
/**
* AgentCore's own JSON-RPC error codes, mapped from this library's refusal codes.
*
* These numbers are the RUNTIME's exception table, not A2A's: `-32051`
* ResourceNotFound, `-32052` Validation, `-32053` Throttling and
* ServiceQuotaExceeded, `-32054` Conflict and RetryableConflict, `-32055`
* RuntimeClientError, `-32603` anything else. A client reads them to decide
* whether to retry — `-32054` with "Session operation in progress" is the one
* that must be retried with backoff, and A2A clients do not do that on their
* own.
*
* Exported so a client built against this host can share one table with it
* rather than keep a second copy that drifts.
*/
export declare function agentCoreA2AErrorCode(code: string | undefined): number;
/**
* Options for {@link agentCoreA2AHost}.
*
* The three cross-site fields are the door guard every `httpHost` keeps, with
* this adapter's defaults INVERTED — off unless set: the runtime's front door
* is the only way to this port and demands a credential no web page holds, so
* the browser rules would protect nothing here (see `platformFrontedDoor` in
* `src/hosting/doorGuard.ts` for the whole argument). Set any of them to
* enforce it, exactly as on every other door. On a LOOPBACK `hostname` every
* plain-host default applies instead; with the rules off the host says so once
* at boot. The session-id bound applies regardless.
*/
export interface AgentCoreA2AHostOptions extends CrossSiteOptions {
/** The agent card other agents read. Required — discovery is not optional in A2A. */
readonly card: A2AAgentCard;
/** Port to bind. Default {@link DEFAULT_AGENTCORE_A2A_PORT}. Pass `0` in tests. */
readonly port?: number;
/** Interface to bind. Default `'0.0.0.0'` — a container's door must be reachable. */
readonly hostname?: string;
/** Ceiling on a request body. Default one mebibyte. */
readonly maxBodyBytes?: number;
/** A `node:http` server you own. Refused together with `port`/`hostname`. */
readonly server?: Server;
/**
* Answer a path this host does not own. The agent card is served BEFORE this
* runs, and everything else falls through to you unchanged — so supplying one
* costs you nothing the card was not already going to take.
*/
readonly onUnhandled?: (req: IncomingMessage, res: ServerResponse) => void;
}
/**
* An {@link HttpHost} serving AgentCore's A2A container contract.
*
* `httpHost` keeps its own promises — draining, aborting on disconnect, failing
* a handler that throws or answers nothing — and this supplies the protocol,
* the paths, the port, the session header and the error table.
*/
export declare function agentCoreA2AHost(options: AgentCoreA2AHostOptions): HttpHost;