UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

98 lines • 4.32 kB
/** * 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 const REQUEST_GUARD = Symbol('agentfootprint.mcp.request-guard'); /** The guard `init` carries, or `undefined` — a request no pool sent. Never throws. */ export function guardOf(init) { if (init === undefined || init === null || typeof init !== 'object') return undefined; let guard; try { guard = init[REQUEST_GUARD]; } catch { return undefined; } if (guard === null || typeof guard !== 'object') return undefined; return typeof guard.check === 'function' && typeof guard.track === 'function' ? guard : undefined; } /** `init` carrying `guard`: a new object, every other field in place. */ export function withGuard(init, guard) { return { ...init, [REQUEST_GUARD]: guard }; } /** * Track `work` under `init`'s guard, when it has one, and hand `work` back * unchanged. The one line every boundary uses. */ export function trackedBy(init, work) { guardOf(init)?.track(work); return work; } // ─── The call's port ───────────────────────────────────────────────── /** * @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 const CALL_GUARD = Symbol('agentfootprint.mcp.call-guard'); /** `ctx` carrying `guard` for the MCP tool it is handed to. */ export function withCallGuard(ctx, guard) { return { ...ctx, [CALL_GUARD]: guard }; } /** The guard a call's context carries, or `undefined`. Never throws, whatever `ctx` is. */ export function callGuardOf(ctx) { if (ctx === null || typeof ctx !== 'object') return undefined; let guard; try { guard = ctx[CALL_GUARD]; } catch { return undefined; } if (guard === null || typeof guard !== 'object') return undefined; return typeof guard.check === 'function' && typeof guard.track === 'function' ? guard : undefined; } //# sourceMappingURL=requestGuard.js.map