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 • 4.09 kB
TypeScript
/**
* 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;
//# sourceMappingURL=elicitation.d.ts.map