UNPKG

agentfootprint

Version:

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

199 lines • 10.5 kB
import type { ToolAbsence } from '../semantics/coverage/types.js'; import { type TimeAskMessages, type TimeFormat } from '../time/ask.js'; import type { ZoneName } from '../time/zone.js'; /** Typed missing-input values. Collection is distinct from permission or consent. */ export type InputValue = string | number | boolean; export interface InputField { readonly id: string; readonly type: 'string' | 'number' | 'boolean'; readonly required?: boolean; readonly description?: string; readonly enum?: readonly InputValue[]; /** * A TIME field (time design § 6.1): the library checks the answer before the * app sees it. `'instant'` — an ISO 8601 date-time with its offset * (`2026-10-09T08:00-07:00`); `'time-range'` — an ISO 8601 interval * `from/to` of two such instants, `from` before `to`; `'zone'` — an IANA * zone name. The value stays a string on the wire, so refused unless * `type: 'string'`. An answer that fails the check is not taken: the resume * door asks again with `refused: { answer, reason }` (the reason a catalog * sentence, `defaultTimeAskMessages`) and `repeat: { count }`, and nothing * runs. A choice or a supplied value that fails it is refused at definition. */ readonly format?: TimeFormat; /** * One label per `enum` choice, in its order — what a person reads beside * the value (`'Fri, Oct 9, 2026, 8:00 – 8:40 AM PDT'`); the value is what the * answer carries. Needs `enum`. */ readonly labels?: readonly string[]; /** * A time field's choices are the only answers. Without it a `format` field * with `enum` keeps free entry open: any answer the format check takes is * taken. Needs `format` and `enum`. */ readonly strict?: boolean; } export interface InputRequestDeclaration { readonly id: string; readonly question: string; readonly fields: readonly InputField[]; /** Values the collection tool already knows; never labelled as a person's answer. */ readonly supplied?: Readonly<Record<string, InputValue>>; /** Opaque JSON authored by the collecting tool, never editable by the reply. */ readonly context?: Readonly<Record<string, unknown>>; /** * What the tool LOOKED AT before it asked (9.114.0): the envelope `absent()` * returns, when a lookup found nothing and raises this request about the * miss. Recognized at raise time by the one recognizer * (`../semantics/coverage/absent.ts` · `readAbsence`) — anything it does not read * is refused, and `null` is the field omitted — and filed by the dispatch * door at the raise, before the * checkpoint is returned: the same `tools.absent` event and * `coverageDeclared` row a RETURNED absence files * (`core/agent/stages/toolCalls.ts` · `declareRaisedAbsence`). Data for the * record, not for the ask: it never rides the awaiting-input shape or the * paused call's served result, and nothing on resume reads it, so the * pending question says the miss only if `question` says it in words. * Under `.limitsTravelWithTheAnswer()` the final answer's limits block * carries it, as it does a returned miss. */ readonly absence?: ToolAbsence; /** * The previous answer to this ask was REFUSED, and why: the app * validated what the person gave, turned it down, and asks again. Carried * on the awaiting-input shape the person receives — the checkpoint's * `pauseData`, the pause outcome, the `pause.request` event — so a UI can * say "Your answer '…' was not accepted: <reason>" instead of repeating * the same question in silence. The reason is the APP'S words — with one * exception the app arms itself: a time field's answer the library's check * refused (`InputField.format`), whose reason is a catalog sentence the app * can override (`defaultTimeAskMessages`, `.time({ messages })`). `answer` * is optional and judged against `fields` like any answer; `null` is the * field omitted. */ readonly refused?: InputRefusal; } /** An app's refusal of the previous answer to the same ask — see `InputRequestDeclaration.refused`. */ export interface InputRefusal { /** The refused values, field id → value, as the person gave them. */ readonly answer?: Readonly<Record<string, InputValue>>; /** Why it was refused, in the app's own words — or, for a time field the library checked, its catalog's (non-blank, at most 4096 characters). */ readonly reason: string; } /** * The runtime's mark on a RE-ASK: the same ask (same declaration * `id`) raised again in the same turn after the person answered it. Facts * only — never a reason. Stamped by the runtime, never declarable. */ export interface InputRepeat { /** How many times the person has already answered this ask in this turn. */ readonly count: number; /** * The person's previous answer (the fields they supplied) as the RECORD * holds it — after the tool-result rules, redaction first among them, ran * on it. Absent when the record no longer holds it in that shape (a rule * replaced the result, placement moved it), never reconstructed. */ readonly previousAnswer?: Readonly<Record<string, InputValue>>; } /** The stamped request as the person, the model and the durable pause read it — never the `absence`. */ export interface AwaitingInput extends Omit<InputRequestDeclaration, 'absence'> { readonly status: 'awaiting_input'; /** Runtime-stamped token, distinct from the author's reusable declaration id. */ readonly requestId: string; readonly supplied: Readonly<Record<string, InputValue>>; readonly origins: Readonly<Record<string, 'declaration' | 'response'>>; readonly missing: readonly string[]; /** Present only on a re-ask — see `InputRepeat`. */ readonly repeat?: InputRepeat; readonly origin: { readonly originalRequest: string; /** * The call that raised the request. For the inputs layer's own batch ask * (`context.agentfootprint.ask === 'arguments'`, honesty layer 2) no single * call raised it — the library asked before anything in the batch ran — so * this names the batch's FIRST asked call, and `context.agentfootprint.fields` * lists every call each field is for. */ readonly toolCallId: string; readonly skillId?: string; readonly offeredSkillIds?: readonly string[]; }; } export interface InputResponse { readonly requestId: string; readonly values: Readonly<Record<string, InputValue>>; } export interface InputCancellation { readonly requestId: string; readonly cancel: true; } /** The dedicated collecting tool's result; it contains inputs, not observations. */ export interface InputResponseResult { readonly status: 'input_received'; readonly requestId: string; readonly values: Readonly<Record<string, InputValue>>; readonly origins: AwaitingInput['origins']; readonly context?: InputRequestDeclaration['context']; readonly origin: AwaitingInput['origin']; } /** A malformed or stale data reply; no value was consumed and the ask remains live. */ export declare class InputRequestError extends TypeError { readonly code: "ERR_INPUT_REQUEST_INVALID"; constructor(reason: string); } /** * A value a typed ask can carry in a field — as a supplied value, a choice or * an answer: a non-blank string of at most 4096 characters, a finite number, * or a boolean. The one rule, shared by this module's validation and by the * declarations that build a typed ask (`tools/declare.ts` judges an * `ask` rule's choices with it at definition). */ export declare function isInputFieldValue(value: unknown): value is InputValue; /** One validation owner, shared by the declaration and durable-pause readers. */ export declare function validateInputDeclaration(raw: unknown): InputRequestDeclaration; /** Where the same ask was last answered in this turn — what the runtime knows at a re-ask. */ export interface AnsweredAsk { /** The declaration `id` that was answered. */ readonly id: string; /** The answered request's runtime token — how its landed result is found in the record. */ readonly requestId: string; /** How many times this ask has been answered in this turn, this answer included. */ readonly count: number; } /** * The re-ask mark for a declaration, or `undefined` when this is not a re-ask. * The previous answer is READ FROM THE RECORD — the * `input_received` result that landed for `answered.requestId`, after the * tool-result rules ran on it — so it is redacted exactly as the answer is. * Only the fields the PERSON supplied (origin `'response'`) are kept; when * the record holds no such result, or its values no longer satisfy the * fields, the mark carries the count alone. */ export declare function repeatOf(declaration: InputRequestDeclaration, answered: AnsweredAsk | undefined, history: readonly { readonly role: string; readonly content: unknown; }[]): InputRepeat | undefined; /** Runtime-only stamping: a model cannot choose its saved skill or original request. */ export declare function stampInputRequest(declaration: InputRequestDeclaration, requestId: string, origin: AwaitingInput['origin'], repeat?: InputRepeat): AwaitingInput; /** Read only an explicitly declared, well-shaped input pause; never assistant prose. */ export declare function readAwaitingInput(pauseData: unknown): AwaitingInput | undefined; /** * What the resume door knows when it checks a time field's answer: the * person's zone (the run clock's, under `.time()` — it arms the DST-gap check) * and the app's catalog overrides (`.time({ messages })`). */ export interface TimeAnswerContext { readonly zone?: ZoneName; readonly messages?: Partial<TimeAskMessages>; } /** * Accept typed fields, or explicit cancellation, without coercing free text. * A time field's answer (`InputField.format`) is checked here too; one the * check refuses comes back as the same ask with `refused` and `repeat`, its * field `missing` again — the door that keeps a partial answer asks again * without running anything. */ export declare function applyInputResponse(waiting: AwaitingInput, raw: unknown, time?: TimeAnswerContext): AwaitingInput | InputCancellation; //# sourceMappingURL=inputRequest.d.ts.map