agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
100 lines • 5.33 kB
TypeScript
/**
* requestCredentials — a {@link CredentialProvider} fed from the INCOMING
* REQUEST: the credential a tool uses is the one this run's caller sent.
*
* ```ts
* import { requestCredentials, requestContext, decodedWith } from 'agentfootprint/security';
*
* // Once, at boot — no secret in here:
* const credentials = requestCredentials({
* services: {
* crm: 'crmToken', // bearer, as sent
* billing: { secret: 'billingKey', as: asApiKey('x-api-key') },
* mcp: { secret: 'mcpToken', as: decodedWith((raw) => decrypt(raw)) },
* },
* });
* const agent = Agent.create({ provider, credentials }).tool(crmTool).build();
*
* // Per request:
* await agent.run({ message }, { request: requestContext({ secrets: { crmToken, mcpToken } }) });
* ```
*
* A tool declares `needs: { credential: 'crm' }` and reads `ctx.credential`,
* exactly as with any other provider. Nothing about the tool changes.
*
* ── Interface → adapter → strategies ────────────────────────────────────────
* The interface is the `CredentialProvider` port. This adapter reads the
* secret named for the service from the request the call belongs to
* (`CredentialRequest.request`, filled by the framework). A strategy turns the
* raw value into a `Credential`: {@link asBearer} (the default),
* {@link asApiKey}, or {@link decodedWith} for a value that must be decrypted
* or exchanged first.
*
* ── Fails closed, by name ───────────────────────────────────────────────────
* Every refusal is a {@link RequestCredentialError} naming the SERVICE and the
* secret's NAME — never a value, and never a strategy's own error text (a
* decrypt library may quote its input). A thrown message reaches the model as
* the tool result and rides `agentfootprint.credential.failed`.
* - no request on the call (a run started without one, a mount no run
* reaches, a connection-level caller) → `'no-request'`;
* - the request does not carry the secret → `'missing'`;
* - a service this provider was not told about → `'unknown-service'`;
* - the strategy threw or returned no credential → `'undecodable'`.
* There is no fallback: a missing per-request credential is never served from
* another request, a default, or the process.
*
* Pattern: Adapter (port) + Strategy (raw value → Credential).
*/
import type { Credential, CredentialProvider } from './types.js';
/** Raw request value → the credential a tool applies. May be async (decrypt, exchange). */
export type RequestCredentialStrategy = (raw: string) => Credential | Promise<Credential>;
/** Where a service's credential comes from in the request, and how it is read. */
export interface RequestCredentialSource {
/** The secret's name in `requestContext({ secrets })`. */
readonly secret: string;
/** How the raw value becomes a credential. Default {@link asBearer}. */
readonly as?: RequestCredentialStrategy;
}
/** What {@link requestCredentials} takes. */
export interface RequestCredentialsOptions {
/** Optional id (defaults to `'request-credentials'`). */
readonly id?: string;
/**
* Service id (what a tool's `needs.credential` names) → its source. A string
* is the secret's name, read as a bearer token.
*/
readonly services: Readonly<Record<string, string | RequestCredentialSource>>;
}
/** Why a request credential was refused. */
export type RequestCredentialRefusal = 'no-request' | 'missing' | 'unknown-service' | 'undecodable';
/**
* A request credential that could not be served. Its message names the
* service and the secret's name; it never carries a value or a strategy's
* own error.
*/
export declare class RequestCredentialError extends Error {
readonly reason: RequestCredentialRefusal;
readonly service: string;
readonly code: "ERR_REQUEST_CREDENTIAL";
constructor(reason: RequestCredentialRefusal, service: string, message: string);
}
/** The raw value, as a bearer token (`Authorization: Bearer …`). The default. */
export declare function asBearer(): RequestCredentialStrategy;
/** The raw value, as an API key in one header (default `x-api-key`). */
export declare function asApiKey(headerName?: string): RequestCredentialStrategy;
/**
* Decode the raw value first — decrypt an encrypted per-request token,
* exchange it — then read the result with `then` (default {@link asBearer}).
* `decode` may return the plain value or a finished `Credential`. Whatever it
* throws is replaced by an `'undecodable'` refusal that quotes nothing.
*/
export declare function decodedWith(decode: (raw: string) => string | Credential | Promise<string | Credential>, then?: RequestCredentialStrategy): RequestCredentialStrategy;
/**
* Build the provider. Configuration is checked here, at boot: an empty
* service map, an empty secret name or a strategy that is not a function is
* refused by name.
*
* @throws TypeError on a malformed `services` map.
*/
export declare function requestCredentials(options: RequestCredentialsOptions): CredentialProvider;
//# sourceMappingURL=requestCredentials.d.ts.map