/** * sim/inference.ts — the planner's per-step reasoning body, ported from * mirofish `engine/agent/inference.py` onto pi-ai. * * decide(): (persona + history + observation) → (thought + actions) + cost, * via native tool-calling in ONE model call. Pure data out — actions are not * executed here (that is agent.execute's job). * * Ported defenses (each one bought with a real failure): * - broken-ReAct repair: the model wrote the action into its monologue and * called no tool → one bounded retry with a repair hint; * - empty-monologue repair: reasoning models hide their thinking → ask for * one in-character line (no tools bound), so the trace keeps a human voice; * - tool-call unwrapping: hallucinated `multi_tool_use.parallel` namespaces * and `functions.` prefixes are unwrapped or actions silently vanish; * - any exception degrades to a SILENT step with `error` set — the session * never explodes mid-run; consecutive errors trip the agent's breaker. */ import { type Api, type AssistantMessage, type Context, type Model, type MutableModels, type ThinkingLevel } from "@earendil-works/pi-ai"; import type { Action, Part, SessionFrame, SimRuntime, StepRecord } from "./models.ts"; /** Default model spec: sim knob first, then the pi-gui default chain. */ export declare function defaultSimModel(env?: Record): string; /** Planner default effort: minimal reasoning — fidelity comes from the prompt, * not from chain-of-thought depth. */ export declare const DEFAULT_EFFORT: ThinkingLevel; /** Per-step output budget. Real steps run ~570 tokens; 5× headroom. */ export declare const DEFAULT_MAX_TOKENS = 3000; export interface CompleteOptions { reasoning?: ThinkingLevel; maxTokens?: number; signal?: AbortSignal; /** Retry attempts; unset reads LLM_MAX_ATTEMPTS (default 5). Never hardcode * a value here — that would silently disconnect the env knob (a real * incident: the knob reached the executor but not the planner). */ attempts?: number; } /** * completeSimple with transient-failure retries (message-string heuristics + * exponential backoff capped by LLM_BACKOFF_CAP_S). pi-ai surfaces failures as * stopReason "error" rather than throwing; both paths are handled. */ export declare function completeResilient(models: MutableModels, model: Model, ctx: Context, opts?: CompleteOptions): Promise; /** Extract the monologue: text blocks first (filtering the occasional base64 * garbage some providers emit), thinking blocks as fallback. */ export declare function extractThinking(resp: AssistantMessage): string; /** tool calls → actions, unwrapping two known OpenAI-family quirks: * hallucinated `multi_tool_use.parallel` batches and `functions.` prefixes. * Without this, actions come back empty and the agent hallucinates progress. */ export declare function flattenToolCalls(calls: Array<{ name: string; arguments: Record; }>): Action[]; /** Broken-chain signature: zero tool calls but the monologue contains * "toolname(" or "做:toolname" — the model is imitating trajectory format * instead of acting. "做:(沉默)" does NOT match (silence is legitimate). */ export declare function intentInText(thought: string, toolNames: string[]): boolean; /** The folded context: system + ONE user message (trajectory text + current * screenshots as image blocks). The planner only sends the current screen — * long-range causality rides in the text history. */ export declare function buildContext(frame: SessionFrame, observation: Part[], opts?: { system?: string; user?: string; runtime?: SimRuntime | null; }): Context; export interface DecideOptions { runtime?: SimRuntime | null; /** "provider/model-id"; unset → defaultSimModel(). */ model?: string; thinkingLevel?: ThinkingLevel | null; maxTokens?: number; debug?: boolean; /** Test seam: pre-configured registry (faux provider) instead of dynamic loading. */ models?: MutableModels; signal?: AbortSignal; } /** * One reasoning step: observation → (thought, actions) with cost (+ optional * debug). Produces data only. The planner vocabulary is the control actions * plus (with a runtime) operate — never externally injected uses. */ export declare function decide(frame: SessionFrame, observation: Part[], opts?: DecideOptions): Promise;