agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
66 lines (65 loc) • 3.17 kB
TypeScript
/**
* runInput — the one door every runner's `run()` input goes through.
*
* Pattern: normalize-or-refuse at a boundary (the shape `toolArgsValidation`
* uses for a model's arguments, applied to a caller's).
* Role: core/ layer. `Agent`, `LLMCall` and the compositions all take the
* same `{ message }` bag, and until 8.18.0 none of them looked at it.
* A caller who wrote `agent.run('go')` — the shape every chat SDK
* takes — reached `buildMessagesSlot` with `content: undefined` and
* got `TypeError: Cannot read properties of undefined (reading
* 'length')` five frames inside the engine. `LLMCall.run('go')` was
* worse: it called the model with ZERO messages and returned an
* answer, so the mistake looked like it worked.
* Emits: N/A — this runs before any chart does.
*
* ## Two answers, and the rule that picks between them
*
* A bare string is **adapted**: `run('go')` means `run({ message: 'go' })`.
* `AgentInput` has one required field, so a lone string has exactly one
* possible reading, and refusing it would be a speed bump rather than a
* teaching. The adaptation is stated here, in the types, and in the docs —
* an adaptation nobody can see is indistinguishable from a guess.
*
* Everything else is **refused, by name**. `{}`, `{ message: undefined }`,
* `{ message: 42 }`, `null`, `''` are not one sane meaning each; they are a
* caller who believes they passed a message and did not. The refusal says
* what arrived, and spells the two accepted forms. It fires before the run
* starts, so nothing is billed and no half-run has to be explained.
*
* An empty (or whitespace-only) message is refused for the same reason it
* cannot work: `Agent` used to send a `content: ''` turn and `LLMCall` used
* to send no turn at all — the two runners disagreed, and both shapes are a
* 400 on a real provider wire. "Run with only the system prompt" is a real
* intention, and the refusal names how to say it: put the instruction in
* the message.
*/
/**
* Thrown by `run()` (and `runTyped()`) when the input is not a message.
*
* `received` is a short, non-leaking description of what arrived — the type
* and, for an object, its keys. Never the value: an input that was refused
* for being the wrong shape is still the caller's data.
*/
export declare class InvalidRunInputError extends Error {
readonly code: "ERR_INVALID_RUN_INPUT";
/** Short description of what was passed, e.g. `'object with keys: text'`. */
readonly received: string;
/** Which runner refused, e.g. `'Agent.run'`. */
readonly runner: string;
constructor(ctx: {
runner: string;
received: string;
hint: string;
});
}
/**
* Normalize a runner input to `{ message, ...rest }`, or refuse.
*
* `runner` names the door in the error (`'Agent.run'`, `'LLMCall.run'`, …).
* Extra fields on an object input are preserved untouched — `identity` and
* anything a subclass adds ride through.
*/
export declare function normalizeRunInput<T extends {
message: string;
}>(input: unknown, runner: string): T;