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
TypeScript
/**
* 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 {};