UNPKG

agentfootprint

Version:

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

85 lines (84 loc) • 4.05 kB
/** * mcp/elicitation — a typed ask carried as an MCP elicitation, and the * elicitation's answer carried back (time design § 6.1). * * Pattern: Adapter, pure, both directions in one file so they cannot drift. * `elicitationOf` maps a pending `AwaitingInput` onto the MCP * elicitation request (`message` + a flat `requestedSchema` of * primitive properties, the MCP specification revision of * 2025-06-18); `answerFromElicitation` maps the client's `content` * back to the `InputResponse` `agent.resume` takes. Nothing is * checked here that the resume door checks: the answer goes through * `tools/inputRequest.ts` · `applyInputResponse` like any other, and a * refused time answer comes back as a re-ask to elicit again. * Role: mcp (the transport). Reads `tools/inputRequest.ts` types only. * Emits: N/A. * * ## The mapping * * MCP's elicitation schema holds strings (with a `format` of `date-time` * among a few), numbers, booleans and string choices (`enum` + `enumNames`). * It has no range, so a time field becomes the properties a person can fill: * * | Field | Properties | * |-------|------------| * | `string` / `number` / `boolean` | `<id>`, as typed; a string's `enum` with its `labels` as `enumNames` | * | `format: 'instant'` | `<id>`: `{ type: 'string', format: 'date-time' }` | * | `format: 'zone'` | `<id>`: a string | * | `format: 'time-range'` | `<id>.from` and `<id>.to`: two `date-time` strings, joined back as `from/to` | * | a `format` field with choices | `<id>`: the choices, labelled; unless `strict`, ALSO the free-entry properties above (`<id>.other` for an instant or a zone) — answer one or the other | * * Only the fields still open are asked (a supplied value is known). A * property is `required` only when it is the one way to answer a missing * field. A number or boolean with `enum` has no MCP spelling and is refused, * never widened; so is a field id that collides with a generated name. * * @example * ```ts * const out = await agent.run({ message }); * if (isInputPause(out)) { * const request = elicitationOf(out.awaitingInput); * const reply = await mcpClientSession.elicit(request); // { action: 'accept', content } * if (reply.action === 'accept') * await agent.resume(out.checkpoint, answerFromElicitation(out.awaitingInput, reply.content)); * } * ``` */ import { type AwaitingInput, type InputResponse } from '../tools/inputRequest.js'; /** One primitive property of an elicitation's `requestedSchema`. */ export type ElicitationProperty = { readonly type: 'string'; readonly description?: string; readonly format?: 'date-time'; readonly enum?: readonly string[]; readonly enumNames?: readonly string[]; } | { readonly type: 'number'; readonly description?: string; } | { readonly type: 'boolean'; readonly description?: string; }; /** The MCP `elicitation/create` request's params. */ export interface ElicitationRequest { readonly message: string; readonly requestedSchema: { readonly type: 'object'; readonly properties: Readonly<Record<string, ElicitationProperty>>; readonly required?: readonly string[]; }; } /** * The MCP elicitation for a pending typed ask: its question (after the * refusal's reason, on a re-ask) and one property per way to answer each open * field. Throws `InputRequestError` on a field MCP cannot carry. */ export declare function elicitationOf(awaiting: AwaitingInput): ElicitationRequest; /** * The `InputResponse` an accepted elicitation's `content` names — each open * field's value, a time range's two properties joined as `from/to`. Throws * `InputRequestError` on a property the elicitation did not ask, on a choice * AND a free entry for one field, and on half a range. The values are judged * by `agent.resume`, as any answer is. */ export declare function answerFromElicitation(awaiting: AwaitingInput, content: unknown): InputResponse;