import type * as smoltalk from "smoltalk"; import type { AgencyFunction, FuncParam } from "./agencyFunction.js"; /** A call is refused once the same tool, with the same arguments, has * returned the same result this many times in a row, with no other call in * between. That is the loop signature: nothing changed, yet the model asks * again (a writer once made 45 identical, successful typecheck calls). The * refusal also restarts the count, so the call is interrupted every N * repeats rather than banned: a status poll that really is waiting on the * world gets to run again after the model says so. `0` disables. */ export declare const DEFAULT_MAX_REPEATED_TOOL_CALLS = 3; /** The name of an optional string argument whose value is the model's own * tool-call markup, or null. Claude sometimes emits the closing tag of its * call syntax where an optional string it meant to leave empty belongs, * and the next parameter's text leaks in: * `stdin: "\n[]"`. * Running that call wastes a round at best (`grep` rejects the regex * flags) and at worst executes with garbage. Deliberately narrow: only a * parameter with a default, and only a value that IS the closing tag * (named for this argument, or followed by leaked parameter markup). A * required parameter, or a string that merely contains such text, is data * a transcript or XML tool may legitimately be given. */ export declare function markupArgument(args: Record, params: readonly FuncParam[]): string | null; export declare function markupArgumentMessage(toolName: string, argument: string): string; /** The key is stored on the checkpoint, and checkpoints are kept as JSON * in places that reject U+0000 (Postgres jsonb, for one), so the two parts * are joined with a colon. A namespaced tool name has colons of its own, * but the digest is always 64 hex characters, so two different calls can * never produce the same key. */ export declare function repeatKey(toolName: string, args: Record): string; /** The current run of identical calls: one record, because only calls in a * row count. Any other call, a different result, or a refusal resets it. */ export type RepeatStreak = { key: string; result: string; count: number; }; export declare function freshRepeatStreak(): RepeatStreak; /** Record one completed call (`result` is the stringified tool result) and * return how many times in a row this exact call has now produced it. */ export declare function noteRepeat(streak: RepeatStreak, key: string, result: string): number; /** How many identical runs in a row precede a call with this key. */ export declare function repeatsBefore(streak: RepeatStreak, key: string): number; export declare function resetRepeat(streak: RepeatStreak): void; export declare function repeatedCallMessage(toolName: string, count: number): string; /** The refusal-gate verdict for one tool invocation. Computed exactly * once, inside its own step, and persisted in runnerState: most gates * read frame state that mutates as sibling branches complete, so a * verdict recomputed on resume could differ from the one the call * originally acted on. */ export type GateVerdict = "removed" | "unhandled" | "tooManyRounds" | "tooManyHandoffs" | "markup" | "priorRejected" | "repeated" | "proceed"; /** The state of one llm() call that the gate reads. The arrays and the * streak are the live records on the runPrompt frame, which change as * sibling calls finish. */ export type GateState = { removedTools: string[]; rejectedCalls: string[]; repeatStreak: RepeatStreak; toolCallRound: number; maxToolCallRounds: number; maxRepeatedToolCalls: number; }; /** One call as the gate sees it. */ export type GateInput = { toolCall: smoltalk.ToolCallJSON; handler: AgencyFunction | null; markupArg: string | null; callKey: string; }; export type GateCall = GateInput & { /** The handoff calls of this round that pass every other check, from * `runnableHandoffs`. */ runnableHandoffs: string[]; }; /** The names of the handoff calls in a round that pass every check * except the one-handoff-per-round rule. Only these count toward that * rule: a handoff that is refused for another reason would not run * anyway, so it must not stop the one that would. Reads mutable state, * so callers compute it once per round, inside a step. */ export declare function runnableHandoffs(state: GateState, inputs: GateInput[]): string[]; /** The refusal-gate decision for one call, in refusal-priority order. * Reads mutable state, so callers must run it exactly once, inside the * call's `.gate` step, and persist the verdict in runnerState. */ export declare function computeGateVerdict(state: GateState, call: GateCall): GateVerdict; /** The model-facing text for a refusal verdict. */ export declare function refusalMessage(state: GateState, verdict: Exclude, call: GateCall): string; /** The result cap of one `llm()` call: per-call `maxToolResultChars`, else * the branch default (setLlmOptions), else agency.json, else the default. * `0` disables. What happens to a result over the cap is * `capToolResultForLlm` in toolInvocation.ts. */ export declare function resultCap(perCall: number | undefined, branch: number | undefined, baked: { maxToolResultChars?: number; }): number; /** The round cap of one `llm()` call. The call's own value and the branch * default (setLlmOptions) are both ceilings, so when both are set the * smaller wins; with neither, the baked value from agency.json. See * "The round cap" in docs/dev/agents/tool-loop-guards.md for the two * cases that decide this. */ export declare function roundCap(perCall: number | undefined, branch: number | undefined, baked: number): number; /** The tools a request may offer. Below the cap, all of them. At the cap, * which is the request after the last allowed round, only the intrinsic * tools (saveDraft), which the cap never applied to: with nothing else to * call the model can only answer. The `tooManyRounds` refusal is the * fallback for a model that calls a tool it was not offered. * `functions` are the tools' functions, for telling an intrinsic one. */ export declare function toolsOffered(tools: T[], functions: AgencyFunction[], atCap: boolean): T[]; /** The request config at the cap: provider-hosted tools dropped, since * they travel in the config rather than the tool list and a model could * otherwise keep searching inside the last request. Unchanged below it. */ export declare function configOffered(config: T, atCap: boolean): T;