UNPKG

agentfootprint

Version:

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

90 lines • 3.89 kB
/** * pooledClient — one client INCARNATION: the conversation it serves, the plan it * was opened from, its leases, its own tool list, and where it is in its life. * * Pattern: a plain record the pool owns; no behaviour beyond its own counters. * Role: § 5 of the design. A client is OPENING until its open settles, OPEN * while it can serve, ENDING from the decision to end it until that * ending's work has settled, then ENDED. It is BUSY while any lease is * open — the library's, one per operation, or the app's — while an * operation that waited for its open has not yet taken its lease (a * RESERVATION), and while it opens; IDLE otherwise. A busy client is * never evicted, closed for idleness or ended. "Used" is the last lease * taken or released. * * Its `clientId` is the pool's own random id; its `generation` counts the key's * incarnations (1 = the key's first client). A replacement is a new record with * a new id, a new list and a count of its own: nothing of an old incarnation * can match it. */ import { DefinitionList } from './definitions.js'; /** A random id of the pool's own — never derived from an identity. */ function newClientId() { return globalThis.crypto.randomUUID().replace(/-/g, '').slice(0, 16); } /** One client incarnation. */ export class PooledClient { key; plan; generation; clientId = newClientId(); state = 'opening'; /** The ordinary `McpClient`, once opened. */ client; /** The port back (`clientPort.ts`), once opened. */ port; /** Open leases: one per library operation, one per app lease. */ leases = 0; /** * Operations waiting for its open, each holding it until it takes its lease — * so between the open settling and that lease nothing can evict or end it. */ reserved = 0; /** The last lease taken or released, on the pool's clock. */ usedAt; /** No new lease: `terminate` named it, its session expired, or its handle was lost. */ closedToNew = false; /** Its open ran past `initTimeoutMs`: refused to every waiter, still counted until it settles. */ abandoned = false; /** Waiting for an ending slot and an operation slot, and why. */ deferredEnd; /** This incarnation's own tool list. */ list = new DefinitionList(); /** * The guard of the open, listing or ending in progress on this client — what * a request that is not a pooled tool call is checked against and counted by. */ phase; /** The open in flight, with the work it owns. */ opening; /** How long the open took, once it settled within its deadline. */ initMs; /** Why it left its key without an ordinary ending: its session expired, or its handle was lost. */ lost; /** The outcome of its ending, once one started. */ endingOutcome; /** * The definitions context its tools are served under now. It starts as its * plan's; the app's `share` is asked again at every use, and a changed answer * moves it here — its connection, the plan's transport, never changes. */ definitions; constructor(key, plan, /** 1 = the key's first client, 2 = its first replacement, … */ generation, now) { this.key = key; this.plan = plan; this.generation = generation; this.usedAt = now; this.definitions = { context: plan.context, shared: plan.shared }; } /** Busy: a lease is open, an operation holds it between its open and its lease, or it is still opening. */ get busy() { return this.leases > 0 || this.reserved > 0 || this.state === 'opening'; } /** Can it serve a new lease right now? */ get serving() { return this.state === 'open' && !this.closedToNew; } } //# sourceMappingURL=pooledClient.js.map