UNPKG

agentfootprint

Version:

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

130 lines • 6.86 kB
/** * hostFilled — tool arguments the HOST fills, from the run's credential or * identity. The model never sees them and cannot set them. * * ```ts * import { hostFilled, fromCredential, fromIdentity } from 'agentfootprint'; * * // An MCP server whose every tool takes a `token` argument: * const tools = hostFilled(await crm.tools(), { * token: fromCredential({ service: 'crm' }), // ctx.credential, resolved per call * tenantId: fromIdentity('tenant'), // ctx.identity.tenant * }); * ``` * * ── What it does ──────────────────────────────────────────────────────────── * 1. **Removes the field from the schema the model sees** (`properties` and * `required`). The model is never told the argument exists. * 2. **Refuses a model-supplied value by name.** A call that sends the field * anyway — guessed, injected by a document, copied from another tool — is * refused with a result naming the argument, and the tool does not run. * 3. **Fills it at the last moment**, inside `execute`, from `ctx.credential` * (the tool's declared `needs`, which `fromCredential({ service })` sets * when the tool has none) or `ctx.identity`. A missing value refuses the * call by name — never an empty or default value. * * ── Why a wrapper around `execute` ────────────────────────────────────────── * The filled value exists only inside the call: the arguments every dispatch * door records (`tool_start`, history, the commit log, a consent pause's * checkpoint) are the MODEL's arguments, which never held it. Wrapping * `execute` makes that true at every door — the agent loop, a resumed call, * an inner `ctx.tools` call and `mcpServe` — without each door knowing. * * A credential value is also SCRUBBED on the way out: every occurrence in the * tool's result is replaced by `[host-filled]`, and a thrown error that * carries it (an SDK that echoes its request) is replaced by one that does * not. That is defence in depth for a server that echoes; it cannot cover a * value the tool transforms (base64, hashing) or sends elsewhere itself. * Identity values are not secrets and are not scrubbed. * * Pattern: Decorator over `Tool` (declaration + execute). Role: tools, argument law. */ import type { Credential } from '../identity/types.js'; import type { HostFilledArguments } from '../semantics/coverage/search/inEffect.js'; import { type Tool } from './tools.js'; /** Which identity field a {@link fromIdentity} fill reads. */ export type HostFilledIdentityField = 'principal' | 'tenant' | 'conversationId'; /** Fill from the call's resolved credential (`ctx.credential`). */ export interface HostFillFromCredential { readonly from: 'credential'; /** The service (`needs.credential`) — required when the tool declares no `needs`. */ readonly service?: string; /** Scopes / mode for the `needs` this fill declares (ignored when the tool has its own). */ readonly scopes?: readonly string[]; readonly mode?: 'machine' | 'user'; /** Credential → argument value. Default: a bearer's `token`, an API key's `key`. */ readonly read?: (credential: Credential) => string; } /** Fill from the identity the run's caller named (`ctx.identity`). */ export interface HostFillFromIdentity { readonly from: 'identity'; readonly field: HostFilledIdentityField; } /** Where a host-filled argument's value comes from. */ export type HostFill = HostFillFromCredential | HostFillFromIdentity; /** * Fill the argument from the call's credential. With `service`, the tool's * `needs` is declared for you (a tool that already declares one must name the * same service, or omit `service`). */ export declare function fromCredential(options?: Omit<HostFillFromCredential, 'from'>): HostFillFromCredential; /** Fill the argument from the identity the run's caller named. */ export declare function fromIdentity(field: HostFilledIdentityField): HostFillFromIdentity; /** * A model sent a value for an argument the host fills. The call is refused * and the tool does not run. Names the arguments, never a value. */ export declare class HostFilledArgumentError extends Error { readonly toolName: string; readonly fields: readonly string[]; readonly code: "ERR_HOST_FILLED_ARGUMENT"; constructor(toolName: string, fields: readonly string[]); } /** * A host-filled argument had no value for this call — no credential was * resolved, the credential has no single value to read, or the run's caller * named no such identity field. The call is refused; nothing is guessed. */ export declare class HostFilledValueMissingError extends Error { readonly toolName: string; readonly field: string; readonly code: "ERR_HOST_FILLED_VALUE_MISSING"; constructor(toolName: string, field: string, why: string); } /** * Which arguments `hostFilled` fills on `tool`, by where their value comes * from — the names only, never a value. `undefined` for a tool no * `hostFilled` returned. * * The filled values exist only inside `execute`, so the arguments every door * records never hold them; a reader that composes ground from a call's * arguments (`Tool.search` composition) learns the filled set here: an * identity-filled argument is a filter in effect, a credential-filled one * names no ground. * * @example * ```ts * hostFillsOf(hostFilled(tool, { tenantId: fromIdentity('tenant') })); * // { identity: { tenantId: 'tenant' }, credential: [] } * ``` */ export declare function hostFillsOf(tool: Tool): HostFilledArguments | undefined; /** What replaces a credential value in a result or an error. */ export declare const HOST_FILLED_PLACEHOLDER = "[host-filled]"; /** * Declare tool arguments as filled by the host. * * One tool: every named argument must be a property of its `inputSchema`. * A list (e.g. `mcpClient().tools()`): each tool takes the fills for the * arguments IT declares and is returned unchanged when it declares none; an * argument no tool in the list declares is refused (a typo would otherwise * hide nothing). * * @throws TypeError at build — an unknown argument, a fill that is not one, * a credential fill with no service on a tool that declares no `needs`, a * service that disagrees with the tool's own `needs`, or an argument the * tool also declares under `wants` / `askOrAssume`. */ export declare function hostFilled<T extends Tool>(tool: T, fills: Readonly<Record<string, HostFill>>): Tool; export declare function hostFilled(tools: readonly Tool[], fills: Readonly<Record<string, HostFill>>): readonly Tool[]; //# sourceMappingURL=hostFilled.d.ts.map