UNPKG

agentfootprint

Version:

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

97 lines (96 loc) • 5.15 kB
/** * requestContext — the INCOMING REQUEST's secrets, sealed for one run. * * A host that receives a per-request credential (an encrypted token in a * header, a session-bound key) seals it here and hands the handle to the run: * * ```ts * const request = requestContext({ secrets: { mcpToken: req.headers['x-mcp-token'] } }); * await agent.run({ message }, { request }); * ``` * * ── Why a sealed handle and not a plain object ────────────────────────────── * The run's input, its options and its state all travel: input and state into * the commit log, the recorders and every exporter; options into whatever a * host logs. A plain `{ mcpToken: '…' }` would ride all of them. The handle * holds NOTHING an enumeration, `JSON.stringify`, `structuredClone` or an * inspector can reach — the values live in a module-private `WeakMap` keyed by * the handle, and only {@link requestSecretOf} (internal, never on a door) * reads them. So the handle can sit in an options bag, be logged by accident, * or be copied, and nothing secret moves with it. * * ── Who reads it ───────────────────────────────────────────────────────────── * Only a credential provider, through `CredentialRequest.request`, which the * framework fills from the run the call belongs to (`bindRequest`). The model * never sees it, no stage writes it to scope, and a tool reaches what it * vends only as `ctx.credential`. * * ── One request, one run ───────────────────────────────────────────────────── * The framework decides WHICH handle a call carries — the agent's own run, or * the run a composition opened for its members (`core/memberRun.ts`). A call * with no run that carries a request gets none: a request credential then * fails closed (`requestCredentials`), and is never borrowed from another run. * * Pattern: sealed value object (module-private WeakMap). Role: identity, run input. */ import type { CredentialProvider } from './types.js'; /** What {@link requestContext} takes. */ export interface RequestContextOptions { /** * The request's secrets, by name — `{ mcpToken: '<from the header>' }`. * Each value must be a non-empty string; names are compared exactly. */ readonly secrets: Readonly<Record<string, string>>; } /** * The incoming request's secrets, sealed. Opaque: it has no readable fields, * serializes as a fixed placeholder, and is recognised by the framework only * when {@link requestContext} built it. */ export interface RequestContext { /** Type-only brand: no value carries it at run time. A value built anywhere * else is refused at the door (`assertRequestContext`), never read. */ readonly [requestContextBrand]: never; } /** The brand's key — declared, never created; it exists only in the types. */ declare const requestContextBrand: unique symbol; /** * Seal an incoming request's secrets for one run. Pass the result as * `run(input, { request })` (or `resume(…, { request })`); a credential * provider built by `requestCredentials` reads it there. * * @throws TypeError when `secrets` is not a plain object of non-empty strings. */ export declare function requestContext(options: RequestContextOptions): RequestContext; /** Whether `value` is a handle {@link requestContext} built. */ export declare function isRequestContext(value: unknown): value is RequestContext; /** * Refuse a `request` that {@link requestContext} did not build — at the door * the caller used, before anything moves. A plain object would carry its * values wherever the options travel. * * @throws TypeError naming the door. */ export declare function assertRequestContext(value: unknown, door: string): void; /** * The secret a sealed request holds under `name`, or `undefined`. * * @internal Never re-exported on a door. The only reader is a credential * provider in this folder, which turns it into a `Credential` for one call. */ export declare function requestSecretOf(request: unknown, name: string): string | undefined; /** * The provider a call of ONE run is handed: a `getCredential` for `service` — * the service the calling tool DECLARES in its `needs` — carries that run's * request, and only that run's. A request the caller put on the request * itself is always replaced (or removed), so a tool cannot present another * run's handle through `ctx.credentials`; and a call for any OTHER service * carries no request, so a tool cannot pull a request credential it never * declared. `service` undefined → no call carries a request. * * Every other field passes through untouched; the provider's `id` is kept. * * @internal The framework's binding; not on a door. */ export declare function bindRequest(provider: CredentialProvider, request: RequestContext | undefined, service: string | undefined): CredentialProvider; export {};