import * as smoltalk from "smoltalk"; export type MessageThreadJSON = { messages: smoltalk.MessageJSON[]; messageLabels?: (string | null)[]; parentId?: string | null; hidden?: boolean; label?: string | null; summary?: string | null; queuedMessages?: QueuedMessage[]; repairs?: number; }; /** A message waiting on a thread for delivery at the thread's next * request-turn (see MessageThread.queueMessage). Plain data — content is * a string or smoltalk user-content parts, both JSON-safe — so the queue * serializes with the thread wherever the thread serializes. No "system" * role: a mid-thread system turn is rejected or reinterpreted by some * providers, and a caller who wants to steer can say role "user". * Assistant content is string-only — smoltalk.assistantMessage takes a * string, and the union makes the compiler reject an array at the * queueMessage call site instead of a producer papering over it. */ export type QueuedMessage = { role: "user"; content: string | smoltalk.UserContentInput; label: string | null; } | { role: "assistant"; content: string; label: string | null; }; export declare class MessageThread { messages: smoltalk.Message[]; id: string; /** ID of the parent thread when this thread was created via * `ThreadStore.createSubthread()`. `null` for top-level threads * (the default `MessageThread` constructor leaves it null). Used by * `agency.threads.list()` to surface the parent linkage in * ThreadInfo, and by `ThreadStore.resumeExisting()` to reject * resuming a subthread outside its parent's context. */ parentId: string | null; /** When `true`, this thread is excluded from `agency.threads.list()` * (and therefore the stdlib `listThreads()` user-facing surface). * Set by `Runner.thread` at first-create time when the user opts * in via `thread(hidden: true) { ... }`. Round-tripped through * `toJSON`/`fromJSON` so the flag survives interrupt resume. */ hidden: boolean; /** Optional user-supplied label from `thread(label: "...") { ... }`. * Set by `Runner.thread` at first-create time. `null` for threads * created without a label or via `subthread {}` without one. * Surfaces on `agency.threads.list()` so the stdlib `listThreads()` * can attach it to `ThreadInfo`. Round-tripped through * `toJSON`/`fromJSON` so the value survives interrupt resume. */ label: string | null; /** Cached summary produced by the stdlib `summaryFor()` helper on * first `listThreads()` call after the thread closes. Written via * the TS-side `_setThreadSummary` shim so the cache lives on the * per-run `MessageThread` and gets GC'd with the run — no * module-level state in TS. `null` until the summary is computed. * Round-tripped through `toJSON`/`fromJSON`. */ summary: string | null; /** Repair generation: how many times `repairAbandonedTurn` * (threadRepair.ts) has rewritten this thread. A repair answers tool * calls a parked-then-abandoned turn left dangling, so any checkpoint * snapshot taken BEFORE it is stale — restoring one would overwrite * the repair and everything appended since. `isNewerThan` is that * comparison; `markRepaired` is the ONLY writer outside the * serialization paths (fromJSON / adoptFrom). Never assign directly. * Round-tripped through `toJSON`/`fromJSON` so checkpoints record the * generation they captured. */ repairs: number; /** Per-message debug labels, aligned with `messages` BY INDEX * (`messageLabels[i]` labels `messages[i]`; the lengths always match). * Observability only: shown in statelog, never sent to the provider. * Distinct from the thread-level `label` above (set by `thread(label:)`). * * The alignment is maintained by keeping the writers to a minimum, and * by giving every caller an operation that carries the labels along * instead of a reason to reach past them: * * - `push` — the only append. * - `removeAt` — the only removal. * - `adoptFrom` — take on another thread's messages and labels. * - the constructor and `setMessages` — the only replacements. * * Nothing else touches `this.messages`. A desync does not degrade * gracefully — it shifts every later label onto the wrong message — so * keep it that way. A rewrite via `setMessages` with no labels * (summarization) drops them; that is intended. (Thread repair * appends via `push`, so it keeps them.) */ messageLabels: (string | null)[]; /** Messages queued by `queueMessage`, waiting for the thread's next * request-turn. Drained by the turn-boundary machinery in the tool * loop; never sent to the provider directly from here. Serialized * only when non-empty (same rule as messageLabels). */ queuedMessages: QueuedMessage[]; constructor(messages?: smoltalk.Message[]); /** Alias for `push` with no label. Kept for the existing public API. */ addMessage(message: smoltalk.Message): void; cloneMessages(): smoltalk.Message[]; getMessages(): smoltalk.Message[]; /** Wholesale rewrite: take on `messages`, and `labels` with them when * the caller has them (the restore path). Without `labels` the new * messages are unlabeled — a rewrite that cannot say what the labels * are does not get to keep the old ones, which is why summarization * and `threadRepair` drop them. * * A `labels` array whose length disagrees with `messages` is refused * outright rather than padded or sliced: the lengths disagreeing means * the source is already wrong, and guessing an alignment would put * real labels on the wrong messages. Unlabeled beats mislabeled. */ setMessages(messages: smoltalk.Message[], labels?: (string | null)[]): void; /** Remove the message at `index`, taking its label with it. For the * callers that edit one message out of the thread and would otherwise * reach for `setMessages` and drop every label as collateral. */ removeAt(index: number): void; /** Take on `other`'s messages AND labels while keeping this thread's * identity. The resume path needs the alias preserved, so it cannot * just swap in the restored object. */ adoptFrom(other: MessageThread): void; /** Record that this thread was repaired. Monotonic on purpose — the * stale-checkpoint check reads `repairs` as a generation number, so it * must only ever go up. */ markRepaired(): void; /** True when this thread has been repaired since `snapshot` was * captured — which makes restoring `snapshot` a write over newer * history. See restoreThreadForResume in threadRepair.ts. */ isNewerThan(snapshot: MessageThread): boolean; /** The ONLY append. Everything that adds a message goes through here, * so `messages` and `messageLabels` cannot drift apart. */ push(message: smoltalk.Message, label?: string | null): void; /** The label of the message at `index`, or null when unlabeled. */ labelAt(index: number): string | null; /** Queue a message for delivery at this thread's next request-turn: * the start of the thread's next `llm()` call, or after a tool round * within a running call. The mid-turn-safe counterpart to an immediate * `push` — callable at any moment without breaking the tool-call / * tool-result adjacency the provider requires. If no `llm()` ever runs * against this thread again, the message simply waits, serialized with * the thread. */ queueMessage(content: string | smoltalk.UserContentInput, opts?: { role?: "user" | "assistant"; label?: string; }): void; /** Remove and return every queued message, oldest first. Called by the * turn-boundary drain exactly once per delivery point. */ takeQueuedMessages(): QueuedMessage[]; hasQueuedMessages(): boolean; newChild(): MessageThread; /** Build a subthread child seeded with a clone of this thread's * messages. Caller passes the parent's registry id so the child * knows where it descended from — used by `ThreadStore` to * link subthreads back into the registry and by * `agency.threads.list()` to surface the parent relationship. */ newSubthreadChild(parentRegistryId: string | null): MessageThread; toJSON(): MessageThreadJSON; static fromJSON(json: MessageThreadJSON | MessageThread | smoltalk.MessageJSON[] | smoltalk.Message[]): MessageThread; }