UNPKG

agentfootprint

Version:

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

95 lines (94 loc) • 5.03 kB
/** * terminate — ending a client's remote session, and saying what the remote side * answered. * * Pattern: a pass-through `fetch` that reads one status + one bounded procedure. * Role: `McpClient.terminate()` (`mcpClient.ts`). The MCP SDK's * `StreamableHTTPClientTransport.close()` only aborts the requests in * flight; `terminateSession()` is the one method that sends the * Streamable HTTP `DELETE` carrying `Mcp-Session-Id`. It treats a 405 * as success, so the status is read here, by a pass-through `fetch` * the library installs on every transport it builds — it changes no * request and reads only a `DELETE`'s answer. * * The procedure, at most once per client: * * 1. no session id was ever issued → `'no-session'`, nothing is sent; * 2. otherwise the transport's own `terminateSession()`, bounded by * `TOOL_TEARDOWN_TIMEOUT_MS` (the tool-session tier's `withTimeout`, shared): * at the deadline the request is aborted and the outcome is `'timed-out'`; * 3. a local close, whatever happened. * * ONE DEADLINE ANSWERS THE CALLER: the remote answer and the local close are * waited for together for at most `TOOL_TEARDOWN_TIMEOUT_MS` of ELAPSED time, * measured by timers started together at the start — never by reading the wall * clock, so a clock stepped back cannot stretch it. A close that finishes in * time is finished before the caller hears; one that hangs does not hold the * answer. The work's own settlement — the `DELETE`, a `fetch` that ignores its * abort, the close — may come later, and is returned apart (`settled`): the MCP * client pool keeps an ending charged until then. The clock (`now`) only * measures `durationMs`. * * Never vendor-aware: the one port is the Streamable HTTP `DELETE`. A transport * that turns it into something else (AgentCore Runtime's `StopRuntimeSession`) * answers it like a server would. */ import type { McpClient, McpTerminateOutcome } from './types.js'; /** An `McpClient` that can end its remote session. */ export type TerminableMcpClient = McpClient & { terminate(): Promise<McpTerminateOutcome>; }; /** * Can `client` end its remote session? Reads the member, never assumes it — * the feature-detection law (`provider-contract/types.ts` · `canStageCodeInputs`). * True for `mcpClient()` and `mockMcpClient()`; false for a hand-written client * without `terminate`. */ export declare function canTerminate(client: unknown): client is TerminableMcpClient; /** What ending one client came to: the caller's answer, and when the work under it settled. */ export interface Ending { /** Answers by `TOOL_TEARDOWN_TIMEOUT_MS`. Never rejects. */ readonly outcome: Promise<McpTerminateOutcome>; /** Resolves once every piece of work the ending started has settled. Never rejects. */ readonly settled: Promise<void>; } /** The slice of the SDK's Streamable HTTP transport the procedure uses. */ export interface TerminableTransport { readonly sessionId?: string; terminateSession(): Promise<void>; } /** * What the pass-through `fetch` saw of a transport's `DELETE` requests: the * status of the latest answer, and the signal it links into each one while an * ending is under way. */ export declare class DeleteWatch { private status; private cut; /** Arm for one ending: forget the last status, link `cut` into its `DELETE`. */ arm(cut: AbortSignal): void; /** The status the latest `DELETE` was answered with, if one was. */ get answered(): number | undefined; /** * `inner` (the global `fetch` when `undefined`, looked up at call time), * passed through untouched except that a `DELETE`'s answer status is read and * an armed ending's signal is linked into it. */ fetch(inner: ((input: string | URL, init?: RequestInit) => Promise<Response>) | undefined): (input: string | URL, init?: RequestInit) => Promise<Response>; } /** The outcome for an ending that sent nothing. */ export declare function outcomeWithout(remote: 'no-session' | 'unavailable', durationMs?: number): McpTerminateOutcome; /** * An ending that sends nothing — `remote` says why — only the local close: * the caller is answered when the close finishes or at `timeoutMs`, whichever * comes first; `settled` waits for the close itself. */ export declare function endLocally(remote: 'no-session' | 'unavailable', close: () => Promise<void>, now?: () => number, timeoutMs?: number): Ending; /** * End `transport`'s session, then close locally with `close` — the procedure * in the module note. `watch` is the `DeleteWatch` installed on the transport's * fetch. `now` is the clock the duration is measured with. */ export declare function endSession(transport: TerminableTransport, watch: DeleteWatch, close: () => Promise<void>, now?: () => number, timeoutMs?: number): Ending; /** A failure's class name, never its message. */ export declare function classOf(error: unknown): string;