@dudousxd/nestjs-agent-core 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,600 +70,200 @@ declare function readElicitationInput(raw: unknown): ElicitationInput | undefine
70
70
  declare function readElicitationQuestions(input: unknown): ElicitationQuestion[];
71
71
 
72
72
  /**
73
- * Asking the USER a structured question, and waiting for the answer.
73
+ * What a store knows about a tool call, read back when its message carries no result for it — see
74
+ * {@link import('./spi/agent-store.js').AgentStore.toolCallOutcomes}.
75
+ */
76
+ interface ToolCallOutcome {
77
+ id: string;
78
+ status: ToolCallStatus;
79
+ output?: unknown;
80
+ error?: string;
81
+ }
82
+ /** What the model is told about a call whose turn died before the call was settled. */
83
+ declare const UNFINISHED_TOOL_CALL: string;
84
+ /** What is written on the row of a call whose run ended without settling it. */
85
+ declare const RUN_ENDED_BEFORE_TOOL_CALL = "the run ended before this tool call was settled";
86
+ /** The ids of every tool call in `messages` that the message asking for it holds no result for. */
87
+ declare function danglingToolCallIds(messages: readonly ModelMessage[]): string[];
88
+ /**
89
+ * Give every tool call in a thread's history a result.
74
90
  *
75
- * `awaitApproval` collects a yes/no about work already proposed; this collects the scope BEFORE the
76
- * work. Two surfaces produce it — a configured intake (`AgentLoopDeps.intake`) and the model-callable
77
- * `ask` tool (`AgentLoopDeps.ask`) — and they deliberately produce the SAME {@link
78
- * ElicitationRequest}, persist through the same tool-call row, and resume through the same
79
- * `tool:<runId>:<callId>` signal. A consumer cannot tell which one asked, and should not have to.
91
+ * A turn writes its results onto the assistant message once the step's LAST tool has settled. A turn
92
+ * that dies before that — its run failed, its worker was killed, an approval was never answered and
93
+ * the run was abandoned — leaves an assistant message that asks for tools and is answered by
94
+ * nothing. A provider refuses a prompt shaped like that (a tool call must be followed by its
95
+ * result), so every LATER turn on the thread fails too, with an error about the stream rather than
96
+ * about the history: one dead turn makes the whole conversation unusable.
97
+ *
98
+ * Here each such call is settled from what is actually known: the call's own row where the store
99
+ * can read it (`outcomes` — a tool that DID run hands the model its real output, so it is not run a
100
+ * second time), and otherwise a result saying the call was never completed. Pure: the same messages
101
+ * and outcomes always produce the same prompt, so a replay composes what the first attempt did.
102
+ * Messages with nothing dangling are returned as they are.
80
103
  */
104
+ declare function settleDanglingToolCalls(messages: ModelMessage[], outcomes?: readonly ToolCallOutcome[]): ModelMessage[];
81
105
 
