agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
71 lines (70 loc) • 4.01 kB
TypeScript
/**
* requestGuard — the last check a pooled operation's request meets at every
* boundary the library controls, and the count of the work under it until
* that work has settled.
*
* Pattern: one object per operation, carried on the request's `init` under a
* library-private symbol.
* Role: The send gate of the MCP client pool (`mcp/pool/`). The MCP SDK and
* the transport wait between a tool call and the network — `send()`
* awaits its own headers, a gateway vends a credential inside `fetch`,
* the AWS SDK signs inside `send` — so the last check cannot live in
* the tool. Each place that waits asks the request's guard after its
* wait and before it hands the request on:
*
* - the watched `send` (`requestLedger.ts`) — the last boundary on stdio,
* whose `send` writes without waiting;
* - the pool's own fetch, before the request enters the transport;
* - the retry series, before every attempt (`throttleRetry.ts`);
* - the vending fetch, once the credential has arrived (`gatewayTransport.ts`);
* - the AWS SDK command, after it is signed and before its request handler
* sends it (`adapters/mcp/agentcoreRuntime/invoke.ts`).
*
* A refusal there sends nothing. What no guard can recall is a request already
* handed on: to the network, or to an app's own `fetch`.
*
* The same object COUNTS the work under the request: every promise a boundary
* starts for it — an attempt's `fetch`, a credential vend, an AWS `send` — is
* tracked until it settles, whatever the caller was told. An abort is a
* signal, not proof that the work stopped, so the pool keeps an operation
* charged until this count says it has.
*
* The guard rides `init` under a symbol, so a layer that rebuilds `init` with
* a spread (`{ ...init, headers }`) carries it on, and nothing reaches the
* wire: `fetch` reads only the fields it knows. A request with no guard — every
* request of a client the pool did not build — is exactly what it always was.
*/
/** @internal The `init` key a pooled request's guard rides under. */
export declare const REQUEST_GUARD: unique symbol;
/** What one pooled operation's requests are checked against, and counted by. */
export interface RequestGuard {
/**
* Throw the refusal when the request may not be handed on now — its binding
* went stale, or its operation was aborted or ran out of time. Synchronous:
* nothing waits between this check and the hand-off it guards.
*/
check(): void;
/** Count `work` as work under this request until it settles. Never throws. */
track(work: PromiseLike<unknown>): void;
/** What aborts the operation's own work — linked into each request it makes. */
readonly signal?: AbortSignal;
}
/** The guard `init` carries, or `undefined` — a request no pool sent. Never throws. */
export declare function guardOf(init: RequestInit | undefined): RequestGuard | undefined;
/** `init` carrying `guard`: a new object, every other field in place. */
export declare function withGuard(init: RequestInit | undefined, guard: RequestGuard): RequestInit;
/**
* Track `work` under `init`'s guard, when it has one, and hand `work` back
* unchanged. The one line every boundary uses.
*/
export declare function trackedBy<T extends PromiseLike<unknown>>(init: RequestInit | undefined, work: T): T;
/**
* @internal The call-context key a pooled tool hands its guard down under, to
* the MCP tool it runs (`callLifetime.ts` · `openCall` issues the call with
* it). Enumerable, so a wrapper that copies the context keeps it.
*/
export declare const CALL_GUARD: unique symbol;
/** `ctx` carrying `guard` for the MCP tool it is handed to. */
export declare function withCallGuard<C extends object>(ctx: C, guard: RequestGuard): C;
/** The guard a call's context carries, or `undefined`. Never throws, whatever `ctx` is. */
export declare function callGuardOf(ctx: unknown): RequestGuard | undefined;