agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
138 lines (137 loc) • 8.32 kB
TypeScript
/**
* invoke — the SigV4 auth strategy: Streamable HTTP, carried by the AWS SDK's
* `InvokeAgentRuntime`.
*
* The MCP client speaks HTTP — a POST, a few headers, a JSON body — and the SDK
* operation carries exactly that, field for field. This file is the mapping and
* nothing else. Every name in it is the SDK's own (`InvokeAgentRuntimeRequest`
* / `InvokeAgentRuntimeResponse` in `@aws-sdk/client-bedrock-agentcore`), read
* off an installed package rather than recalled:
*
* request InvokeAgentRuntimeRequest
* ───────────────────────────────────────────── ────────────────────────
* body payload (bytes)
* content-type contentType
* accept accept
* mcp-session-id mcpSessionId
* mcp-protocol-version mcpProtocolVersion
* x-amzn-bedrock-agentcore-runtime-session-id runtimeSessionId
* (the runtime, from options) agentRuntimeArn, qualifier, accountId
*
* InvokeAgentRuntimeResponse response
* ───────────────────────────────────────────── ────────────────────────
* statusCode status
* contentType content-type
* mcpSessionId mcp-session-id
* mcpProtocolVersion mcp-protocol-version
* runtimeSessionId x-amzn-bedrock-agentcore-runtime-session-id
* response (a stream) body
*
* The signature is the SDK's: `send` signs every attempt with the client's own
* credentials, so no credential passes through this file.
*
* ── The SDK's own retry is OFF ───────────────────────────────────────────────
* The client built here has `maxAttempts: 1`. The SDK's standard policy would
* re-send a `tools/call` after a 5xx or a dropped connection — a call that may
* already have run, in the same pinned session — which a retry here must never
* do on its own. Every retry goes through the transport's single loop instead,
* which repeats only what the replay rule permits (`mcp/replay.ts`): the
* refusals its retry policy recognises — the session-busy reply and a 429, each
* assumed, not proven, to have run nothing — and a dropped connection only when
* the app declared that for the tool. A `client` you pass keeps ITS policy:
* build it with `maxAttempts: 1` for the same guarantee.
*
* ── A service exception is an ANSWER ─────────────────────────────────────────
* When the service answers with a non-2xx status, the SDK throws instead of
* returning — `RetryableConflictException` (409), `ThrottlingException` (429),
* or a hosted server's own 404 as `Unknown`. Each is handed on as the `Response`
* an HTTPS caller would have read: its status, its error type in
* `x-amzn-errortype`, `retry-after` when the service sent one, and as the body
* the server's own JSON-RPC error when it sent one (the SDK spreads a body it
* cannot model onto the exception) — never the exception's message, which can
* quote the signed request. What the MCP client may read of it is decided
* once, by `safeAnswer.ts`. So the transport's retry decision sees the 409,
* `mcpClient`'s 429 retry sees the 429, and the MCP client reports the
* status. A failure with no status — the
* network, an abort — never reached the service as an answer, and is thrown as
* it is.
*
* ── What the operation cannot carry ──────────────────────────────────────────
* Any method but POST. A GET (the optional server-message stream) and a DELETE
* are answered HERE with 405, which the MCP Streamable HTTP spec defines as "not
* offered" for both, so the MCP client carries on. And any header the operation
* does not model — it has no field to put one in. A `'reuse'` connection's
* DELETE never reaches this far: the session strategy turns it into a stop
* (`STOP_RUNTIME_SESSION`), sent as `StopRuntimeSessionCommand`.
*
* ── The send gate runs after signing ─────────────────────────────────────────
* A request the MCP client pool sent carries its send gate (`mcp/requestGuard.ts`).
* Signing is asynchronous work inside the AWS SDK's `send`, so the gate is a
* step on each COMMAND's own middleware stack, after the request is signed and
* before the request handler sends it — never on the client's stack, so an AWS
* client the app passed is not changed. A command with no stack (a double) has
* the gate asked just before `send`, as every command also does.
*
* Pattern: Adapter (fetch ↔ command). Role: outer ring, lazy AWS peer dep.
*/
import type { ThrottleFetch } from '../../../mcp/throttleRetry.js';
/** A command-based SDK client — `send` is the only member this strategy uses. */
export interface AgentCoreRuntimeSdkClient {
send(command: unknown, options?: {
abortSignal?: AbortSignal;
}): Promise<unknown>;
}
/** The slice of `@aws-sdk/client-bedrock-agentcore` this strategy touches. */
export interface BedrockAgentCoreRuntimeSdkModule {
readonly BedrockAgentCoreClient?: new (config: {
region?: string;
maxAttempts?: number;
}) => AgentCoreRuntimeSdkClient;
readonly InvokeAgentRuntimeCommand?: new (input: unknown) => unknown;
/** Needed under `session: 'reuse'`, where ending a client stops its runtime session. */
readonly StopRuntimeSessionCommand?: new (input: unknown) => unknown;
}
/**
* @internal The `init` key a `'reuse'` connection's stop rides under: the
* runtime session id to stop (`transport.ts` · `stopSession`). A symbol, so no
* request the MCP client makes can ever claim it.
*/
export declare const STOP_RUNTIME_SESSION: unique symbol;
/** Which runtime every request goes to, as the operation names it. */
export interface InvokeTarget {
readonly agentRuntimeArn: string;
readonly qualifier?: string;
readonly accountId?: string;
}
/** What the strategy is built from: a region, and optionally what was injected. */
export interface InvokeSdkOptions {
readonly region: string;
/**
* A client the caller built — its credentials, region and retry settings win,
* so build it with `maxAttempts: 1` (see the module note).
*/
readonly client?: AgentCoreRuntimeSdkClient;
/** The SDK module the caller imported; otherwise the peer dep is loaded. */
readonly sdk?: BedrockAgentCoreRuntimeSdkModule;
}
/**
* The client and the command constructor this strategy sends with.
*
* Loaded when the transport is BUILT, not at the first call, so a missing peer
* dependency is refused where the transport is declared rather than mid-run.
*
* @throws when the SDK cannot be loaded, or when it lacks what is needed.
*/
export declare function invokeSdk(options: InvokeSdkOptions): {
readonly client: AgentCoreRuntimeSdkClient;
readonly InvokeAgentRuntimeCommand: new (input: unknown) => unknown;
readonly StopRuntimeSessionCommand?: new (input: unknown) => unknown;
};
/**
* A `fetch` that sends each request as one `InvokeAgentRuntimeCommand`.
*
* The URL the MCP client passes is ignored on purpose: it names the endpoint for
* reports and errors, while the invocation goes to `target` — the SDK resolves
* the endpoint itself, from the client's region.
*/
export declare function invokeFetch(target: InvokeTarget, client: AgentCoreRuntimeSdkClient, InvokeAgentRuntimeCommand: new (input: unknown) => unknown, StopRuntimeSessionCommand?: new (input: unknown) => unknown): ThrottleFetch;