import type { MessageThread } from "./state/messageThread.js"; import type { StateStack, TurnMarks, TurnScope } from "./state/stateStack.js"; export type { TurnMarks, TurnScope }; /** The refusal for a handoff call made in the same response as another * handoff call. `names` lists the round's handoff calls, one entry per * call, so a tool called twice appears twice. */ export declare function tooManyHandoffsMessage(toolName: string, names: string[]): string; /** The text for a handoff whose round stopped before it could start, * for example when a guard trip was rejected. It answers the handoff's * tool call, which would otherwise be left without a result. */ export declare function handoffNotStartedMessage(toolName: string, reason: string): string; /** The scope key one dispatch tags its body's system messages with. The * nesting depth is part of it because a provider that sends no call ids * (Gemini) leaves a handoff nested inside itself with the same name and * the same empty id. Compute it before entering the scope and again after * leaving it; the depth is the same at both points. */ export declare function handoffScopeKey(thread: MessageThread, toolName: string, toolCallId: string): string; export declare function handoffResumeText(toolName: string, body: string): string; /** The resume message for a handoff that failed or was aborted partway. */ export declare function handoffStoppedText(toolName: string, reason: string): string; /** * Drop the handoff's tool call from the assistant message that carried * it: the last assistant message on the thread. The other calls in the * round have run by now, so their tool results, and any guard feedback, * may follow that message. `call.index` is the call's position in the * message's tool calls. * * When other calls remain, only this one is removed, so their tool * results still pair with them. When it was the only call, its text * stays and a message that was only the call is removed, so the thread * reads as the user's request followed by the body's work. * Nothing is added in its place: a model that sees dispatch narration in * its history learns to write it instead of calling the tool. */ export declare function dropHandoffToolCall(thread: MessageThread, call: { index: number; id: string; name: string; }): void; /** * Remove the system messages the body pushed: every message that expires * with this dispatch. See docs/dev/runtime/expiring-messages.md. */ export declare function stripHandoffSystemMessages(thread: MessageThread, scopeKey: string): void; /** * Close a handoff: remove the body's system messages (its persona is not * useful to the caller afterwards and would grow the context on every * dispatch), then hand control back with a user-role message that carries * the body's result. */ export declare function finishHandoff(args: { thread: MessageThread; scopeKey: string; toolName: string; body: string; }): void; /** Close a handoff that failed or was aborted. */ export declare function finishStoppedHandoff(args: { thread: MessageThread; scopeKey: string; toolName: string; reason: string; }): void; /** A marked handoff's hand-back, held until the round's decision step * knows whether the turn ends. */ export type DeferredHandBack = { scopeKey: string; body: string; message: string | null; }; /** One marked call, kept in runnerState.turnMarks[round][callIndex]. Only * calls that set something are recorded, and the answer value only when * endTurn is set: runnerState rides in every later checkpoint. */ export type TurnMark = { toolName: string; isHandoff: boolean; endTurn: boolean; scope: TurnScope; message: string | null; /** The answer value; present only when endTurn is set. */ value?: unknown; /** Set when a marked handoff deferred its hand-back message. */ deferredHandBack: DeferredHandBack | null; }; /** The value a tool answered with: unwrapped from success(), as the model * sees it, but without the "ran successfully" placeholder the tool loop * substitutes for a missing value. A tool that returned nothing answers * null, which is not a string, so it never ends the turn. */ export declare function answerValueOf(rawToolResult: unknown): unknown; /** Record a finished call's marks in runnerState.turnMarks[round][callIndex], * but only when something was set, and the answer value only when endTurn * is: runnerState rides in every later checkpoint of the llm() call. */ export declare function recordTurnMark(args: { runnerState: Record; round: number; callIndex: number; toolName: string; isHandoff: boolean; marks: TurnMarks; rawToolResult: unknown; deferredHandBack: DeferredHandBack | null; }): void; /** How a handoff's body finished, from the tool loop's point of view. */ export type HandoffOutcome = { kind: "success"; body: string; } | { kind: "stopped"; reason: string; } | { kind: "rejected"; body: string; }; /** * Close a handoff: strip the body's system messages, then hand control * back with a user-role message, or defer that message when the handoff * asked to end the turn. The custom text from handBack replaces the * default when the outcome allows it: never for a rejection, whose text * tells the model the user said no, and never when empty, because a * follow-up request needs a user-role message in front of it. Returns * the deferred record for the decision step, or null when the message * was pushed here. */ export declare function closeHandoff(args: { thread: MessageThread; scopeKey: string; toolName: string; outcome: HandoffOutcome; marks: TurnMarks; warn: (message: string) => void; }): DeferredHandBack | null; /** How a handoff's body finished, for closeHandoff. */ export declare function handoffOutcomeFor(body: string, stoppedReason: string | undefined, rejected: boolean | undefined): HandoffOutcome; export type EndTurnDecision = { kind: "end"; callIndex: number; scope: TurnScope; answerText: string; returnValue: unknown; } | { kind: "continue"; }; /** * Decide from a round's recorded marks whether the turn ends. A mark * counts only when its call was the last to run in the round, since only * then has the tool seen everything the model asked for: a handoff (it * runs alone after the others), or an ordinary tool that was the round's * only call that ran. Then the value has to fit what llm() returns: the * caller's structured output type, or a string. Calls `warn` once per * mark that is dropped or falls back, naming the tool and the reason. */ export declare function decideEndTurn(args: { marks: Record; /** Dispatch indexes of the calls that ran (success, failure, or rejection). */ ranCallIndexes: number[]; responseFormat: unknown; pendingAttachments: number; warn: (message: string) => void; }): EndTurnDecision; /** The record the tool loop keeps in runnerState.turnEnded[round]. */ export type TurnEnded = { ended: true; returnValue: unknown; } | { ended: false; }; /** * Apply a decision to the thread. On "end", make the thread end with the * answer, and when the scope is "turn" and this llm() call runs inside a * tool body, re-mark that body's stack so the enclosing loop ends too. On * "continue", push every deferred hand-back, so the follow-up request has * its user-role message. Returns the record for runnerState. */ export declare function applyEndTurnDecision(args: { thread: MessageThread; decision: EndTurnDecision; marks: Record; /** The stack of the tool body this llm() runs in, or null at the top level. */ enclosingStack: StateStack | null; }): TurnEnded; /** * Make the thread end with the answer the user sees, as an assistant * message. A trailing assistant message that carries no tool calls is * replaced: it is the body's structured reply, or the caller's own text * left by dropHandoffToolCall, and keeping it would put two assistant * messages in a row, which the Anthropic client merges into one that * reads as JSON followed by prose. Anything else (a tool message, the * user's request) is followed by a push. */ export declare function endThreadWithAnswer(thread: MessageThread, answerText: string): void; /** * The body of the tool loop's `round.N.endTurn` step: decide from the * round's marks, apply the decision to the thread, record the outcome in * runnerState.turnEnded[round], and drop the marks, which have served. * Everything it reads is in runnerState, so a resumed pass that skips the * step finds the same outcome recorded. */ export declare function runEndTurnStep(args: { runnerState: Record; round: number; ranCallIndexes: number[]; responseFormat: unknown; thread: MessageThread; enclosingStack: StateStack | null; warn: (message: string) => void; }): void;