82
- /** One choice a question offers. */
83
- interface ElicitationOption {
84
- /** Stable identifier submitted back. Never shown to the user. */
85
- value: string;
86
- /** What the user reads. */
87
- label: string;
106
+ interface CreateThreadInput {
107
+ actor: Actor;
108
+ transient?: boolean;
109
+ title?: string;
88
110
  /**
89
- * A single character a UI may bind as a keyboard shortcut for this option. Advisory — nothing in
90
- * the library reads it, and a client is free to render its own.
111
+ * Create the thread under THIS id instead of a generated one — for a caller whose protocol names
112
+ * the conversation itself (AG-UI's `threadId`, at most 255 characters). OPTIONAL to honour: a
113
+ * store that ignores it still creates a thread, under an id of its own, and the caller reads the
114
+ * id off the result. A store that honours it rejects an id already taken (soft-deleted threads
115
+ * included).
91
116
  */
92
- hotkey?: string;
117
+ id?: string;
93
118
  }
94
- /** One question in a set. */
95
- interface ElicitationQuestion {
96
- /** Unique within its request; the key answers come back under. */
97
- id: string;
98
- prompt: string;
99
- /** A line of help under the prompt. */
100
- description?: string;
119
+ interface AppendMessageInput {
120
+ threadId: string;
121
+ role: StoredMessage['role'];
122
+ content: string;
123
+ /** Which agent produced this message (assistant messages) — provenance. */
124
+ agentName?: string;
125
+ toolCalls?: ToolCallRequest[];
126
+ toolResults?: ToolResult[];
127
+ /** Files the user attached to this message (image/PDF). Persisted verbatim. */
128
+ attachments?: MessageAttachment[];
129
+ followUps?: string[];
130
+ usage?: MessageUsage;
101
131
  /**
102
- * The choices. Required unless {@link input} asks for a typed value — then optional, and a UI may
103
- * offer them as suggestions. An `input` of type `select` still picks from these.
132
+ * The run (turn) that produced this message. Without it a consumer can only guess which turn a
133
+ * message belongs to by comparing timestamps against the run's `startedAt`, and that guess breaks
134
+ * the moment a turn is regenerated — the replaced answer is truncated away, so the times no longer
135
+ * line up 1:1. Optional so a caller predating this (and a host that appends messages outside a
136
+ * run) can omit it; the store persists it as `null` when absent.
104
137
  */
105
- options?: ElicitationOption[];
138
+ runId?: string;
139
+ /** The step's streamed thinking. See {@link StoredMessage.reasoning}. */
140
+ reasoning?: string;
141
+ /** Time spent thinking in this step, in ms. See {@link StoredMessage.reasoningMs}. */
142
+ reasoningMs?: number;
143
+ /** Components pushed during this step. See {@link StoredMessage.ui}. */
144
+ ui?: AgentUiComponent[];
145
+ }
146
+ interface RecordToolCallInput {
147
+ toolCallId: string;
148
+ messageId: string;
149
+ toolName: string;
150
+ toolType: 'read' | 'action';
151
+ input: unknown;
152
+ status: ToolCallStatus;
106
153
  /**
107
- * Ask for a typed value (a number, a date, a sentence…) instead of — or, for `select`, on top of —
108
- * a pick from `options`. The answer is still a `string[]`, in the type's canonical form; see
109
- * `elicitation-input.ts`.
154
+ * The run (turn) this tool call belongs to — enables a governance surface to deep-link a tool
155
+ * call out to its trace waterfall. Optional so a caller predating this (or a store's own
156
+ * synthetic tool calls) can omit it; the store persists it as `null` when absent.
110
157
  */
111
- input?: ElicitationInput;
112
- /** More than one option may be chosen. Omit → single choice. */
113
- multiple?: boolean;
158
+ runId?: string;
114
159
  /**
115
- * The options already picked for the user. The claim this whole surface makes is that confirming
116
- * is enough, so a question with no defaults is a question the user must stop and think about —
117
- * which is the case the design is trying to avoid. Empty/omitted is allowed and means exactly
118
- * that: submitting without answering leaves this question unanswered.
160
+ * Who has to approve this call (`'requester'` or a role), from the turn's `ApprovalPolicy`. Set on
161
+ * an action call that was put to a person — or approved by a remembered decision — and never
162
+ * otherwise; persisted as `null` when absent.
119
163
  */
120
- defaults?: string[];
121
- /** Accept values that are not among `options` (a typed-in answer). Omit → options only. */
122
- allowFreeText?: boolean;
164
+ approver?: string;
165
+ /** ISO-8601 instant the approval request lapses. Absent → it never does. */
166
+ expiresAt?: string;
123
167
  }
124
- /**
125
- * A question set awaiting a human. Identical in shape whether an `@Agent`'s configured intake or
126
- * the model's `ask` tool authored it — `source` records which, for audit, not for control flow.
127
- *
128
- * `questions.length` is known when the request is written, which is what lets a client render
129
- * "Question 1 of 3" without guessing whether a fourth is coming.
130
- */
131
- interface ElicitationRequest {
132
- /** The tool-call id this request is persisted under, and the signal it is answered through. */
133
- id: string;
134
- source: 'intake' | 'ask';
135
- /** What the assistant says above the form. */
136
- preamble?: string;
137
- questions: ElicitationQuestion[];
168
+ interface UpdateToolCallInput {
169
+ toolCallId: string;
170
+ status: ToolCallStatus;
171
+ output?: unknown;
172
+ error?: string;
173
+ executionMs?: number;
174
+ executedByRef?: string;
175
+ /** The approval asked for later calls of this tool in this thread to run without asking. */
176
+ remember?: boolean;
177
+ /** The surface the decision came through. See {@link import('../types.js').Decision.decidedVia}. */
178
+ decidedVia?: string;
138
179
  }
139
- /** What a human sent back for an {@link ElicitationRequest}. */
140
- interface ElicitationReply {
141
- /**
142
- * questionId → chosen values. A question whose id is ABSENT takes the request's own `defaults` —
143
- * that is what makes "just submit" mean "yes, your pre-picked answers". A present-but-empty array
144
- * is an explicit "none of these" and does NOT fall back.
145
- */
146
- answers: Record<string, string[]>;
147
- /**
148
- * The user declined to answer and told the agent to proceed on its own assumptions. Distinct from
149
- * confirming the defaults even though the resulting values are the same: one is a decision the
150
- * user made, the other is one they refused to make, and only the first is evidence of intent.
151
- */
152
- skipped?: boolean;
153
- /** Opaque ref of WHO answered, when it wasn't the run's own actor. */
154
- answeredByRef?: string;
180
+ /** What the approve/reject routes need to know about a call before they signal a decision on it. */
181
+ interface ToolCallApprovalState {
182
+ status: ToolCallStatus;
183
+ /** `null` for a call no policy put to anyone (an `ask`, or a row written before approvers existed). */
184
+ approver: string | null;
185
+ expiresAt: string | null;
186
+ }
187
+ /** Patch applied by {@link AgentStore.updateThread}. An omitted key leaves that field untouched. */
188
+ interface UpdateThreadInput {
189
+ title?: string;
190
+ /** `null` clears the thread's default agent (falls back to the module default). */
191
+ defaultAgent?: string | null;
192
+ /** `null` unpins the thread's model (turns run on the provider default). */
193
+ model?: string | null;
194
+ }
195
+ interface RecordUsageInput {
196
+ threadId: string;
197
+ actorRef: string;
198
+ messageId?: string;
199
+ modelId: string;
200
+ purpose: UsagePurpose;
201
+ usage: MessageUsage;
202
+ /** Provider-reported actual USD cost for this turn, when known (gateways report it). */
203
+ costUsd?: number;
204
+ }
205
+ interface RecordRunStartInput {
206
+ runId: string;
207
+ threadId: string;
208
+ actorRef: string;
209
+ agentName?: string;
155
210
  /**
156
- * The surface the answer came through — `'web'`, `'slack'`, `'console'`, … — the counterpart of
157
- * an approval's `decidedVia`.
211
+ * The run that started this one, for a delegation's child run. The parent->child edge exists in
212
+ * the durable runtime's own journal, but only there: a governance surface reading run rows alone
213
+ * cannot roll a delegation's cost up to the turn that asked for it, and a DETACHED child outlives
214
+ * its parent's turn entirely, so nothing in the transcript pairs them either.
215
+ *
216
+ * Optional, and a store that persists nothing for it still works — it loses the tree, not the run.
158
217
  */
159
- answeredVia?: string;
218
+ parentRunId?: string;
219
+ /** sha256 hex of the run's resolved (pre-RAG) system prompt — identifies the prompt VERSION. */
220
+ promptHash?: string;
160
221
  }
161
- /** A settled elicitation: what the agent proceeds on, and how it got there. */
162
- interface ElicitationOutcome {
163
- /** One entry per question, in request order — always present, so a caller never re-applies defaults. */
164
- answers: Record<string, string[]>;
165
- skipped: boolean;
166
- /** Question ids filled from the request's `defaults` rather than by the human. */
167
- defaulted: string[];
222
+ interface RecordRunEndInput {
223
+ runId: string;
168
224
  /**
169
- * Who answered (or skipped) — the reply's `answeredByRef`, which a host may make a display name.
170
- * The counterpart of an approval's `decidedBy`. Absent when the reply did not say.
225
+ * `cancelled` is a THIRD terminal, not a flavour of `failed`: someone asked the run to stop and it
226
+ * did, which is the control working. A consumer computing a failure rate over these rows has to be
227
+ * able to leave it out — counting a user pressing Stop as an error pages whoever is on call for
228
+ * model failures. It carries no `errorCode`/`errorMessage`, since there is nothing to diagnose.
171
229
  */
172
- answeredBy?: string;
173
- /** The surface it came through (`'web'`, `'slack'`, …) — an approval's `decidedVia`. */
174
- answeredVia?: string;
230
+ status: 'completed' | 'failed' | 'cancelled';
231
+ durationMs?: number;
232
+ errorCode?: string;
233
+ errorMessage?: string;
234
+ }
235
+ /** Which thread, and how many of its newest messages, {@link ThreadTurnReader.loadThreadForTurn} reads. */
236
+ interface ThreadTurnQuery {
237
+ threadId: string;
238
+ /** Omitted reads every message; `0` reads none. */
239
+ messageLimit?: number;
240
+ }
241
+ /** What a turn reads off a thread — a bounded window, not the transcript. */
242
+ interface ThreadTurnPage {
243
+ title: string;
244
+ defaultAgent: string | null;
245
+ /** Whether the THREAD has ever been answered, not whether {@link messages} holds an answer. */
246
+ hasAssistantMessage: boolean;
247
+ /** Oldest first, carrying only the fields a model turn reads. */
248
+ messages: StoredMessage[];
175
249
  }
176
250
  /**
177
- * Read whatever the human channel delivered as an {@link ElicitationReply}.
178
- *
179
- * A question set is persisted as an `action` tool call in `pending_approval` — that is what puts it
180
- * in the approvals inbox a deployment already has, instead of needing one of its own. The cost of
181
- * that choice is that the thing which comes back may be a {@link Decision} someone pressed
182
- * Approve/Reject on rather than a set of answers, and a `Decision` carries no `answers` at all.
251
+ * A store that can hand a turn the WINDOW it is about to send, instead of the thread's transcript.
183
252
  *
184
- * Approve means every question keeps its own pre-picked `defaults`, which is exactly what "just
185
- * submit" already means on this surface; Reject is the same declining-to-answer a skip is. Neither
186
- * reading is a guess — a yes/no channel cannot say more than that, and saying it here is what lets
187
- * one inbox settle both kinds of pending work.
253
+ * {@link AgentStore.getThread} materializes every message row, every attachment and every tool
254
+ * output a thread ever recorded, and the run then journals what it loaded — so a long thread pays
255
+ * for its whole history on every turn and again on every replay, to send a prompt bounded to its
256
+ * last few messages. This read is bounded by the database (`order by created_at desc limit ?`),
257
+ * projected to the columns a model turn actually reads.
188
258
  *
189
- * Returns the reply UNCHANGED when it already carries answers, so the common path allocates nothing
190
- * and a caller can identity-compare.
191
- */
192
- declare function normalizeElicitationReply(reply: ElicitationReply | Decision): ElicitationReply;
193
- /**
194
- * Settle a reply against the request it answers: fill every unanswered question from its own
195
- * `defaults`, drop submitted values that aren't on offer, and collapse a single-choice question to
196
- * one value.
259
+ * `hasAssistantMessage` is answered over the WHOLE thread, never the page: it answers "has this
260
+ * conversation been answered before?" — what a `thread-start` intake asks — and a thread whose window
261
+ * happens to hold only the user's last questions has still been answered. `null` for a thread that is
262
+ * unknown or soft-deleted, matching `getThread`.
197
263
  *
198
- * PURE, and deliberately so. Both of its inputs are already journaled by the time the loop calls it
199
- * — the request came from module config or from an `llm:<i>` checkpoint, the reply from the signal
200
- * checkpoint — so every process replaying the turn reaches the same values without a checkpoint of
201
- * its own. Resolving defaults in the HTTP layer instead would put them behind a store read that a
202
- * replay would have to repeat.
203
- */
204
- declare function resolveElicitation(request: ElicitationRequest, raw: ElicitationReply | Decision): ElicitationOutcome;
205
- /**
206
- * What a settled elicitation looks like to everyone downstream: the model reading it back as a tool
207
- * result, the thread reader rendering it, the auditor asking what the agent was told to do. One
208
- * shape for both surfaces — nothing here records which of them asked.
209
- */
210
- interface ElicitationResult extends ElicitationOutcome {
211
- /** The questions against the chosen LABELS, so a reader (and a model) can act on it. */
212
- summary: string;
213
- }
214
- /** {@link resolveElicitation} plus its human-readable rendering. Pure, for the same reason. */
215
- declare function settleElicitation(request: ElicitationRequest, reply: ElicitationReply | Decision): ElicitationResult;
216
- /**
217
- * The answers as the model reads them: the question's own prompt against the chosen options' LABELS,
218
- * not their opaque `value`s — a model shown `{"scope":["b"]}` has been told nothing.
219
- */
220
- declare function renderElicitationAnswers(request: ElicitationRequest, outcome: ElicitationOutcome): string;
221
- /** The reserved tool name the model calls to ask the user something. */
222
- declare const ASK_TOOL_NAME = "ask";
223
- /** What the model must supply when it calls `ask`. */
224
- interface AskToolInput {
225
- preamble?: string;
226
- questions: ElicitationQuestion[];
227
- }
228
- /** How many questions one `ask` may carry. A form the user has to scroll is a form they skip. */
229
- declare const MAX_ASK_QUESTIONS = 5;
230
- /**
231
- * The `ask` tool's input schema, hand-written rather than borrowed from a validation library: core
232
- * depends on no validator, and the schema has to carry a JSON Schema a provider can constrain
233
- * generation against. It publishes one through the Standard JSON Schema extension
234
- * (`~standard.jsonSchema.input`), which is the path the AI SDK adapter already recognises for
235
- * Valibot / ArkType / Zod 4.
236
- */
237
- declare const askInputSchema: StandardSchemaV1<unknown, AskToolInput>;
238
- /**
239
- * What the model is told the `ask` tool is for. Written to discourage the two failure modes that
240
- * make a clarifying question worse than a guess: asking about something the conversation already
241
- * settled, and asking without saying what you would have done.
242
- */
243
- declare const ASK_TOOL_DESCRIPTION = "Ask the user to settle the scope of the work before you do it. Use it when a reasonable person would produce a materially different result depending on the answer \u2014 not to confirm something the conversation already says. Every question must pre-pick the answer you would choose, so the user can confirm instead of deciding. The user may decline, in which case you proceed on those pre-picked answers.";
244
- /**
245
- * The `ask` tool as the model sees it. NOT a `ToolSpec` and never registered: `ask` has no handler,
246
- * because the loop settles it against a human instead of invoking anything. Keeping it out of the
247
- * `ToolRegistry` is also what keeps the kind decision off a process-local lookup — see
248
- * `claimToolCall`.
249
- */
250
- declare function askToolDefinition(): ToolDefinition;
251
- /** A question set an `@Agent` asks before it starts working. See `AgentLoopDeps.intake`. */
252
- interface AgentIntake {
253
- questions: ElicitationQuestion[];
254
- /** What the assistant says above the form. Omit → {@link DEFAULT_INTAKE_PREAMBLE}. */
255
- preamble?: string;
256
- /**
257
- * `'thread-start'` (default) asks once, on the first turn of a thread; `'every-turn'` asks before
258
- * every turn. Both are decided from what `load:thread` recorded about the thread when the turn
259
- * began, never from anything this process happens to know — by the time a replay reaches the
260
- * question, the thread already holds the assistant message the first attempt wrote.
261
- */
262
- when?: 'thread-start' | 'every-turn';
263
- }
264
- declare const DEFAULT_INTAKE_PREAMBLE = "A few questions before I start. I have pre-picked what I would choose, so confirming is enough.";
265
-
266
- /**
267
- * The structured live-stream vocabulary carried over the {@link SinkWriter} byte channel.
268
- *
269
- * The model turn (via the AI-SDK adapter) and the agent loop write these events as NDJSON — one
270
- * `JSON.stringify(event)\n` per {@link SinkWriter.write}. The HTTP layer forwards each line as an
271
- * SSE `data:` frame, and the client transport maps them back to the AI SDK UI-message chunk
272
- * protocol so the browser renders text, reasoning, and tool cards (input + output) LIVE — the same
273
- * rich rendering a raw `streamText().toUIMessageStream()` would give, but reconstructed on the
274
- * client so the sink stays a format-agnostic byte buffer (durable buffering/replay is untouched).
275
- *
276
- * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
277
- * the adapter owns model-parts → event, the transport owns event → UI-chunk.
278
- *
279
- * The vocabulary is also a CONTRACT for runners that are not this library's loop: anything that
280
- * writes these frames (one JSON object per SSE `data:` line) gets the React transport, transcript
281
- * model and hooks for free. Two rules keep it evolvable:
282
- * - every frame is a JSON object with a string `kind`; a reader MUST tolerate kinds it does not
283
- * know (the React transport forwards them as `data-<kind>` parts rather than dropping them);
284
- * - fields are only ever added, and new fields are optional.
285
- */
286
-
287
- /**
288
- * A component the server pushed into the conversation: generative UI that is NOT a tool call's
289
- * rendering. It is addressed by `component` (a key in the client's own component registry), never
290
- * by a tool name, so a runner can emit one from anywhere — a tool body, a post-processing step, a
291
- * sandboxed agent that renders through its own protocol.
292
- *
293
- * `id` is the component's identity within the message: a second frame with the same `id` REPLACES
294
- * the first (streaming props into a chart, flipping a card from "loading" to "ready"), it never
295
- * adds a second component.
296
- */
297
- interface AgentUiComponent {
298
- id: string;
299
- /** Registry key the client resolves to its own renderer, e.g. `data-table`. */
300
- component: string;
301
- props: Record<string, unknown>;
302
- /** Schema version of `props`, so a client can keep rendering components persisted by an older server. */
303
- version?: number;
304
- /**
305
- * The tool call that pushed the component (`ctx.emitUi`), when one did. Lets a client place it
306
- * with that call — a reloaded message puts it right after the call's tool part, where the live
307
- * stream showed it. Absent for a component pushed outside a tool.
308
- */
309
- toolCallId?: string;
310
- }
311
- /**
312
- * Who has to settle an action tool call, and until when. Metadata only: the call itself is still
313
- * settled through the tool-call approve/reject routes, by its `toolCallId`.
314
- */
315
- interface AgentApprovalRequest {
316
- /** The tool call awaiting the decision — the `id` of a call already announced on this stream. */
317
- id: string;
318
- /**
319
- * Who may decide. Open vocabulary the host defines — `'requester'` (the person chatting),
320
- * `'admin'`, a role name, a team. A client uses it to say "waiting on an admin" instead of
321
- * offering buttons the viewer cannot use.
322
- */
323
- approver: string;
324
- /** ISO-8601 instant after which the request lapses. Absent → it never expires. */
325
- expiresAt?: string;
326
- /** Why this call needs a person, in words for that person. */
327
- reason?: string;
328
- }
329
- /**
330
- * How an approval settled — the other half of {@link AgentApprovalRequest}, under the same `id`.
331
- * Metadata again: the call's own outcome still arrives as `tool-output` (approved and ran),
332
- * `tool-output-error` (approved and failed) or `tool-output-denied` (rejected or expired).
333
- */
334
- interface AgentApprovalSettlement {
335
- id: string;
336
- status: 'approved' | 'rejected' | 'expired';
337
- /** Repeated from the request, for a call approved without one being streamed (a remembered approval). */
338
- approver?: string;
339
- /** Opaque ref of who decided. Absent on an expiry. */
340
- decidedBy?: string;
341
- /** The surface the decision came through: `'web'`, `'slack'`, `'remembered'`, … */
342
- decidedVia?: string;
343
- /** The approval also covers later calls of this tool in this thread. */
344
- remember?: boolean;
345
- /** What the person said when declining. */
346
- reason?: string;
347
- }
348
- type AgentStreamEvent = {
349
- kind: 'step-start';
350
- }
351
- /**
352
- * Closes the step opened by the matching `step-start`. Carries the model call's token usage and
353
- * `costUsd` (an estimate from the bound pricing store, or `null` when unpriced/unbound — never a
354
- * fabricated `0`) so a live client can render running cost without waiting for a thread re-fetch.
355
- */
356
- | {
357
- kind: 'step-finish';
358
- usage?: MessageUsage;
359
- costUsd?: number | null;
360
- /**
361
- * How long the model spent thinking in this step, in ms — the same number persisted as
362
- * `StoredMessage.reasoningMs`, so a live thread and a reloaded one read the same duration.
363
- * Absent when the step had no reasoning.
364
- */
365
- reasoningMs?: number;
366
- } | {
367
- kind: 'text';
368
- text: string;
369
- } | {
370
- kind: 'reasoning';
371
- text: string;
372
- }
373
- /**
374
- * `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool.
375
- *
376
- * `parentId` nests this call under another call on the same stream: the inner calls a code-mode
377
- * `execute` makes, the tools a delegated sub-agent runs. The parent must be announced first. A
378
- * call's parent is fixed by its first frame that names one; later frames may omit it.
379
- */
380
- | {
381
- kind: 'tool-input-start';
382
- id: string;
383
- name: string;
384
- toolKind: 'read' | 'action';
385
- parentId?: string;
386
- } | {
387
- kind: 'tool-input-delta';
388
- id: string;
389
- delta: string;
390
- } | {
391
- kind: 'tool-input-available';
392
- id: string;
393
- name: string;
394
- input: unknown;
395
- toolKind: 'read' | 'action';
396
- /** Same as on `tool-input-start`, for a runner that announces a call without streaming its input. */
397
- parentId?: string;
398
- } | {
399
- kind: 'tool-output';
400
- id: string;
401
- output: unknown;
402
- } | {
403
- kind: 'tool-output-error';
404
- id: string;
405
- error: string;
406
- }
407
- /**
408
- * A person was asked to approve an action tool and declined it. Its own frame, NOT
409
- * `tool-output-error`: a refusal is a decision with a known outcome — nothing ran — while a
410
- * failure is an outcome nobody chose and whose effects are unknown. Rendering them the same way
411
- * tells an operator their own "no" was a malfunction. Maps onto the SDK's `output-denied` tool
412
- * part state, which a client reads without knowing any tool's name.
413
- */
414
- | {
415
- kind: 'tool-output-denied';
416
- id: string;
417
- reason?: string;
418
- }
419
- /**
420
- * The run has put a question set to the user and is parked until someone answers it (or skips).
421
- * Written by the LOOP for both elicitation surfaces — the configured intake and the model's `ask`
422
- * tool — so a client renders one form either way rather than learning to recognise a tool name.
423
- * The matching `tool-output` frame, under the same `id`, carries the settled answers.
424
- */
425
- | {
426
- kind: 'elicitation';
427
- id: string;
428
- request: ElicitationRequest;
429
- }
430
- /**
431
- * An action tool call (already announced by `tool-input-start`/`tool-input-available` under the
432
- * same `id`) is parked on a person. Optional: a client still treats an `action` call stuck at
433
- * its input as pending — this frame adds WHO has to decide and UNTIL WHEN, and moves the call
434
- * into the AI SDK's native `approval-requested` state.
435
- */
436
- | ({
437
- kind: 'approval-requested';
438
- } & AgentApprovalRequest)
439
- /**
440
- * A parked action call was decided (or lapsed). Optional, like `approval-requested`: it adds WHO
441
- * decided, THROUGH WHAT and whether the approval is REMEMBERED; the outcome itself rides the
442
- * call's own output frame. See {@link AgentApprovalSettlement}.
443
- */
444
- | ({
445
- kind: 'approval-settled';
446
- } & AgentApprovalSettlement)
447
- /**
448
- * Server-pushed generative UI, positioned in the message where it arrives. Not tied to a tool
449
- * call. See {@link AgentUiComponent}.
450
- */
451
- | ({
452
- kind: 'ui';
453
- } & AgentUiComponent)
454
- /**
455
- * The thread's title was set or changed while this run streamed (typically derived from the
456
- * first exchange). Thread-level, not message content: a client updates its header/sidebar and
457
- * does not render it in the transcript.
458
- */
459
- | {
460
- kind: 'title';
461
- title: string;
462
- }
463
- /**
464
- * Host-defined facts about the message being streamed (the model that answered, how long it took,
465
- * the error it ended with), merged into the client message's `metadata`. The persisted
466
- * counterpart is `StoredMessage.metadata`, so a reload reads the same values. The library's own
467
- * loop never writes it; a runner that is not this library's loop uses it for what its store keeps
468
- * per message.
469
- */
470
- | {
471
- kind: 'message-metadata';
472
- metadata: Record<string, unknown>;
473
- }
474
- /**
475
- * Someone stopped this run. The stream's LAST frame, written by the runner that settled the
476
- * cancel, immediately before a normal `end()` — never a `fail()`, because a cancel is not an
477
- * error and a client that retries on a failed stream must not retry this.
478
- *
479
- * A run that simply ends wrote everything it had; one that ends after this frame did not, and the
480
- * difference is the whole point: without it a reader cannot tell a truncated answer from a
481
- * complete one. Consumers that predate the frame ignore it and see the `end()` they always saw.
482
- */
483
- | {
484
- kind: 'cancelled';
485
- }
486
- /**
487
- * The thread's message queue changed — a snapshot of the whole queue, never a delta, so a client
488
- * that missed one frame is corrected by the next. Written into the stream of the run that is
489
- * holding the thread: when someone queues, edits, reorders or removes a waiting message, and, just
490
- * before this run's own terminal frame, with what happens next — `started` names the queued
491
- * message that became the next turn and that turn's run id (attach to it with
492
- * `GET <base>/chat/:runId/stream`), `queue.paused` says why nothing starts.
493
- */
494
- | {
495
- kind: 'queue';
496
- queue: ChatQueueState;
497
- started?: {
498
- messageId: string;
499
- runId: string;
500
- };
501
- };
502
- /** Encode one event as an NDJSON line (`{...}\n`) for {@link SinkWriter.write}. */
503
- declare function encodeStreamEvent(event: AgentStreamEvent): Uint8Array;
504
- /**
505
- * Read one NDJSON line back, or `null` when the line is not a stream event at all.
506
- *
507
- * `null` covers a genuinely opaque chunk, not just malformed JSON: the sink is a byte channel, so a
508
- * model provider is free to write anything into it and some write bare text. A caller that has to
509
- * CLASSIFY a chunk — the output gate, which may only forward what it can prove is not the answer —
510
- * treats an unreadable frame as unclassifiable rather than guessing.
511
- */
512
- declare function decodeStreamEvent(line: string): AgentStreamEvent | null;
513
-
514
- interface CreateThreadInput {
515
- actor: Actor;
516
- transient?: boolean;
517
- title?: string;
518
- }
519
- interface AppendMessageInput {
520
- threadId: string;
521
- role: StoredMessage['role'];
522
- content: string;
523
- /** Which agent produced this message (assistant messages) — provenance. */
524
- agentName?: string;
525
- toolCalls?: ToolCallRequest[];
526
- toolResults?: ToolResult[];
527
- /** Files the user attached to this message (image/PDF). Persisted verbatim. */
528
- attachments?: MessageAttachment[];
529
- followUps?: string[];
530
- usage?: MessageUsage;
531
- /**
532
- * The run (turn) that produced this message. Without it a consumer can only guess which turn a
533
- * message belongs to by comparing timestamps against the run's `startedAt`, and that guess breaks
534
- * the moment a turn is regenerated — the replaced answer is truncated away, so the times no longer
535
- * line up 1:1. Optional so a caller predating this (and a host that appends messages outside a
536
- * run) can omit it; the store persists it as `null` when absent.
537
- */
538
- runId?: string;
539
- /** The step's streamed thinking. See {@link StoredMessage.reasoning}. */
540
- reasoning?: string;
541
- /** Time spent thinking in this step, in ms. See {@link StoredMessage.reasoningMs}. */
542
- reasoningMs?: number;
543
- /** Components pushed during this step. See {@link StoredMessage.ui}. */
544
- ui?: AgentUiComponent[];
545
- }
546
- interface RecordToolCallInput {
547
- toolCallId: string;
548
- messageId: string;
549
- toolName: string;
550
- toolType: 'read' | 'action';
551
- input: unknown;
552
- status: ToolCallStatus;
553
- /**
554
- * The run (turn) this tool call belongs to — enables a governance surface to deep-link a tool
555
- * call out to its trace waterfall. Optional so a caller predating this (or a store's own
556
- * synthetic tool calls) can omit it; the store persists it as `null` when absent.
557
- */
558
- runId?: string;
559
- /**
560
- * Who has to approve this call (`'requester'` or a role), from the turn's `ApprovalPolicy`. Set on
561
- * an action call that was put to a person — or approved by a remembered decision — and never
562
- * otherwise; persisted as `null` when absent.
563
- */
564
- approver?: string;
565
- /** ISO-8601 instant the approval request lapses. Absent → it never does. */
566
- expiresAt?: string;
567
- }
568
- interface UpdateToolCallInput {
569
- toolCallId: string;
570
- status: ToolCallStatus;
571
- output?: unknown;
572
- error?: string;
573
- executionMs?: number;
574
- executedByRef?: string;
575
- /** The approval asked for later calls of this tool in this thread to run without asking. */
576
- remember?: boolean;
577
- /** The surface the decision came through. See {@link import('../types.js').Decision.decidedVia}. */
578
- decidedVia?: string;
579
- }
580
- /** What the approve/reject routes need to know about a call before they signal a decision on it. */
581
- interface ToolCallApprovalState {
582
- status: ToolCallStatus;
583
- /** `null` for a call no policy put to anyone (an `ask`, or a row written before approvers existed). */
584
- approver: string | null;
585
- expiresAt: string | null;
586
- }
587
- /** Patch applied by {@link AgentStore.updateThread}. An omitted key leaves that field untouched. */
588
- interface UpdateThreadInput {
589
- title?: string;
590
- /** `null` clears the thread's default agent (falls back to the module default). */
591
- defaultAgent?: string | null;
592
- /** `null` unpins the thread's model (turns run on the provider default). */
593
- model?: string | null;
594
- }
595
- interface RecordUsageInput {
596
- threadId: string;
597
- actorRef: string;
598
- messageId?: string;
599
- modelId: string;
600
- purpose: UsagePurpose;
601
- usage: MessageUsage;
602
- /** Provider-reported actual USD cost for this turn, when known (gateways report it). */
603
- costUsd?: number;
604
- }
605
- interface RecordRunStartInput {
606
- runId: string;
607
- threadId: string;
608
- actorRef: string;
609
- agentName?: string;
610
- /**
611
- * The run that started this one, for a delegation's child run. The parent->child edge exists in
612
- * the durable runtime's own journal, but only there: a governance surface reading run rows alone
613
- * cannot roll a delegation's cost up to the turn that asked for it, and a DETACHED child outlives
614
- * its parent's turn entirely, so nothing in the transcript pairs them either.
615
- *
616
- * Optional, and a store that persists nothing for it still works — it loses the tree, not the run.
617
- */
618
- parentRunId?: string;
619
- /** sha256 hex of the run's resolved (pre-RAG) system prompt — identifies the prompt VERSION. */
620
- promptHash?: string;
621
- }
622
- interface RecordRunEndInput {
623
- runId: string;
624
- /**
625
- * `cancelled` is a THIRD terminal, not a flavour of `failed`: someone asked the run to stop and it
626
- * did, which is the control working. A consumer computing a failure rate over these rows has to be
627
- * able to leave it out — counting a user pressing Stop as an error pages whoever is on call for
628
- * model failures. It carries no `errorCode`/`errorMessage`, since there is nothing to diagnose.
629
- */
630
- status: 'completed' | 'failed' | 'cancelled';
631
- durationMs?: number;
632
- errorCode?: string;
633
- errorMessage?: string;
634
- }
635
- /** Which thread, and how many of its newest messages, {@link ThreadTurnReader.loadThreadForTurn} reads. */
636
- interface ThreadTurnQuery {
637
- threadId: string;
638
- /** Omitted reads every message; `0` reads none. */
639
- messageLimit?: number;
640
- }
641
- /** What a turn reads off a thread — a bounded window, not the transcript. */
642
- interface ThreadTurnPage {
643
- title: string;
644
- defaultAgent: string | null;
645
- /** Whether the THREAD has ever been answered, not whether {@link messages} holds an answer. */
646
- hasAssistantMessage: boolean;
647
- /** Oldest first, carrying only the fields a model turn reads. */
648
- messages: StoredMessage[];
649
- }
650
- /**
651
- * A store that can hand a turn the WINDOW it is about to send, instead of the thread's transcript.
652
- *
653
- * {@link AgentStore.getThread} materializes every message row, every attachment and every tool
654
- * output a thread ever recorded, and the run then journals what it loaded — so a long thread pays
655
- * for its whole history on every turn and again on every replay, to send a prompt bounded to its
656
- * last few messages. This read is bounded by the database (`order by created_at desc limit ?`),
657
- * projected to the columns a model turn actually reads.
658
- *
659
- * `hasAssistantMessage` is answered over the WHOLE thread, never the page: it answers "has this
660
- * conversation been answered before?" — what a `thread-start` intake asks — and a thread whose window
661
- * happens to hold only the user's last questions has still been answered. `null` for a thread that is
662
- * unknown or soft-deleted, matching `getThread`.
663
- *
664
- * Probed STRUCTURALLY rather than declared on {@link AgentStore}, the same way `defaultAgentForThread`
665
- * is: it is an optimization a store either offers or does not, and one that predates it still answers
666
- * correctly through the full read.
264
+ * Probed STRUCTURALLY rather than declared on {@link AgentStore}, the same way `defaultAgentForThread`
265
+ * is: it is an optimization a store either offers or does not, and one that predates it still answers
266
+ * correctly through the full read.
667
267
  */
668
268
  interface ThreadTurnReader {
669
269
  loadThreadForTurn(query: ThreadTurnQuery): Promise<ThreadTurnPage | null>;
@@ -773,6 +373,21 @@ interface AgentStore {
773
373
  setMessageFeedback?(messageId: string, feedback: MessageFeedback | null): Promise<void>;
774
374
  recordToolCall(input: RecordToolCallInput): Promise<void>;
775
375
  updateToolCall(input: UpdateToolCallInput): Promise<void>;
376
+ /**
377
+ * OPTIONAL: what is recorded about each of these calls — its status, and the output or error it
378
+ * settled with. Read when a thread's history holds a tool call its message has no result for (the
379
+ * turn died mid-step), so the next turn can be told what actually happened instead of being
380
+ * handed a call answered by nothing. Unknown ids are left out. Absent → such a call is put to the
381
+ * model as never completed.
382
+ */
383
+ toolCallOutcomes?(toolCallIds: readonly string[]): Promise<ToolCallOutcome[]>;
384
+ /**
385
+ * OPTIONAL: settle every call of `runId` still awaiting a decision (`pending_approval`) as
386
+ * `failed` with `error`, and answer how many there were. Called when the run ends without
387
+ * settling them — it failed, or it was found dead — so an approval card never waits on a run that
388
+ * will not come back. Calls already settled are left alone, so a repeat changes nothing.
389
+ */
390
+ failUnsettledToolCalls?(runId: string, error: string): Promise<number>;
776
391
  /**
777
392
  * OPTIONAL: the names of the tools whose approval someone asked to REMEMBER in this thread — an
778
393
  * approved call persisted with `remember: true`. The loop approves a later call of one of them
@@ -1695,281 +1310,646 @@ interface ThreadSummary {
1695
1310
  */
1696
1311
  model?: string | null;
1697
1312
  }
1698
- interface StoredMessage {
1699
- id: string;
1700
- role: MessageRole;
1701
- content: string;
1702
- /** Which agent produced this message (assistant messages) — provenance for replay / UI / telescope. */
1703
- agentName?: string;
1704
- toolCalls?: ToolCallRequest[];
1705
- toolResults?: ToolResult[];
1706
- /** Files the user attached to this message (image/PDF). Persisted with the message, replayed as-is. */
1707
- attachments?: MessageAttachment[];
1708
- followUps?: string[];
1709
- usage?: MessageUsage;
1710
- /** The run (turn) that produced this message; absent on a row written before this was recorded. */
1711
- runId?: string;
1313
+ interface StoredMessage {
1314
+ id: string;
1315
+ role: MessageRole;
1316
+ content: string;
1317
+ /** Which agent produced this message (assistant messages) — provenance for replay / UI / telescope. */
1318
+ agentName?: string;
1319
+ toolCalls?: ToolCallRequest[];
1320
+ toolResults?: ToolResult[];
1321
+ /** Files the user attached to this message (image/PDF). Persisted with the message, replayed as-is. */
1322
+ attachments?: MessageAttachment[];
1323
+ followUps?: string[];
1324
+ usage?: MessageUsage;
1325
+ /** The run (turn) that produced this message; absent on a row written before this was recorded. */
1326
+ runId?: string;
1327
+ /**
1328
+ * The model's thinking for this step, as it streamed (`reasoning` frames), so a reloaded thread
1329
+ * shows it where the live one did. Absent when the model produced none, or on a row written
1330
+ * before this was recorded.
1331
+ */
1332
+ reasoning?: string;
1333
+ /** How long the model spent thinking in this step, in ms — what a "Thought for 4s" label reads. */
1334
+ reasoningMs?: number;
1335
+ /**
1336
+ * Components the server pushed into this step (`ui` frames), in first-seen order with the last
1337
+ * props for each `id` — a reloaded thread replays them as `data-ui` parts.
1338
+ */
1339
+ ui?: AgentUiComponent[];
1340
+ /**
1341
+ * The approval record of every call on this message that was put to a person under an
1342
+ * {@link import('./spi/approval-policy.js').ApprovalPolicy} — who had to decide, until when, and how
1343
+ * it settled. Read off the tool-call rows by the store; absent when no call on the message asked
1344
+ * for one, or on a store that does not record approvals.
1345
+ */
1346
+ approvals?: ToolCallApproval[];
1347
+ /**
1348
+ * The thread owner's rating of this message (`POST <base>/messages/:id/feedback`). Absent when
1349
+ * nobody rated it, or on a store that does not record feedback.
1350
+ */
1351
+ feedback?: MessageFeedback;
1352
+ /**
1353
+ * Host-defined facts about the message (which model answered, how long it took, the error a turn
1354
+ * ended with, …) — what a runner that is not this library's loop streamed as a
1355
+ * `message-metadata` frame. Replayed into the client message's `metadata`, under the library's
1356
+ * own keys (`usage`, `feedback`, `createdAt` win on a clash). Never read by the library itself.
1357
+ */
1358
+ metadata?: Record<string, unknown>;
1359
+ createdAt: string;
1360
+ }
1361
+ /** A thumbs-up/down on one message, with an optional free-text comment. */
1362
+ type MessageFeedbackValue = 'up' | 'down';
1363
+ /** What {@link StoredMessage.feedback} holds. Not copied when a thread is forked. */
1364
+ interface MessageFeedback {
1365
+ value: MessageFeedbackValue;
1366
+ comment?: string;
1367
+ /** ISO-8601 instant the rating was last set. */
1368
+ updatedAt: string;
1369
+ }
1370
+ /**
1371
+ * How one approval stands. `pending` → still parked; `approved` → someone said yes (or a remembered
1372
+ * approval did); `rejected` → someone said no; `expired` → nobody answered before `expiresAt`.
1373
+ */
1374
+ type ToolCallApprovalStatus = 'pending' | 'approved' | 'rejected' | 'expired';
1375
+ /** The persisted approval metadata of one action tool call. See {@link StoredMessage.approvals}. */
1376
+ interface ToolCallApproval {
1377
+ toolCallId: string;
1378
+ /** Who may decide: `'requester'` (the thread's own actor) or a role name. */
1379
+ approver: string;
1380
+ /** ISO-8601 instant the request lapses; absent → it never does. */
1381
+ expiresAt?: string;
1382
+ status: ToolCallApprovalStatus;
1383
+ /** The decision asked for later calls of this tool in this thread to be approved automatically. */
1384
+ remember?: boolean;
1385
+ /** Opaque ref of who decided. Absent while pending and on an expiry. */
1386
+ decidedBy?: string;
1387
+ /** The surface the decision came through (`'web'`, `'slack'`, `'remembered'`, …). */
1388
+ decidedVia?: string;
1389
+ /** What the person said when declining. */
1390
+ reason?: string;
1391
+ }
1392
+ interface ThreadDetail extends ThreadSummary {
1393
+ messages: StoredMessage[];
1394
+ /**
1395
+ * Messages sent while a turn was running, waiting to run after it, and whether the queue is
1396
+ * draining. Present when the store supports a queue (`ChatQueueStore`); the REST read-model
1397
+ * omits it otherwise.
1398
+ */
1399
+ queue?: ChatQueueState;
1400
+ }
1401
+ type ToolCallStatus = 'auto_executed' | 'pending_approval' | 'executed' | 'rejected' | 'failed'
1402
+ /** An approval request lapsed before anyone decided; the tool never ran. */
1403
+ | 'expired';
1404
+ /**
1405
+ * Serializable input for a dispatched model-turn step. Carries only data — the serving worker
1406
+ * re-resolves the model/sink/registry from its own DI via AGENT_DEPS_FACTORY.forAgent(agentName).
1407
+ */
1408
+ interface LlmStepEnvelope {
1409
+ /** Undefined = default agent (same semantics as {@link AgentRunInput.agentName}). */
1410
+ agentName?: string;
1411
+ system: string;
1412
+ messages: ModelMessage[];
1413
+ /** The turn's actor — the handler re-derives tool definitions from it (definitionsFor). */
1414
+ actor: Actor;
1415
+ /**
1416
+ * The turn's thread — with {@link actor}, what a tool's per-turn `describe` is scoped on. Optional
1417
+ * so an envelope from a loop that predates it still parses.
1418
+ */
1419
+ threadId?: string;
1420
+ /**
1421
+ * Hold this call's stream frames rather than writing them to the run's sink, and return them on
1422
+ * the result. Set by the loop when an output processor has to see the whole answer before the
1423
+ * subscriber does — the dispatched handler streams to a worker-side sink the loop cannot
1424
+ * interpose on, so the instruction has to ride the envelope. Absent → stream live, as before.
1425
+ */
1426
+ bufferOutput?: boolean;
1427
+ /** The turn's selected model ({@link AgentRunInput.model}), for the worker's provider call. */
1428
+ model?: string;
1429
+ }
1430
+ /**
1431
+ * The serializable subset of `AiToolCtx` — everything except `host` (re-attached handler-side
1432
+ * from DI).
1433
+ */
1434
+ interface ToolStepCtx {
1435
+ actor: Actor;
1436
+ threadId: string;
1437
+ runId: string;
1438
+ requestId: string;
1439
+ agentName?: string;
1440
+ pageContext?: PageContext;
1441
+ }
1442
+ /** Serializable input for a dispatched tool-execution step. */
1443
+ interface ToolStepEnvelope {
1444
+ toolName: string;
1445
+ input: unknown;
1446
+ ctx: ToolStepCtx;
1447
+ /** Applied INSIDE the handler (`withToolTimeout`) — never as a durable step `timeoutMs`. */
1448
+ timeoutMs?: number;
1449
+ /**
1450
+ * The numeric half of `toolTransientRetry` (resolved by the loop from `AgentLoopDeps`, always a
1451
+ * definite value — `false` when disabled, else concrete `{ attempts, backoffMs }` with defaults
1452
+ * already filled in) — never `undefined`, so the dispatched handler gets the SAME policy the
1453
+ * loop would have used locally. The `classify` function is deliberately absent: it isn't
1454
+ * wire-safe, so the handler resolves its own from its local module options (see
1455
+ * `AgentRunSteps.tool`) instead of trying to serialize a function.
1456
+ */
1457
+ transientRetry: ToolTransientRetryNumbers | false;
1458
+ /**
1459
+ * The dispatching loop reads the components the tool pushed (`ctx.emitUi`) off the step's result.
1460
+ * A handler that sees it returns {@link wrapToolStepOutput}'s envelope when the tool pushed any —
1461
+ * and ONLY then, so a loop that predates this (and never sets it) always gets the bare output.
1462
+ */
1463
+ collectUi?: boolean;
1464
+ }
1465
+ /**
1466
+ * What `GET <base>/config` answers — server-side facts a client would otherwise repeat in its own
1467
+ * configuration (and let drift).
1468
+ */
1469
+ interface AgentClientConfig {
1470
+ attachments: AgentAttachmentConfig;
1471
+ /** A model catalog is bound, so `GET <base>/models` lists something to pick. */
1472
+ models: {
1473
+ enabled: boolean;
1474
+ };
1475
+ /** Sends are refused with `429` once `GET <base>/quota` reports `blocked`. */
1476
+ quota: {
1477
+ enforced: boolean;
1478
+ };
1479
+ /** No `actorResolver`: every browser is its own anonymous actor. */
1480
+ identity: {
1481
+ anonymous: boolean;
1482
+ };
1483
+ }
1484
+ /** The attachment rules in force — what the upload route enforces. */
1485
+ interface AgentAttachmentConfig {
1486
+ /** A staging store is bound, so uploads work at all. */
1487
+ enabled: boolean;
1488
+ /** How a client uploads (`null` when `enabled` is false). */
1489
+ upload: 'multipart' | 'resumable' | null;
1490
+ maxBytes: number;
1491
+ allowedContentTypes: readonly string[];
1492
+ /** How many attachments one message may name. */
1493
+ maxPerMessage: number;
1494
+ }
1495
+
1496
+ /**
1497
+ * Asking the USER a structured question, and waiting for the answer.
1498
+ *
1499
+ * `awaitApproval` collects a yes/no about work already proposed; this collects the scope BEFORE the
1500
+ * work. Two surfaces produce it — a configured intake (`AgentLoopDeps.intake`) and the model-callable
1501
+ * `ask` tool (`AgentLoopDeps.ask`) — and they deliberately produce the SAME {@link
1502
+ * ElicitationRequest}, persist through the same tool-call row, and resume through the same
1503
+ * `tool:<runId>:<callId>` signal. A consumer cannot tell which one asked, and should not have to.
1504
+ */
1505
+
1506
+ /** One choice a question offers. */
1507
+ interface ElicitationOption {
1508
+ /** Stable identifier submitted back. Never shown to the user. */
1509
+ value: string;
1510
+ /** What the user reads. */
1511
+ label: string;
1512
+ /**
1513
+ * A single character a UI may bind as a keyboard shortcut for this option. Advisory — nothing in
1514
+ * the library reads it, and a client is free to render its own.
1515
+ */
1516
+ hotkey?: string;
1517
+ }
1518
+ /** One question in a set. */
1519
+ interface ElicitationQuestion {
1520
+ /** Unique within its request; the key answers come back under. */
1521
+ id: string;
1522
+ prompt: string;
1523
+ /** A line of help under the prompt. */
1524
+ description?: string;
1525
+ /**
1526
+ * The choices. Required unless {@link input} asks for a typed value — then optional, and a UI may
1527
+ * offer them as suggestions. An `input` of type `select` still picks from these.
1528
+ */
1529
+ options?: ElicitationOption[];
1530
+ /**
1531
+ * Ask for a typed value (a number, a date, a sentence…) instead of — or, for `select`, on top of —
1532
+ * a pick from `options`. The answer is still a `string[]`, in the type's canonical form; see
1533
+ * `elicitation-input.ts`.
1534
+ */
1535
+ input?: ElicitationInput;
1536
+ /** More than one option may be chosen. Omit → single choice. */
1537
+ multiple?: boolean;
1538
+ /**
1539
+ * The options already picked for the user. The claim this whole surface makes is that confirming
1540
+ * is enough, so a question with no defaults is a question the user must stop and think about —
1541
+ * which is the case the design is trying to avoid. Empty/omitted is allowed and means exactly
1542
+ * that: submitting without answering leaves this question unanswered.
1543
+ */
1544
+ defaults?: string[];
1545
+ /** Accept values that are not among `options` (a typed-in answer). Omit → options only. */
1546
+ allowFreeText?: boolean;
1547
+ }
1548
+ /**
1549
+ * A question set awaiting a human. Identical in shape whether an `@Agent`'s configured intake or
1550
+ * the model's `ask` tool authored it — `source` records which, for audit, not for control flow.
1551
+ *
1552
+ * `questions.length` is known when the request is written, which is what lets a client render
1553
+ * "Question 1 of 3" without guessing whether a fourth is coming.
1554
+ */
1555
+ interface ElicitationRequest {
1556
+ /** The tool-call id this request is persisted under, and the signal it is answered through. */
1557
+ id: string;
1558
+ source: 'intake' | 'ask';
1559
+ /** What the assistant says above the form. */
1560
+ preamble?: string;
1561
+ questions: ElicitationQuestion[];
1562
+ }
1563
+ /** What a human sent back for an {@link ElicitationRequest}. */
1564
+ interface ElicitationReply {
1712
1565
  /**
1713
- * The model's thinking for this step, as it streamed (`reasoning` frames), so a reloaded thread
1714
- * shows it where the live one did. Absent when the model produced none, or on a row written
1715
- * before this was recorded.
1566
+ * questionId → chosen values. A question whose id is ABSENT takes the request's own `defaults` —
1567
+ * that is what makes "just submit" mean "yes, your pre-picked answers". A present-but-empty array
1568
+ * is an explicit "none of these" and does NOT fall back.
1716
1569
  */
1717
- reasoning?: string;
1718
- /** How long the model spent thinking in this step, in ms — what a "Thought for 4s" label reads. */
1719
- reasoningMs?: number;
1570
+ answers: Record<string, string[]>;
1720
1571
  /**
1721
- * Components the server pushed into this step (`ui` frames), in first-seen order with the last
1722
- * props for each `id` — a reloaded thread replays them as `data-ui` parts.
1572
+ * The user declined to answer and told the agent to proceed on its own assumptions. Distinct from
1573
+ * confirming the defaults even though the resulting values are the same: one is a decision the
1574
+ * user made, the other is one they refused to make, and only the first is evidence of intent.
1723
1575
  */
1724
- ui?: AgentUiComponent[];
1576
+ skipped?: boolean;
1577
+ /** Opaque ref of WHO answered, when it wasn't the run's own actor. */
1578
+ answeredByRef?: string;
1725
1579
  /**
1726
- * The approval record of every call on this message that was put to a person under an
1727
- * {@link import('./spi/approval-policy.js').ApprovalPolicy} — who had to decide, until when, and how
1728
- * it settled. Read off the tool-call rows by the store; absent when no call on the message asked
1729
- * for one, or on a store that does not record approvals.
1580
+ * The surface the answer came through — `'web'`, `'slack'`, `'console'`, … — the counterpart of
1581
+ * an approval's `decidedVia`.
1730
1582
  */
1731
- approvals?: ToolCallApproval[];
1583
+ answeredVia?: string;
1584
+ }
1585
+ /** A settled elicitation: what the agent proceeds on, and how it got there. */
1586
+ interface ElicitationOutcome {
1587
+ /** One entry per question, in request order — always present, so a caller never re-applies defaults. */
1588
+ answers: Record<string, string[]>;
1589
+ skipped: boolean;
1590
+ /** Question ids filled from the request's `defaults` rather than by the human. */
1591
+ defaulted: string[];
1732
1592
  /**
1733
- * The thread owner's rating of this message (`POST <base>/messages/:id/feedback`). Absent when
1734
- * nobody rated it, or on a store that does not record feedback.
1593
+ * Who answered (or skipped) — the reply's `answeredByRef`, which a host may make a display name.
1594
+ * The counterpart of an approval's `decidedBy`. Absent when the reply did not say.
1735
1595
  */
1736
- feedback?: MessageFeedback;
1596
+ answeredBy?: string;
1597
+ /** The surface it came through (`'web'`, `'slack'`, …) — an approval's `decidedVia`. */
1598
+ answeredVia?: string;
1599
+ }
1600
+ /**
1601
+ * Read whatever the human channel delivered as an {@link ElicitationReply}.
1602
+ *
1603
+ * A question set is persisted as an `action` tool call in `pending_approval` — that is what puts it
1604
+ * in the approvals inbox a deployment already has, instead of needing one of its own. The cost of
1605
+ * that choice is that the thing which comes back may be a {@link Decision} someone pressed
1606
+ * Approve/Reject on rather than a set of answers, and a `Decision` carries no `answers` at all.
1607
+ *
1608
+ * Approve means every question keeps its own pre-picked `defaults`, which is exactly what "just
1609
+ * submit" already means on this surface; Reject is the same declining-to-answer a skip is. Neither
1610
+ * reading is a guess — a yes/no channel cannot say more than that, and saying it here is what lets
1611
+ * one inbox settle both kinds of pending work.
1612
+ *
1613
+ * Returns the reply UNCHANGED when it already carries answers, so the common path allocates nothing
1614
+ * and a caller can identity-compare.
1615
+ */
1616
+ declare function normalizeElicitationReply(reply: ElicitationReply | Decision): ElicitationReply;
1617
+ /**
1618
+ * Settle a reply against the request it answers: fill every unanswered question from its own
1619
+ * `defaults`, drop submitted values that aren't on offer, and collapse a single-choice question to
1620
+ * one value.
1621
+ *
1622
+ * PURE, and deliberately so. Both of its inputs are already journaled by the time the loop calls it
1623
+ * — the request came from module config or from an `llm:<i>` checkpoint, the reply from the signal
1624
+ * checkpoint — so every process replaying the turn reaches the same values without a checkpoint of
1625
+ * its own. Resolving defaults in the HTTP layer instead would put them behind a store read that a
1626
+ * replay would have to repeat.
1627
+ */
1628
+ declare function resolveElicitation(request: ElicitationRequest, raw: ElicitationReply | Decision): ElicitationOutcome;
1629
+ /**
1630
+ * What a settled elicitation looks like to everyone downstream: the model reading it back as a tool
1631
+ * result, the thread reader rendering it, the auditor asking what the agent was told to do. One
1632
+ * shape for both surfaces — nothing here records which of them asked.
1633
+ */
1634
+ interface ElicitationResult extends ElicitationOutcome {
1635
+ /** The questions against the chosen LABELS, so a reader (and a model) can act on it. */
1636
+ summary: string;
1637
+ }
1638
+ /** {@link resolveElicitation} plus its human-readable rendering. Pure, for the same reason. */
1639
+ declare function settleElicitation(request: ElicitationRequest, reply: ElicitationReply | Decision): ElicitationResult;
1640
+ /**
1641
+ * The answers as the model reads them: the question's own prompt against the chosen options' LABELS,
1642
+ * not their opaque `value`s — a model shown `{"scope":["b"]}` has been told nothing.
1643
+ */
1644
+ declare function renderElicitationAnswers(request: ElicitationRequest, outcome: ElicitationOutcome): string;
1645
+ /** The reserved tool name the model calls to ask the user something. */
1646
+ declare const ASK_TOOL_NAME = "ask";
1647
+ /** What the model must supply when it calls `ask`. */
1648
+ interface AskToolInput {
1649
+ preamble?: string;
1650
+ questions: ElicitationQuestion[];
1651
+ }
1652
+ /** How many questions one `ask` may carry. A form the user has to scroll is a form they skip. */
1653
+ declare const MAX_ASK_QUESTIONS = 5;
1654
+ /**
1655
+ * The `ask` tool's input schema, hand-written rather than borrowed from a validation library: core
1656
+ * depends on no validator, and the schema has to carry a JSON Schema a provider can constrain
1657
+ * generation against. It publishes one through the Standard JSON Schema extension
1658
+ * (`~standard.jsonSchema.input`), which is the path the AI SDK adapter already recognises for
1659
+ * Valibot / ArkType / Zod 4.
1660
+ */
1661
+ declare const askInputSchema: StandardSchemaV1<unknown, AskToolInput>;
1662
+ /**
1663
+ * What the model is told the `ask` tool is for. Written to discourage the two failure modes that
1664
+ * make a clarifying question worse than a guess: asking about something the conversation already
1665
+ * settled, and asking without saying what you would have done.
1666
+ */
1667
+ declare const ASK_TOOL_DESCRIPTION = "Ask the user to settle the scope of the work before you do it. Use it when a reasonable person would produce a materially different result depending on the answer \u2014 not to confirm something the conversation already says. Every question must pre-pick the answer you would choose, so the user can confirm instead of deciding. The user may decline, in which case you proceed on those pre-picked answers.";
1668
+ /**
1669
+ * The `ask` tool as the model sees it. NOT a `ToolSpec` and never registered: `ask` has no handler,
1670
+ * because the loop settles it against a human instead of invoking anything. Keeping it out of the
1671
+ * `ToolRegistry` is also what keeps the kind decision off a process-local lookup — see
1672
+ * `claimToolCall`.
1673
+ */
1674
+ declare function askToolDefinition(): ToolDefinition;
1675
+ /** A question set an `@Agent` asks before it starts working. See `AgentLoopDeps.intake`. */
1676
+ interface AgentIntake {
1677
+ questions: ElicitationQuestion[];
1678
+ /** What the assistant says above the form. Omit → {@link DEFAULT_INTAKE_PREAMBLE}. */
1679
+ preamble?: string;
1737
1680
  /**
1738
- * Host-defined facts about the message (which model answered, how long it took, the error a turn
1739
- * ended with, …) — what a runner that is not this library's loop streamed as a
1740
- * `message-metadata` frame. Replayed into the client message's `metadata`, under the library's
1741
- * own keys (`usage`, `feedback`, `createdAt` win on a clash). Never read by the library itself.
1681
+ * `'thread-start'` (default) asks once, on the first turn of a thread; `'every-turn'` asks before
1682
+ * every turn. Both are decided from what `load:thread` recorded about the thread when the turn
1683
+ * began, never from anything this process happens to know — by the time a replay reaches the
1684
+ * question, the thread already holds the assistant message the first attempt wrote.
1742
1685
  */
1743
- metadata?: Record<string, unknown>;
1744
- createdAt: string;
1686
+ when?: 'thread-start' | 'every-turn';
1745
1687
  }
1746
- /** A thumbs-up/down on one message, with an optional free-text comment. */
1747
- type MessageFeedbackValue = 'up' | 'down';
1748
- /** What {@link StoredMessage.feedback} holds. Not copied when a thread is forked. */
1749
- interface MessageFeedback {
1750
- value: MessageFeedbackValue;
1751
- comment?: string;
1752
- /** ISO-8601 instant the rating was last set. */
1753
- updatedAt: string;
1688
+ declare const DEFAULT_INTAKE_PREAMBLE = "A few questions before I start. I have pre-picked what I would choose, so confirming is enough.";
1689
+
1690
+ /**
1691
+ * The structured live-stream vocabulary carried over the {@link SinkWriter} byte channel.
1692
+ *
1693
+ * The model turn (via the AI-SDK adapter) and the agent loop write these events as NDJSON — one
1694
+ * `JSON.stringify(event)\n` per {@link SinkWriter.write}. The HTTP layer forwards each line as an
1695
+ * SSE `data:` frame, and the client transport maps them back to the AI SDK UI-message chunk
1696
+ * protocol so the browser renders text, reasoning, and tool cards (input + output) LIVE — the same
1697
+ * rich rendering a raw `streamText().toUIMessageStream()` would give, but reconstructed on the
1698
+ * client so the sink stays a format-agnostic byte buffer (durable buffering/replay is untouched).
1699
+ *
1700
+ * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
1701
+ * the adapter owns model-parts → event, the transport owns event → UI-chunk.
1702
+ *
1703
+ * The vocabulary is also a CONTRACT for runners that are not this library's loop: anything that
1704
+ * writes these frames (one JSON object per SSE `data:` line) gets the React transport, transcript
1705
+ * model and hooks for free. Two rules keep it evolvable:
1706
+ * - every frame is a JSON object with a string `kind`; a reader MUST tolerate kinds it does not
1707
+ * know (the React transport forwards them as `data-<kind>` parts rather than dropping them);
1708
+ * - fields are only ever added, and new fields are optional.
1709
+ */
1710
+
1711
+ /**
1712
+ * A component the server pushed into the conversation: generative UI that is NOT a tool call's
1713
+ * rendering. It is addressed by `component` (a key in the client's own component registry), never
1714
+ * by a tool name, so a runner can emit one from anywhere — a tool body, a post-processing step, a
1715
+ * sandboxed agent that renders through its own protocol.
1716
+ *
1717
+ * `id` is the component's identity within the message: a second frame with the same `id` REPLACES
1718
+ * the first (streaming props into a chart, flipping a card from "loading" to "ready"), it never
1719
+ * adds a second component.
1720
+ */
1721
+ interface AgentUiComponent {
1722
+ id: string;
1723
+ /** Registry key the client resolves to its own renderer, e.g. `data-table`. */
1724
+ component: string;
1725
+ props: Record<string, unknown>;
1726
+ /** Schema version of `props`, so a client can keep rendering components persisted by an older server. */
1727
+ version?: number;
1728
+ /**
1729
+ * The tool call that pushed the component (`ctx.emitUi`), when one did. Lets a client place it
1730
+ * with that call — a reloaded message puts it right after the call's tool part, where the live
1731
+ * stream showed it. Absent for a component pushed outside a tool.
1732
+ */
1733
+ toolCallId?: string;
1754
1734
  }
1755
1735
  /**
1756
- * How one approval stands. `pending` → still parked; `approved` → someone said yes (or a remembered
1757
- * approval did); `rejected` → someone said no; `expired` → nobody answered before `expiresAt`.
1736
+ * Who has to settle an action tool call, and until when. Metadata only: the call itself is still
1737
+ * settled through the tool-call approve/reject routes, by its `toolCallId`.
1758
1738
  */
1759
- type ToolCallApprovalStatus = 'pending' | 'approved' | 'rejected' | 'expired';
1760
- /** The persisted approval metadata of one action tool call. See {@link StoredMessage.approvals}. */
1761
- interface ToolCallApproval {
1762
- toolCallId: string;
1763
- /** Who may decide: `'requester'` (the thread's own actor) or a role name. */
1739
+ interface AgentApprovalRequest {
1740
+ /** The tool call awaiting the decision — the `id` of a call already announced on this stream. */
1741
+ id: string;
1742
+ /**
1743
+ * Who may decide. Open vocabulary the host defines — `'requester'` (the person chatting),
1744
+ * `'admin'`, a role name, a team. A client uses it to say "waiting on an admin" instead of
1745
+ * offering buttons the viewer cannot use.
1746
+ */
1764
1747
  approver: string;
1765
- /** ISO-8601 instant the request lapses; absent → it never does. */
1748
+ /** ISO-8601 instant after which the request lapses. Absent → it never expires. */
1766
1749
  expiresAt?: string;
1767
- status: ToolCallApprovalStatus;
1768
- /** The decision asked for later calls of this tool in this thread to be approved automatically. */
1769
- remember?: boolean;
1770
- /** Opaque ref of who decided. Absent while pending and on an expiry. */
1750
+ /** Why this call needs a person, in words for that person. */
1751
+ reason?: string;
1752
+ }
1753
+ /**
1754
+ * How an approval settled — the other half of {@link AgentApprovalRequest}, under the same `id`.
1755
+ * Metadata again: the call's own outcome still arrives as `tool-output` (approved and ran),
1756
+ * `tool-output-error` (approved and failed) or `tool-output-denied` (rejected or expired).
1757
+ */
1758
+ interface AgentApprovalSettlement {
1759
+ id: string;
1760
+ status: 'approved' | 'rejected' | 'expired';
1761
+ /** Repeated from the request, for a call approved without one being streamed (a remembered approval). */
1762
+ approver?: string;
1763
+ /** Opaque ref of who decided. Absent on an expiry. */
1771
1764
  decidedBy?: string;
1772
- /** The surface the decision came through (`'web'`, `'slack'`, `'remembered'`, …). */
1765
+ /** The surface the decision came through: `'web'`, `'slack'`, `'remembered'`, … */
1773
1766
  decidedVia?: string;
1767
+ /** The approval also covers later calls of this tool in this thread. */
1768
+ remember?: boolean;
1774
1769
  /** What the person said when declining. */
1775
- reason?: string;
1776
- }
1777
- interface ThreadDetail extends ThreadSummary {
1778
- messages: StoredMessage[];
1779
- /**
1780
- * Messages sent while a turn was running, waiting to run after it, and whether the queue is
1781
- * draining. Present when the store supports a queue (`ChatQueueStore`); the REST read-model
1782
- * omits it otherwise.
1783
- */
1784
- queue?: ChatQueueState;
1770
+ reason?: string;
1771
+ }
1772
+ type AgentStreamEvent = {
1773
+ kind: 'step-start';
1785
1774
  }
1786
- type ToolCallStatus = 'auto_executed' | 'pending_approval' | 'executed' | 'rejected' | 'failed'
1787
- /** An approval request lapsed before anyone decided; the tool never ran. */
1788
- | 'expired';
1789
1775
  /**
1790
- * Serializable input for a dispatched model-turn step. Carries only data — the serving worker
1791
- * re-resolves the model/sink/registry from its own DI via AGENT_DEPS_FACTORY.forAgent(agentName).
1776
+ * Closes the step opened by the matching `step-start`. Carries the model call's token usage and
1777
+ * `costUsd` (an estimate from the bound pricing store, or `null` when unpriced/unbound — never a
1778
+ * fabricated `0`) so a live client can render running cost without waiting for a thread re-fetch.
1792
1779
  */
1793
- interface LlmStepEnvelope {
1794
- /** Undefined = default agent (same semantics as {@link AgentRunInput.agentName}). */
1795
- agentName?: string;
1796
- system: string;
1797
- messages: ModelMessage[];
1798
- /** The turn's actor — the handler re-derives tool definitions from it (definitionsFor). */
1799
- actor: Actor;
1780
+ | {
1781
+ kind: 'step-finish';
1782
+ usage?: MessageUsage;
1783
+ costUsd?: number | null;
1800
1784
  /**
1801
- * The turn's thread — with {@link actor}, what a tool's per-turn `describe` is scoped on. Optional
1802
- * so an envelope from a loop that predates it still parses.
1785
+ * How long the model spent thinking in this step, in ms — the same number persisted as
1786
+ * `StoredMessage.reasoningMs`, so a live thread and a reloaded one read the same duration.
1787
+ * Absent when the step had no reasoning.
1803
1788
  */
1804
- threadId?: string;
1789
+ reasoningMs?: number;
1805
1790
  /**
1806
- * Hold this call's stream frames rather than writing them to the run's sink, and return them on
1807
- * the result. Set by the loop when an output processor has to see the whole answer before the
1808
- * subscriber does — the dispatched handler streams to a worker-side sink the loop cannot
1809
- * interpose on, so the instruction has to ride the envelope. Absent → stream live, as before.
1791
+ * The model the step ran on: the one the provider reported, else the configured `modelId`.
1792
+ * What a per-model usage report keys on (the AG-UI producer's `RUN_FINISHED.usage`). Absent
1793
+ * when neither is known; a reader that does not know the field ignores it.
1810
1794
  */
1811
- bufferOutput?: boolean;
1812
- /** The turn's selected model ({@link AgentRunInput.model}), for the worker's provider call. */
1813
1795
  model?: string;
1796
+ } | {
1797
+ kind: 'text';
1798
+ text: string;
1799
+ } | {
1800
+ kind: 'reasoning';
1801
+ text: string;
1814
1802
  }
1815
1803
  /**
1816
- * The serializable subset of `AiToolCtx` — everything except `host` (re-attached handler-side
1817
- * from DI).
1804
+ * `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool.
1805
+ *
1806
+ * `parentId` nests this call under another call on the same stream: the inner calls a code-mode
1807
+ * `execute` makes, the tools a delegated sub-agent runs. The parent must be announced first. A
1808
+ * call's parent is fixed by its first frame that names one; later frames may omit it.
1818
1809
  */
1819
- interface ToolStepCtx {
1820
- actor: Actor;
1821
- threadId: string;
1822
- runId: string;
1823
- requestId: string;
1824
- agentName?: string;
1825
- pageContext?: PageContext;
1826
- }
1827
- /** Serializable input for a dispatched tool-execution step. */
1828
- interface ToolStepEnvelope {
1829
- toolName: string;
1810
+ | {
1811
+ kind: 'tool-input-start';
1812
+ id: string;
1813
+ name: string;
1814
+ toolKind: 'read' | 'action';
1815
+ parentId?: string;
1816
+ } | {
1817
+ kind: 'tool-input-delta';
1818
+ id: string;
1819
+ delta: string;
1820
+ } | {
1821
+ kind: 'tool-input-available';
1822
+ id: string;
1823
+ name: string;
1830
1824
  input: unknown;
1831
- ctx: ToolStepCtx;
1832
- /** Applied INSIDE the handler (`withToolTimeout`) — never as a durable step `timeoutMs`. */
1833
- timeoutMs?: number;
1834
- /**
1835
- * The numeric half of `toolTransientRetry` (resolved by the loop from `AgentLoopDeps`, always a
1836
- * definite value — `false` when disabled, else concrete `{ attempts, backoffMs }` with defaults
1837
- * already filled in) — never `undefined`, so the dispatched handler gets the SAME policy the
1838
- * loop would have used locally. The `classify` function is deliberately absent: it isn't
1839
- * wire-safe, so the handler resolves its own from its local module options (see
1840
- * `AgentRunSteps.tool`) instead of trying to serialize a function.
1841
- */
1842
- transientRetry: ToolTransientRetryNumbers | false;
1843
- /**
1844
- * The dispatching loop reads the components the tool pushed (`ctx.emitUi`) off the step's result.
1845
- * A handler that sees it returns {@link wrapToolStepOutput}'s envelope when the tool pushed any —
1846
- * and ONLY then, so a loop that predates this (and never sets it) always gets the bare output.
1847
- */
1848
- collectUi?: boolean;
1825
+ toolKind: 'read' | 'action';
1826
+ /** Same as on `tool-input-start`, for a runner that announces a call without streaming its input. */
1827
+ parentId?: string;
1828
+ } | {
1829
+ kind: 'tool-output';
1830
+ id: string;
1831
+ output: unknown;
1832
+ } | {
1833
+ kind: 'tool-output-error';
1834
+ id: string;
1835
+ error: string;
1849
1836
  }
1850
1837
  /**
1851
- * What `GET <base>/config` answers — server-side facts a client would otherwise repeat in its own
1852
- * configuration (and let drift).
1838
+ * A person was asked to approve an action tool and declined it. Its own frame, NOT
1839
+ * `tool-output-error`: a refusal is a decision with a known outcome — nothing ran — while a
1840
+ * failure is an outcome nobody chose and whose effects are unknown. Rendering them the same way
1841
+ * tells an operator their own "no" was a malfunction. Maps onto the SDK's `output-denied` tool
1842
+ * part state, which a client reads without knowing any tool's name.
1853
1843
  */
1854
- interface AgentClientConfig {
1855
- attachments: AgentAttachmentConfig;
1856
- /** A model catalog is bound, so `GET <base>/models` lists something to pick. */
1857
- models: {
1858
- enabled: boolean;
1859
- };
1860
- /** Sends are refused with `429` once `GET <base>/quota` reports `blocked`. */
1861
- quota: {
1862
- enforced: boolean;
1863
- };
1864
- /** No `actorResolver`: every browser is its own anonymous actor. */
1865
- identity: {
1866
- anonymous: boolean;
1867
- };
1868
- }
1869
- /** The attachment rules in force — what the upload route enforces. */
1870
- interface AgentAttachmentConfig {
1871
- /** A staging store is bound, so uploads work at all. */
1872
- enabled: boolean;
1873
- /** How a client uploads (`null` when `enabled` is false). */
1874
- upload: 'multipart' | 'resumable' | null;
1875
- maxBytes: number;
1876
- allowedContentTypes: readonly string[];
1877
- /** How many attachments one message may name. */
1878
- maxPerMessage: number;
1844
+ | {
1845
+ kind: 'tool-output-denied';
1846
+ id: string;
1847
+ reason?: string;
1879
1848
  }
1880
-
1881
1849
  /**
1882
- * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
1883
- * on {@link AiToolCtx.actor} — read `ctx.actor.id` / `ctx.actor.tenantRef` (single source of truth;
1884
- * no denormalized copies).
1850
+ * The run has put a question set to the user and is parked until someone answers it (or skips).
1851
+ * Written by the LOOP for both elicitation surfaces — the configured intake and the model's `ask`
1852
+ * tool — so a client renders one form either way rather than learning to recognise a tool name.
1853
+ * The matching `tool-output` frame, under the same `id`, carries the settled answers.
1885
1854
  */
1886
- interface AiToolCtx {
1887
- actor: Actor;
1888
- threadId: string;
1889
- runId: string;
1890
- requestId: string;
1891
- /** The name of the agent running this turn — provenance a tool can scope on (e.g. capability sets). */
1892
- agentName?: string;
1893
- pageContext?: PageContext;
1894
- /** Optional host handle (e.g. an ORM EntityManager) the app threads through options. */
1895
- host?: unknown;
1896
- /**
1897
- * Push a component into the assistant message: streamed live as a `ui` frame and persisted on
1898
- * the message, so a reload shows it where the live stream did. Resolves to the component's id.
1899
- *
1900
- * `id` defaults to `<toolCallId>:ui:<n>` (the n-th push without an `id` in this invocation), so a retried or
1901
- * re-executed call REPLACES what it pushed before instead of adding a second copy; pass your own
1902
- * `id` to update one component across pushes (streaming rows into a table). `props` must be
1903
- * JSON; it is snapshotted when pushed.
1904
- *
1905
- * Replay-safe under the durable runner: the pushed components ride the tool step's journaled
1906
- * result, so a replay neither streams nor persists them again.
1907
- *
1908
- * Always present. On a surface with no conversation to push into (the MCP server, a direct
1909
- * `registry.invoke` without one) it is a no-op that still resolves to an id, so a tool calls
1910
- * `ctx.emitUi(…)` unconditionally.
1911
- */
1912
- emitUi(component: string, props: Record<string, unknown>, options?: {
1913
- id?: string;
1914
- version?: number;
1915
- }): Promise<{
1916
- id: string;
1917
- }>;
1855
+ | {
1856
+ kind: 'elicitation';
1857
+ id: string;
1858
+ request: ElicitationRequest;
1918
1859
  }
1919
- /** A tool implementation. `I` is the parsed (Zod-validated) input. */
1920
- interface ToolHandler<I = unknown> {
1921
- execute(input: I, ctx: AiToolCtx): Promise<unknown>;
1922
- /**
1923
- * Whether this tool exists in this deployment at all — evaluated per turn, BEFORE the roles
1924
- * policy, so a `false` here means the model is never shown the tool rather than being shown one
1925
- * it will be refused. Omit → always enabled.
1926
- *
1927
- * This is the seam for a feature flag or a licensing tier: the handler is an ordinary provider,
1928
- * so it can read injected config (`this.config.featureX`) that a decorator, evaluated at import
1929
- * time, cannot. Answering "does this capability exist here?"; `roles`/`RolesPolicy` answers the
1930
- * separate question "may THIS actor use it?", and both still run.
1931
- *
1932
- * Prefer this over conditionally registering the provider: registration happens while the
1933
- * `@Module` metadata is built, which in most apps is before configuration is loaded.
1934
- */
1935
- isEnabled?(): boolean | Promise<boolean>;
1936
- /**
1937
- * Whether THIS actor may use the tool, decided per turn. Omit → the role gate alone decides.
1938
- *
1939
- * The three existing gates all answer the question somewhere else: `roles` is static data,
1940
- * `RolesPolicy` is one app-wide rule for every tool, and an agent's `tools` allow-list is fixed
1941
- * when the agent is declared. This one lives on the tool and runs with DI, so it can ask the
1942
- * questions only the tool knows to ask — is this user's org on the plan that includes it, does
1943
- * this actor own the base being queried, is the per-user override in the DB set today.
1944
- *
1945
- * Runs AFTER {@link isEnabled} and the `RolesPolicy`, and all of them must pass. Applied both
1946
- * when the turn's tool list is built (a denied actor is never shown it) and again on invoke.
1947
- */
1948
- canUse?(actor: Actor): boolean | Promise<boolean>;
1949
- /**
1950
- * What the model is told about this tool for THIS turn — a description and/or input schema that
1951
- * depend on who is asking (a per-tenant component catalog, a per-plan list of options). Called
1952
- * when the turn's tool list is built, after every gate has passed; whatever it returns replaces
1953
- * the registered spec's `description` / `inputSchema` in the definition the model sees. Omit, or
1954
- * return `undefined`, to use the registered spec as is.
1955
- *
1956
- * It shapes what the model is SHOWN only: the registry still validates a call against the
1957
- * registered `inputSchema`, so a tool whose accepted input varies per turn registers a permissive
1958
- * schema and validates in `execute`.
1959
- */
1960
- describe?(scope: ToolDescribeScope): ToolDescription | undefined | Promise<ToolDescription | undefined>;
1860
+ /**
1861
+ * An action tool call (already announced by `tool-input-start`/`tool-input-available` under the
1862
+ * same `id`) is parked on a person. Optional: a client still treats an `action` call stuck at
1863
+ * its input as pending — this frame adds WHO has to decide and UNTIL WHEN, and moves the call
1864
+ * into the AI SDK's native `approval-requested` state.
1865
+ */
1866
+ | ({
1867
+ kind: 'approval-requested';
1868
+ } & AgentApprovalRequest)
1869
+ /**
1870
+ * A parked action call was decided (or lapsed). Optional, like `approval-requested`: it adds WHO
1871
+ * decided, THROUGH WHAT and whether the approval is REMEMBERED; the outcome itself rides the
1872
+ * call's own output frame. See {@link AgentApprovalSettlement}.
1873
+ */
1874
+ | ({
1875
+ kind: 'approval-settled';
1876
+ } & AgentApprovalSettlement)
1877
+ /**
1878
+ * Server-pushed generative UI, positioned in the message where it arrives. Not tied to a tool
1879
+ * call. See {@link AgentUiComponent}.
1880
+ */
1881
+ | ({
1882
+ kind: 'ui';
1883
+ } & AgentUiComponent)
1884
+ /**
1885
+ * The thread's title was set or changed while this run streamed (typically derived from the
1886
+ * first exchange). Thread-level, not message content: a client updates its header/sidebar and
1887
+ * does not render it in the transcript.
1888
+ */
1889
+ | {
1890
+ kind: 'title';
1891
+ title: string;
1961
1892
  }
1962
- /** Who a turn's tool list is being built for — what {@link ToolHandler.describe} can vary on. */
1963
- interface ToolDescribeScope {
1964
- actor: Actor;
1965
- /** Absent where the list is built outside a conversation (the MCP server's `tools/list`). */
1966
- threadId?: string;
1967
- agentName?: string;
1893
+ /**
1894
+ * Host-defined facts about the message being streamed (the model that answered, how long it took,
1895
+ * the error it ended with), merged into the client message's `metadata`. The persisted
1896
+ * counterpart is `StoredMessage.metadata`, so a reload reads the same values. The library's own
1897
+ * loop never writes it; a runner that is not this library's loop uses it for what its store keeps
1898
+ * per message.
1899
+ */
1900
+ | {
1901
+ kind: 'message-metadata';
1902
+ metadata: Record<string, unknown>;
1968
1903
  }
1969
- /** A per-turn override of a tool's model-facing definition ({@link ToolHandler.describe}). */
1970
- interface ToolDescription {
1971
- description?: string;
1972
- inputSchema?: StandardSchemaV1;
1904
+ /**
1905
+ * Someone stopped this run. The stream's LAST frame, written by the runner that settled the
1906
+ * cancel, immediately before a normal `end()` — never a `fail()`, because a cancel is not an
1907
+ * error and a client that retries on a failed stream must not retry this.
1908
+ *
1909
+ * A run that simply ends wrote everything it had; one that ends after this frame did not, and the
1910
+ * difference is the whole point: without it a reader cannot tell a truncated answer from a
1911
+ * complete one. Consumers that predate the frame ignore it and see the `end()` they always saw.
1912
+ */
1913
+ | {
1914
+ kind: 'cancelled';
1973
1915
  }
1916
+ /**
1917
+ * The thread's message queue changed — a snapshot of the whole queue, never a delta, so a client
1918
+ * that missed one frame is corrected by the next. Written into the stream of the run that is
1919
+ * holding the thread: when someone queues, edits, reorders or removes a waiting message, and, just
1920
+ * before this run's own terminal frame, with what happens next — `started` names the queued
1921
+ * message that became the next turn and that turn's run id (attach to it with
1922
+ * `GET <base>/chat/:runId/stream`), `queue.paused` says why nothing starts.
1923
+ */
1924
+ | {
1925
+ kind: 'queue';
1926
+ queue: ChatQueueState;
1927
+ started?: {
1928
+ messageId: string;
1929
+ runId: string;
1930
+ };
1931
+ };
1932
+ /** Encode one event as an NDJSON line (`{...}\n`) for {@link SinkWriter.write}. */
1933
+ declare function encodeStreamEvent(event: AgentStreamEvent): Uint8Array;
1934
+ /**
1935
+ * Read one NDJSON line back, or `null` when the line is not a stream event at all.
1936
+ *
1937
+ * `null` covers a genuinely opaque chunk, not just malformed JSON: the sink is a byte channel, so a
1938
+ * model provider is free to write anything into it and some write bare text. A caller that has to
1939
+ * CLASSIFY a chunk — the output gate, which may only forward what it can prove is not the answer —
1940
+ * treats an unreadable frame as unclassifiable rather than guessing.
1941
+ */
1942
+ declare function decodeStreamEvent(line: string): AgentStreamEvent | null;
1943
+ /**
1944
+ * The `code` of a failed run's `event: error` frame — what a client branches on, and translates.
1945
+ * `quota_exceeded`, `output_rejected` and `structured_output_invalid` are outcomes the library words
1946
+ * itself (their `message` is safe to show as it is); the rest are crashes, whose `message` is a
1947
+ * generic sentence in production. Open-ended on purpose: a host's own runner may send other codes.
1948
+ */
1949
+ type AgentStreamErrorCode = 'quota_exceeded' | 'output_rejected' | 'structured_output_invalid'
1950
+ /** The durable runtime refused a checkpoint position: the run's journal and its code disagree. */
1951
+ | 'replay_diverged'
1952
+ /** A model call ended without producing anything. */
1953
+ | 'model_no_output' | 'run_failed';
1974
1954
 
1975
- export { ASK_TOOL_NAME as $, type Actor as A, type ThreadDetail as B, type ChatQueueStore as C, type DetachedDelivery as D, type ElicitationRequest as E, type ToolCallApprovalState as F, type EnqueueMessageInput as G, type HumanReply as H, type QueuedMessage as I, type QueuedMessagePatch as J, type QueuePause as K, type LlmStepEnvelope as L, type ModelMessage as M, type AppendMessageInput as N, type ToolResult as O, type PageContext as P, type QuotaState as Q, type RecordRunStartInput as R, type StoredMessage as S, type ToolSpec as T, type UpdateThreadInput as U, type MessageFeedback as V, type RecordToolCallInput as W, type UpdateToolCallInput as X, type RecordUsageInput as Y, ALL_AGENTS as Z, ASK_TOOL_DESCRIPTION as _, type ToolHandler as a, validateElicitationAnswer as a$, type AgentApprovalRequest as a0, type AgentApprovalSettlement as a1, type AgentAttachmentConfig as a2, type AgentCatalogEntry as a3, type AgentClientConfig as a4, type AgentHistoryWindow as a5, type AskToolInput as a6, type ChatQueueState as a7, DEFAULT_INTAKE_PREAMBLE as a8, DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS as a9, type ToolConfirmation as aA, type ToolDescription as aB, type ToolPresentationTone as aC, type ToolResultField as aD, type ToolResultView as aE, type ToolStepCtx as aF, type ToolTransientRetryNumbers as aG, type ToolTransientRetryOptions as aH, type UsagePurpose as aI, askInputSchema as aJ, askToolDefinition as aK, decodeStreamEvent as aL, encodeStreamEvent as aM, invokeWithTransientRetry as aN, isChatQueueStore as aO, isTransientToolError as aP, isTypedQuestion as aQ, normalizeElicitationReply as aR, questionOptions as aS, queuedMessageView as aT, readElicitationInput as aU, readElicitationQuestions as aV, releaseThreadRun as aW, renderElicitationAnswers as aX, resolveElicitation as aY, resolveToolTransientRetryNumbers as aZ, settleElicitation as a_, DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS as aa, ELICITATION_INPUT_TYPES as ab, type ElicitationInput as ac, type ElicitationInputType as ad, type ElicitationOption as ae, type ElicitationOutcome as af, type ElicitationQuestion as ag, type ElicitationReply as ah, type ElicitationResult as ai, type HistoryPolicyContext as aj, type HistorySelection as ak, type HistorySummary as al, type InvokeWithTransientRetryOptions as am, MAX_ASK_QUESTIONS as an, type MessageFeedbackValue as ao, type MessageRole as ap, type PromptContext as aq, type QueuePauseReason as ar, type QueuedMessageView as as, type QuotaView as at, type RecordRunEndInput as au, type ThreadTurnPage as av, type ThreadTurnQuery as aw, type ThreadTurnReader as ax, type ToolCallApprovalStatus as ay, type ToolCatalogEntry as az, type ToolPresentation as b, validateElicitationValue as b0, type ToolDefinition as c, type ToolCallRequest as d, type MessageUsage as e, type AgentUiComponent as f, type AiToolCtx as g, type AgentStreamEvent as h, type AgentRunInput as i, type MessageAttachment as j, type ToolKind as k, type ToolCallStatus as l, type ToolCallApproval as m, type HistoryPolicy as n, type AgentDefinition as o, type AgentDelegation as p, type AgentStore as q, type ToolDescribeScope as r, type PromptBuilder as s, type PromptContributor as t, type ToolTransientRetrySetting as u, type AgentIntake as v, type Decision as w, type ToolStepEnvelope as x, type CreateThreadInput as y, type ThreadSummary as z };
1955
+ export { type AgentApprovalSettlement as $, type AgentStreamEvent as A, type QueuedMessage as B, type ChatQueueStore as C, type DetachedDelivery as D, type ElicitationRequest as E, type QueuedMessagePatch as F, type QueuePause as G, type HumanReply as H, type AppendMessageInput as I, type ToolResult as J, type MessageFeedback as K, type LlmStepEnvelope as L, type ModelMessage as M, type RecordToolCallInput as N, type ToolCallOutcome as O, type PageContext as P, type QuotaState as Q, type RecordRunStartInput as R, type StoredMessage as S, type ToolSpec as T, type UpdateThreadInput as U, type UpdateToolCallInput as V, type RecordUsageInput as W, ALL_AGENTS as X, ASK_TOOL_DESCRIPTION as Y, ASK_TOOL_NAME as Z, type AgentApprovalRequest as _, type Actor as a, settleDanglingToolCalls as a$, type AgentAttachmentConfig as a0, type AgentCatalogEntry as a1, type AgentClientConfig as a2, type AgentHistoryWindow as a3, type AgentStreamErrorCode as a4, type AskToolInput as a5, type ChatQueueState as a6, DEFAULT_INTAKE_PREAMBLE as a7, DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS as a8, DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS as a9, type ToolConfirmation as aA, type ToolPresentationTone as aB, type ToolResultField as aC, type ToolResultView as aD, type ToolStepCtx as aE, type ToolTransientRetryNumbers as aF, type ToolTransientRetryOptions as aG, UNFINISHED_TOOL_CALL as aH, type UsagePurpose as aI, askInputSchema as aJ, askToolDefinition as aK, danglingToolCallIds as aL, decodeStreamEvent as aM, encodeStreamEvent as aN, invokeWithTransientRetry as aO, isChatQueueStore as aP, isTransientToolError as aQ, isTypedQuestion as aR, normalizeElicitationReply as aS, questionOptions as aT, queuedMessageView as aU, readElicitationInput as aV, readElicitationQuestions as aW, releaseThreadRun as aX, renderElicitationAnswers as aY, resolveElicitation as aZ, resolveToolTransientRetryNumbers as a_, ELICITATION_INPUT_TYPES as aa, type ElicitationInput as ab, type ElicitationInputType as ac, type ElicitationOption as ad, type ElicitationOutcome as ae, type ElicitationQuestion as af, type ElicitationReply as ag, type ElicitationResult as ah, type HistoryPolicyContext as ai, type HistorySelection as aj, type HistorySummary as ak, type InvokeWithTransientRetryOptions as al, MAX_ASK_QUESTIONS as am, type MessageFeedbackValue as an, type MessageRole as ao, type PromptContext as ap, type QueuePauseReason as aq, type QueuedMessageView as ar, type QuotaView as as, RUN_ENDED_BEFORE_TOOL_CALL as at, type RecordRunEndInput as au, type ThreadTurnPage as av, type ThreadTurnQuery as aw, type ThreadTurnReader as ax, type ToolCallApprovalStatus as ay, type ToolCatalogEntry as az, type ToolPresentation as b, settleElicitation as b0, validateElicitationAnswer as b1, validateElicitationValue as b2, type ToolDefinition as c, type ToolCallRequest as d, type MessageUsage as e, type AgentUiComponent as f, type AgentRunInput as g, type MessageAttachment as h, type ToolKind as i, type ToolCallStatus as j, type ToolCallApproval as k, type HistoryPolicy as l, type AgentDefinition as m, type AgentStore as n, type AgentDelegation as o, type PromptBuilder as p, type PromptContributor as q, type ToolTransientRetrySetting as r, type AgentIntake as s, type Decision as t, type ToolStepEnvelope as u, type CreateThreadInput as v, type ThreadSummary as w, type ThreadDetail as x, type ToolCallApprovalState as y, type EnqueueMessageInput as z };