import { type ImagePart, type FilePart } from "smoltalk"; /** * Reply attachments: the channel by which a TOOL hands images back to the * model. `attachToReply` (std::thread) queues onto the calling tool * invocation's branch-local stack via `StateStack.queueReplyAttachment` * (serialized — survives a mid-round interrupt); the tool loop drains the * branch queue at invocation completion, harvests it here into the prompt's * `runnerState` (per-llm()-call, serialized, fork-safe), and appends a * marker to that tool's result text; after the full tool round the loop * injects ONE labeled user message built here. See * docs/superpowers/specs/2026-07-02-tool-reply-attachments-design.md and * docs/dev/agents/reply-attachments.md. * * The marker strings below are MODEL-FACING API — tests pin them; do not * reword casually. (They contain em-dashes, not hyphens.) */ /** A tool-produced attachment is exactly a smoltalk user-content image or * file part — the same shapes `image()` / `file()` (std::thread) build. */ export type ReplyAttachmentPart = ImagePart | FilePart; /** What a tool queues: the part, and for a path source the bridge could * not read into bytes, the reason harvest puts in the skip marker. */ export type QueuedReplyAttachment = { part: ReplyAttachmentPart; unreadable?: string; }; /** The skip reasons for a path source, decided by the bridge when the * tool queues it and reported by harvest. */ export declare const SKIP_NOT_FOUND = "file not found"; export declare const SKIP_TOO_LARGE = "too large to attach (over 20 MB)"; export type HarvestedReplyAttachment = { id: string; toolName: string; part: ReplyAttachmentPart; }; /** The runnerState key the harvested attachments wait under until the * round boundary delivers them. */ export declare const BUFFER_KEY = "replyAttachments"; /** Gate each drained entry, move survivors into `runnerState[BUFFER_KEY]`, * and return the marker text to append to that tool's result (empty string * when nothing was queued). Runs inside the idempotent per-tool invoke step * of the tool loop, so it executes exactly once per tool call, including * across interrupt/resume. */ export declare function harvestReplyAttachments(args: { queued: QueuedReplyAttachment[]; runnerState: Record; model: unknown; toolName: string; }): string; /** Append the harvest marker to a (possibly non-string) capped tool * result. Kept here so the "" no-marker sentinel never leaks decisions * into prompt.ts — the caller composes unconditionally. */ export declare function appendReplyMarker(cappedResult: unknown, marker: string, stringify: (value: unknown) => string): unknown; /** Build the parts array for the injected user message: one label text part * immediately before each attachment part. A path source was read into * base64 when the tool queued it, so the persistent thread never re-reads * a file that may later be deleted or edited; url/base64/provider sources * pass through. Never throws: a path source, which harvest does not let * through, becomes a text part. */ export declare function buildReplyUserMessage(harvested: HarvestedReplyAttachment[]): unknown[];