import * as smoltalk from "smoltalk"; import { UserContentInput } from "smoltalk"; import { type FuncParam } from "./agencyFunction.js"; import type { RetryPolicy, RetryConfig, LLMRetryReason } from "./llmRetry.js"; import type { NormalizedLLMError } from "./llmClient.js"; import type { SourceLocationOpts } from "./state/checkpointStore.js"; import { MessageThread } from "./state/messageThread.js"; /** Result of `_runPrompt`. Callback bodies cannot raise interrupts * (typechecker-enforced), so the result is always a plain `{messages, * toolCalls}` shape — the LLM hooks (onLLMCallStart, onLLMCallEnd) * fire as side effects only. */ export type RunPromptResult = { messages: MessageThread; toolCalls: smoltalk.ToolCallJSON[]; }; /** Flatten a prompt (a string, or an array of text/attachment parts) to plain * text for consumers that require a string — e.g. memory recall and log * previews. Attachments are dropped. Statelog does NOT use this: it keeps the * structured prompt, redacted (see `redactMessagesForLog`). */ export declare function promptText(p: string | UserContentInput): string; /** Deep copy of a thread's messages with attachment payloads (base64 / data * URIs) redacted, for statelog. Uses `toJSON()` — the same plain shape * `JSON.stringify` would emit — so wire consumers like * `wireAccessors.userMessageOf` keep working. Redaction only shortens base64 * string values, so the result is still structurally `MessageJSON[]`. */ export declare function redactMessagesForLog(messages: MessageThread): smoltalk.MessageJSON[]; /** A thread's redacted messages for statelog, each carrying its debug * label (from `llm(label:)` / `userMessage(msg, label:)`) when it has * one. Reads the labels off the SAME thread that produced the dump, so * index `i` lines up: `redactMessagesForLog` maps `messages` in order, * and smoltalk's `redactAttachments` is 1:1 over an array. That second * half is someone else's implementation detail, so a test pins the * pairing through the redaction path with a real attachment. * * Unlabeled messages are emitted unchanged rather than with `label: * null`, so logs for label-free programs stay byte-identical. */ export declare function withMessageLabels(messages: MessageThread): (smoltalk.MessageJSON & { label?: string; })[]; /** A prompt with attachment payloads redacted, preserving its string-or-array * shape, for statelog / hook data. */ export declare function redactPromptForLog(p: string | UserContentInput): string | UserContentInput; /** LLMs routinely emit an explicit `null` for an optional tool argument * they chose not to set (e.g. `bash(command, cwd: null, timeout: null)`). * Agency default-parameter values only fill `undefined`, so a `null` would * sail past the default into the function body — `bash(cwd: null)` reaches * `applyAgentCwd` → `path.resolve(base, null)` and throws. Drop any argument * whose value is `null` when its parameter declares a default, so the call * behaves exactly as if the LLM had omitted the key (the default applies). * Params without a default keep their value untouched: a required arg passed * `null` still surfaces its normal type error for the model to correct, and * an intentionally-nullable param is left alone. */ declare function dropNullDefaultedArgs(args: Record | null | undefined, params: readonly FuncParam[]): Record; /** Coerce an arbitrary tool result to the string the LLM would see. * Strings pass through; everything else is JSON-stringified, with a * `String()` fallback for values JSON can't represent (e.g. circular). */ declare function stringifyToolResult(result: any): string; /** Unwrap a SUCCESS Result before it goes back to the model: the LLM * should see the tool's value, not the `{__type, success, value}` * envelope (a wrapped envelope makes the model re-derive `.value` and, * worse, reconstruct wrapped objects when echoing them into later tool * arguments — the compile→run CompiledProgram bug). Only the LLM-facing * message unwraps; the Result cached on the branch for Agency code is * untouched. Failures/rejections never reach this point (handled * upstream in the invoke path), but pass through unchanged * defensively. */ declare function unwrapToolResultForLlm(result: any, toolName: string): any; /** Render a failure Result's error for the model. String errors pass * through; structured errors (e.g. writeAgency's `{source, errors}`) * JSON-stringify — `String(error)` would send the useless * "[object Object]". */ declare function toolErrorMessage(error: any): string; type FailureTier = "destructive" | "neverStarted" | "idempotent" | "neutral"; /** Classify a tool failure. Most-specific fact wins: a started destructive * operation, then a proved-nothing-ran, then the tool's own idempotent * declaration, else neutral. */ declare function failureTier(f: { destructiveRan?: boolean; neverStarted?: boolean; }, markers?: { idempotent?: boolean; }): FailureTier; /** Truncate a tool result for the LLM if its serialized form exceeds * `cap` characters. Returns the ORIGINAL value untouched when within * the cap (so smoltalk serializes it exactly as before) or when the cap * is disabled (`cap <= 0` or non-finite). Over the cap, returns the * first `cap` characters plus a marker noting the original length, so * the model knows it was cut. */ declare function capToolResultForLlm(result: any, cap: number): any; /** Provider APIs (Anthropic, OpenAI) reject an LLM request whose tool list * contains duplicate names, and tool-call dispatch here matches handlers by * name — so duplicate names are always a bug. They're easy to introduce * by accident because `.partial()` / `.describe()` preserve the base * function's name (e.g. `skillsDir` returns `read.partial(dir)`, so four * skill tools are all named `read`). Catch it before the request hits the * wire with a message that names the collision and points at `.rename()`, * instead of an opaque transport-layer 400 that never reaches the statelog. */ declare function assertUniqueToolNames(tools: { name: string; }[]): void; /** * Bound one LLM-call attempt by a per-call deadline. Returns a signal that * aborts (carrying a `callTimeout` cause) after `limitMs`, composed with the * parent (guard / Esc) signal so either source cancels the call. `limitMs <= 0` * means "no deadline" — the parent signal passes through unchanged. Structurally * a `TimeGuard`, scoped to a single call rather than a block. */ declare function armCallTimeout(parentSignal: AbortSignal | undefined, limitMs: number): { signal: AbortSignal | undefined; dispose: () => void; }; type RetryHooks = { onRetry: (d: { attempt: number; maxRetries: number; delayMs: number; reason: LLMRetryReason; detail: string; }) => void | Promise; onTimeout: (d: { limitMs: number; attempt: number; }) => void | Promise; }; /** * Run `dispatch(signal)` under the retry policy. Each attempt is bounded by a * per-call timeout (armCallTimeout). On a classified-transient failure with * attempts remaining, fire onLLMRetry and wait a cancellable backoff, then * re-issue. A user/abort cause is always re-thrown untouched (never retried); * an exhausted provider error is converted by the catch ladder into a normal * Failure (we throw a plain Error so `try llm(...)` can handle it, rather than * a branded abort that would unwind the whole run). The policy decision lives * in the pure `decideRetry`; this loop is the thin driver. */ declare function runWithRetry(dispatch: (signal: AbortSignal | undefined) => Promise, policy: RetryPolicy, parentSignal: AbortSignal | undefined, hooks: RetryHooks, normalizeError: (err: unknown) => NormalizedLLMError): Promise; /** Test-only surface for the pure tool-result-cap helpers. Not part of * the supported runtime API. */ export declare const _internal: { DEFAULT_TOOL_RESULT_CHARS: number; stringifyToolResult: typeof stringifyToolResult; capToolResultForLlm: typeof capToolResultForLlm; assertUniqueToolNames: typeof assertUniqueToolNames; unwrapToolResultForLlm: typeof unwrapToolResultForLlm; toolErrorMessage: typeof toolErrorMessage; failureTier: typeof failureTier; TIER_SUFFIX: Record; MAX_TOOL_FAILURES: number; armCallTimeout: typeof armCallTimeout; runWithRetry: typeof runWithRetry; dropNullDefaultedArgs: typeof dropNullDefaultedArgs; }; export declare function runPrompt(args: { prompt: string | UserContentInput; messages: MessageThread; responseFormat?: any; /** Provider-shaped config (model/temperature/...). Retry fields may also * appear here when the Agency-source `llm()` codegen passes through a * user-written options object verbatim; they are extracted below and * stripped before the config is forwarded to the LLM client. Direct TS * callers (e.g. `agency.llm`) should use the dedicated `retryConfig` * parameter instead. */ clientConfig: Partial & RetryConfig & { tools?: any[]; }; /** Per-call resilience policy. Takes precedence over any retry fields * piggybacked on `clientConfig`. */ retryConfig?: RetryConfig; maxToolCallRounds?: number; removedTools?: string[]; /** The CALLING function's locals object, threaded in by the `llm()` codegen * as `destructiveSink: __self`. The codegen passes the whole `__self`, but * runPrompt only ever writes ONE key on it, `__destructiveRan`, so the type * narrows to exactly that field. When a tool does destructive work, * decision 8 sets `destructiveSink.__destructiveRan = true`, and the * caller's exit stamp reads it back. Passed by reference — the same * by-reference trick `removedTools` uses. Absent for direct TS callers * (e.g. `agency.llm`), so the mark is a no-op there. */ destructiveSink?: { __destructiveRan?: boolean; }; /** Zod schema for the saveDraft tool's `value` param, threaded by the * llm() codegen from the enclosing def's declared return type. * Consulted only when the tools array contains the stdlib saveDraft * (see intrinsicTools.ts). */ draftSchema?: unknown; checkpointInfo?: SourceLocationOpts; }): Promise; export {};