agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
78 lines (77 loc) • 3.85 kB
TypeScript
/**
* plan — what one client is opened from, resolved before anything opens, and
* the context its tool definitions belong to.
*
* Pattern: a pure resolution step + two hashes.
* Role: § 6.1–6.2 of the design. A client is opened from a PLAN: the
* transport the factory returns for this identity, resolved at that
* moment and kept unchanged for the client's life. The plan's
* `configHash` is SHA-256 over a canonical encoding of the transport's
* comparable data — the pool name; the transport kind and URL; header
* names and a digest of their values; a gateway's service, scopes,
* mode and credential provider id; stdio's command, args and working
* directory; the transport's own stated `settings`; `resultMode` and
* `clientInfo`. A function (a `fetch`, an AWS client) cannot be
* compared: the app's `configuration` name covers it.
*
* The definitions CONTEXT is the binding a tool is listed under:
*
* - per client (the default): `pc:<configHash>` — the client's own list;
* - shared, when the app's `share` names both a configuration and an
* authorization: `sh:<sha256(configHash, configuration, authorization,
* tenant)>`. The tenant is always in it: the app's names widen sharing
* across principals, never across tenants.
*
* The transport is resolved once, when a client opens; the context is not. The
* app's names can move — a policy version in `authorization` — so the pool asks
* `share` again at every listing, every call and every send gate
* (`contextFor`), over the client's own `configHash`: a conversation that
* already has a client follows a new name as a new conversation would.
*
* Neither hash leaves the process; the app's names are hashed and never kept.
*/
import type { ClientPort, PooledClientSeam } from '../clientPort.js';
import type { McpClient, McpClientOptions, McpTransport } from '../types.js';
import type { PoolKey } from './identityKey.js';
import type { PoolSettings } from './options.js';
/** What a client is opened from. */
export interface ClientPlan {
readonly transport: McpTransport;
readonly configHash: string;
/** The definitions context: `pc:<configHash>` or `sh:<context>`. */
readonly context: string;
/** True when the app's sharing contract applies to this conversation. */
readonly shared: boolean;
}
/** An opened client: the ordinary `McpClient`, and the port back (`clientPort.ts`). */
export interface OpenedClient {
readonly client: McpClient;
readonly port: ClientPort;
}
/**
* How the pool opens one client. The real path is `openPooledClient`; a test
* injects its own (the `_open` seam).
*/
export type PoolClientOpener = (options: McpClientOptions, seam: PooledClientSeam) => Promise<OpenedClient>;
/**
* The plan for `key`: the transport the factory returns now, and the context
* its definitions belong to.
*
* @throws what the transport factory or `share` throws, and a TypeError when
* `share` answers with anything but two names or `undefined` — the caller
* refuses the open with `init-failed`, the error as its `cause`
*/
export declare function resolvePlan(settings: PoolSettings, key: PoolKey): ClientPlan;
/**
* The definitions context `key` belongs to now, for a client opened with
* `configHash`: the app's `share` asked again.
*
* @throws what `share` throws, and a TypeError when it answers with anything
* but two names or `undefined`
*/
export declare function contextFor(settings: PoolSettings, key: PoolKey, configHash: string): {
readonly context: string;
readonly shared: boolean;
};
/** SHA-256 over the transport's comparable data (see the module note). */
export declare function configHashOf(settings: PoolSettings, transport: McpTransport): string;