UNPKG

agentfootprint

Version:

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

141 lines (140 loc) • 6.29 kB
/** * AgentThinkingTraceRecorder — produce an AgentThinkingUI `Trace` from a run. * * AgentThinkingUI (the "watch it think" player) consumes a framework-agnostic * `Trace` — a list of beats: `prompt → ask → return → answer`, where a tool * reply is `data` (reason) or an `instruction` (a skill that says how to act). * This recorder builds that `Trace` from agentfootprint's emit stream AS THE RUN * TRAVERSES (no post-processing) — so any agentfootprint agent gets the * domain-expert view for free, and AgentThinkingUI stays vendor-agnostic (it * just renders the `Trace` JSON, exactly as it renders the OTLP adapter's output). * * Mapping (from the events already on the stream): * stream.llm_end (toolCalls>0) → the brain reasoned; content + usage become * the upcoming ask's `brain` + `cost`. * stream.llm_end (toolCalls==0) → the final `answer`, unless contentWithheld. * agent.turn_end → a withheld answer becomes a beat only on * accepted delivery, using finalContent. * stream.tool_start → `ask` (read_skill → reaching for a skill). * stream.tool_end → `return` (read_skill → replyType:'instruction' * + skill; any other tool → replyType:'data'). * a bracket carrying → no beat (9.113.0): a call a paused batch * `notDispatched` never dispatched neither asked nor returned. * * Commentary (each beat's `brain`): filled from agentfootprint's OWN commentary * engine — the SAME `selectCommentaryKey`/`extractCommentaryVars`/`renderCommentary` * the Lens uses — so AgentThinkingUI's Notepad / bottom caption read identically * to the Lens commentary panel (one engine, consumer-overridable via * `commentaryTemplates`). The LLM's own reasoning still wins on the first ask of * an iteration; the engine fills every other beat so no line is ever blank. * * Convention 1 (one purpose) + Convention 4 (run-scoped — resets per run). */ import type { EmitRecorder } from 'footprintjs'; import { type CommentaryTemplates } from './commentary/commentaryTemplates.js'; export interface AttCost { ms: number; tokens: number; tokensIn?: number; tokensOut?: number; tokensCached?: number; } export interface AttAnswer { headline: string; [key: string]: unknown; } /** One tool the model had at its disposal for an LLM call — what it "saw" when it * decided. Lets a domain expert expand and read the menu next to the model's * reasoning, to debug WHY it chose (or skipped) a tool. */ export interface AttToolSeen { readonly name: string; readonly description?: string; } export type AttStep = { kind: 'prompt'; brain: string; cost: AttCost; } | { kind: 'ask'; tool: string; toolName?: string; /** The call's id on the record (9.111.0) — the key every ledger row about * this call carries, so a reader can join the beat to its declarations * without guessing by name or order. Absent only on a trace recorded * before the field existed. */ toolCallId?: string; input: Record<string, unknown>; brain: string; cost: AttCost; /** Model's extended-thinking chain-of-thought for this iteration (when the * provider emitted reasoning blocks). Rendered as the "thinking" callout. */ thinking?: string; /** The tools the model saw (name + description) for this iteration's call — * the menu it chose from. On the iteration's first ask only. */ toolsSeen?: readonly AttToolSeen[]; } | { kind: 'return'; tool: string; toolName?: string; /** The call this result answers (9.111.0) — the same id as its ask beat. */ toolCallId?: string; replyType: 'data' | 'instruction' | 'both'; output: Record<string, unknown>; brain: string; /** Who authored `brain`: the model's own words, or a framework delivery * sentence. Consumers (the notepad) prefix "LLM reasons —" only for * model-authored text; absent = model (the pre-field-report default). */ brainSource?: 'model' | 'framework'; cost: AttCost; brainMode?: 'reason' | 'act'; skill?: string; error?: string; } | { kind: 'answer'; to: string; brain: string; answer: AttAnswer; cost: AttCost; error?: string; /** Model's extended-thinking chain-of-thought before the final answer. */ thinking?: string; /** The tools the model saw (name + description) for the final call. */ toolsSeen?: readonly AttToolSeen[]; }; export interface AttTrace { task: string; title?: string; agent: string; model: string; asker: string; steps: AttStep[]; /** * The stage each step was recorded under — `at[i]` is the `runtimeStageId` * of the emit that produced `steps[i]` (the prompt step, made by * `getTrace`, carries `''`: it names no stage). Index-aligned with `steps` * so a story beat can be placed on the run's own cursor axes; a trace * recorded before 9.99.0 has no `at`. */ at?: readonly string[]; } export interface AgentThinkingTraceOptions { readonly id?: string; readonly agent?: string; readonly model?: string; readonly asker?: string; /** * Override agentfootprint's bundled commentary templates — the SAME shape as * the Lens's `commentaryTemplates` prop (partial; spread over the defaults). * Drives each beat's `brain` narration, so AgentThinkingUI's Notepad / bottom * caption read like the Lens commentary panel — one engine, one voice, * consumer-overridable. Omit to use the bundled English defaults. */ readonly commentaryTemplates?: Partial<CommentaryTemplates>; } export interface AgentThinkingTraceHandle extends EmitRecorder { /** The AgentThinkingUI `Trace` for the run so far. `task` (the headline of the * replay pill) defaults to the captured user message; override any field. */ getTrace(overrides?: Partial<Pick<AttTrace, 'task' | 'title' | 'agent' | 'model' | 'asker'>>): AttTrace; clear(): void; } export declare function agentThinkingTrace(options?: AgentThinkingTraceOptions): AgentThinkingTraceHandle;