@dudousxd/nestjs-agent 1.27.1 → 1.28.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.
@@ -0,0 +1,619 @@
1
+ import { A2uiStreamOptions } from '@dudousxd/nestjs-agent-core/a2ui';
2
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
3
+ import { Request, Response } from 'express';
4
+ import { UiCapabilities } from '@dudousxd/nestjs-agent-core/genui';
5
+ import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-B-fVOJGf.js';
6
+
7
+ /** Default per-file size cap (20 MiB). */
8
+ declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
9
+ /** Default allowlist: what multimodal model providers commonly accept as native image/file parts. */
10
+ declare const DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES: readonly string[];
11
+ /** How many attachments one message may name. */
12
+ declare const MAX_ATTACHMENTS_PER_MESSAGE = 10;
13
+ /** The attachment rules in force — `AgentAttachmentConfig`, as `GET <base>/config` serves it. */
14
+ type AttachmentLimits = AgentAttachmentConfig;
15
+ /**
16
+ * One source for attachment limits: the staging store's own `describe()` when it declares them
17
+ * (`AgentMediaAttachmentsModule` does), else `AgentModule`'s `attachments` option, else the defaults.
18
+ */
19
+ declare function attachmentLimits(options: Pick<AgentModuleOptions, 'attachments'> | undefined, staging: AttachmentStagingStore | undefined): AttachmentLimits;
20
+
21
+ declare class ActionProposalService {
22
+ private readonly store;
23
+ private readonly options;
24
+ constructor(store: AgentStore, options: Pick<AgentModuleOptions, 'approvalPolicy' | 'actionProposalText'>);
25
+ /** The words a text decision is made of, and what the agent answers — English unless configured. */
26
+ private readonly vocabulary;
27
+ private readonly replies;
28
+ private hasCapability;
29
+ private capability;
30
+ private scope;
31
+ private mayDecide;
32
+ /**
33
+ * One page of a thread's proposals this actor may see. A store without the proposal capability
34
+ * can only run blocking approvals, so a thread there has no proposals: an empty, final page rather
35
+ * than `501` — a client that lists by default (React's `useAgentChat`) needs no flag. Deciding one
36
+ * still answers `501`.
37
+ */
38
+ listPage(threadId: string, actor: Actor, query?: Pick<ListActionProposals, 'after'>): Promise<{
39
+ items: ActionProposalView[];
40
+ next?: NonNullable<ListActionProposals['after']>;
41
+ }>;
42
+ list(threadId: string, actor: Actor): Promise<ActionProposalView[]>;
43
+ decide(threadId: string, proposalId: string, actor: Actor, command: {
44
+ decision: 'approved' | 'rejected';
45
+ remember?: boolean;
46
+ reason?: string;
47
+ via?: string;
48
+ }): Promise<ActionProposalMutationView>;
49
+ handleTextDecision(threadId: string, actor: Actor, text: string): Promise<{
50
+ status: "unmatched";
51
+ } | {
52
+ threadId: string;
53
+ proposalDecision: ActionProposalMutationView;
54
+ text: string;
55
+ } | {
56
+ threadId: string;
57
+ proposalDecision: {
58
+ status: "ambiguous";
59
+ proposalIds: string[];
60
+ };
61
+ text: string;
62
+ }>;
63
+ private textReceipt;
64
+ /** The configured words a text decision is made of (`actionProposalText.vocabulary`). */
65
+ textVocabulary(): TextActionProposalVocabulary;
66
+ /** The configured reply to a decision the store answered with `result`. */
67
+ reply(result: Pick<ActionProposalMutationView, 'status' | 'proposal'>, decision: 'approved' | 'rejected'): string;
68
+ }
69
+
70
+ /**
71
+ * What a send does when its thread already has a turn running:
72
+ * - `'auto'` (default) — run now when the thread is idle, else wait in the thread's queue.
73
+ * - `'queue'` — always wait in the queue, behind whatever is already waiting (it still starts at
74
+ * once when nothing is running and nothing is ahead of it).
75
+ * - `'interrupt'` — cancel the running turn and run this one next, ahead of the queue.
76
+ */
77
+ type ChatSendMode = 'auto' | 'queue' | 'interrupt';
78
+ /** A send that is waiting in its thread's queue instead of running. */
79
+ interface QueuedSend {
80
+ threadId: string;
81
+ queued: true;
82
+ /** The queued message's id — also the run id it will start under. */
83
+ messageId: string;
84
+ /** 0-based place in the queue at the time it was queued (`0` → next). */
85
+ position: number;
86
+ queue: ChatQueueState;
87
+ /** The turn it started under, when it started straight away (an idle thread). */
88
+ runId?: string;
89
+ /** The run an interrupt cancelled to make room for it. */
90
+ interrupting?: string;
91
+ }
92
+ /** What {@link AgentService.send} did: started a turn, or queued the message. */
93
+ type ChatSendResult = {
94
+ runId: string;
95
+ threadId: string;
96
+ queued?: undefined;
97
+ } | QueuedSend | {
98
+ threadId: string;
99
+ proposalDecision: unknown;
100
+ text: string;
101
+ queued?: undefined;
102
+ };
103
+ /** The code of the `400` a send with nothing to answer gets (also AG-UI's, for the same case). */
104
+ declare const NO_USER_MESSAGE_CODE = "no_user_message";
105
+ interface ChatParams {
106
+ uiCapabilities?: UiCapabilities;
107
+ actor: Actor;
108
+ message: string;
109
+ /** The host's own facts about this send — see `AgentRunInput.hostContext`. */
110
+ hostContext?: AgentHostContext;
111
+ /**
112
+ * The HOST already decided this actor may write this thread — a chat-app thread several people
113
+ * share, where anyone in it may continue the conversation. Skips the thread-ownership check of a
114
+ * send; never set it from a request's own claim.
115
+ */
116
+ authorized?: boolean;
117
+ threadId?: string;
118
+ agentName?: string;
119
+ /**
120
+ * Run this turn under one of the agent's personas (`@Agent({ personas })`), and pin it on the
121
+ * thread so later sends that name none keep it. Refused (400 `persona_not_found`) unless the agent
122
+ * declares it. Omitted → the thread's pinned persona, else the agent's `defaultPersona`, else none.
123
+ */
124
+ personaId?: string;
125
+ /**
126
+ * Files attached to this message (image/PDF) for a vision-capable model, named by the `mediaId`
127
+ * the upload returned. Only the id is read: the url, content type and name are resolved from the
128
+ * bound {@link AttachmentStagingStore}, so nothing a caller says decides what the model fetches.
129
+ */
130
+ attachments?: AttachmentRef[];
131
+ pageContext?: PageContext;
132
+ /** Re-run the last exchange instead of adding a new message. Requires an existing `threadId`. */
133
+ regenerate?: boolean;
134
+ /**
135
+ * Run this turn on a catalog model instead of the thread's pinned one (or the provider default).
136
+ * Refused (400) unless the bound `ModelCatalog` lists it as available to this actor and agent.
137
+ */
138
+ model?: string;
139
+ /**
140
+ * When creating a thread (no `threadId`), start it transient — a scratch conversation hidden from
141
+ * the thread list until the caller promotes it. Ignored when `threadId` is set.
142
+ */
143
+ transient?: boolean;
144
+ /** What to do when the thread already has a turn running — see {@link ChatSendMode}. */
145
+ mode?: ChatSendMode;
146
+ /**
147
+ * When creating a thread (no `threadId`), ask the store to create it under this id — the id an
148
+ * AG-UI consumer named the conversation with. A store may ignore it (`CreateThreadInput.id`); the
149
+ * thread's real id is always the one on the result.
150
+ */
151
+ newThreadId?: string;
152
+ }
153
+ /**
154
+ * A store that can answer a thread's default agent on its own, without materializing the thread.
155
+ *
156
+ * Probed STRUCTURALLY rather than declared on `AgentStore`: the projection is an optimization a
157
+ * store either offers or doesn't, and a store that predates it still answers correctly through
158
+ * `getThread`. Every adapter in this repo implements it.
159
+ */
160
+ interface ThreadDefaultAgentReader {
161
+ defaultAgentForThread(threadId: string): Promise<string | null>;
162
+ }
163
+ /** Like {@link ThreadDefaultAgentReader}, for the thread's pinned model. */
164
+ interface ThreadModelReader {
165
+ modelForThread(threadId: string): Promise<string | null>;
166
+ }
167
+ /** The orchestration facade the controllers call. */
168
+ declare class AgentService {
169
+ private readonly runner;
170
+ private readonly store;
171
+ private readonly deps;
172
+ private readonly staging?;
173
+ private readonly models?;
174
+ private readonly quotaProvider?;
175
+ private readonly options?;
176
+ private readonly queue?;
177
+ private readonly proposals?;
178
+ constructor(runner: AgentRunner, store: AgentStore, deps: AgentDepsFactory, staging?: AttachmentStagingStore | undefined, models?: ModelCatalog | undefined, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota" | "actionApprovalMode" | "attachments"> | undefined, queue?: ChatQueueService | undefined, proposals?: ActionProposalService | undefined);
179
+ /**
180
+ * Decide one independent proposal through the scoped proposal service — what the native
181
+ * `action-proposals/:id/approve|reject` routes call — and the reply to show for it. `404` when
182
+ * proposals are not on, `403`/`404` when this actor may not decide it.
183
+ */
184
+ decideActionProposal(actor: Actor, threadId: string, proposalId: string, command: {
185
+ decision: 'approved' | 'rejected';
186
+ remember?: boolean;
187
+ reason?: string;
188
+ via: string;
189
+ }): Promise<{
190
+ proposalDecision: ActionProposalMutationView;
191
+ text: string;
192
+ }>;
193
+ handleTextDecision(threadId: string, actor: Actor, text: string): Promise<{
194
+ status: "unmatched";
195
+ } | {
196
+ threadId: string;
197
+ proposalDecision: ActionProposalMutationView;
198
+ text: string;
199
+ } | {
200
+ threadId: string;
201
+ proposalDecision: {
202
+ status: "ambiguous";
203
+ proposalIds: string[];
204
+ };
205
+ text: string;
206
+ }>;
207
+ /** How `action` tools wait for their approval here — `AgentModule.forRoot({ actionApprovalMode })`. */
208
+ actionApprovalMode(): ActionApprovalMode;
209
+ /**
210
+ * The words a text decision is made of (`actionProposalText.vocabulary`, English by default) — what
211
+ * a surface without buttons tells the person to reply.
212
+ */
213
+ actionProposalVocabulary(): TextActionProposalVocabulary;
214
+ /** The configured reply (`actionProposalText.replies`) to a decision the store answered with `result`. */
215
+ actionProposalReply(result: Pick<ActionProposalMutationView, 'status' | 'proposal'>, decision: 'approved' | 'rejected'): string;
216
+ /**
217
+ * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
218
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
219
+ * Without independent approvals (the default blocking mode) there are none, so the read answers an
220
+ * empty list rather than refusing; deciding one still refuses.
221
+ */
222
+ listActionProposals(actor: Actor, threadId: string): Promise<ActionProposalView[]>;
223
+ /**
224
+ * The attachment rules in force — what `GET <base>/config` serves and `POST <base>/attachments`
225
+ * enforces. `enabled: false` when no `AGENT_ATTACHMENT_STAGING` provider is bound.
226
+ */
227
+ attachmentLimits(): AttachmentLimits;
228
+ /**
229
+ * Stage a file for a later turn, held to the same limits as `POST <base>/attachments` — for a
230
+ * surface that receives files itself (a messaging webhook). Refuses with `501` when attachments are
231
+ * off, `415` for a type outside the allowlist and `413` past the size cap; send the returned
232
+ * `mediaId` as `attachments: [{ mediaId }]`.
233
+ */
234
+ stageAttachment(actor: Actor, file: {
235
+ data: Buffer;
236
+ contentType: string;
237
+ filename: string;
238
+ }): Promise<MessageAttachment>;
239
+ /** The queue service when the bound store can hold a queue, else `undefined`. */
240
+ private queueing;
241
+ /** The queue service, or a 501 naming what is missing. */
242
+ private requireQueue;
243
+ /**
244
+ * The actor's budget across windows (`GET <base>/quota`) — the bound {@link QuotaProvider}'s
245
+ * report. Without one (a service built outside the module), a day window from the ledger.
246
+ */
247
+ quotaReport(actor: Actor): Promise<QuotaReport>;
248
+ /**
249
+ * Refuse a turn the actor's budget no longer covers — only when the host configured one
250
+ * (`quota`): the default report is informational.
251
+ */
252
+ private assertWithinQuota;
253
+ /**
254
+ * The models `actor` may pick for `agent` (`GET <base>/models`). An empty catalog when none is
255
+ * bound, so a picker simply has nothing to offer.
256
+ */
257
+ listModels(actor: Actor, agent?: string): Promise<ModelCatalogView>;
258
+ /**
259
+ * `model` if the catalog offers it to this actor and agent right now, else a 400 naming why. The
260
+ * one gate every model choice passes — a send's own, and a thread's pinned one at the moment it is
261
+ * pinned and again at every turn that runs on it (availability can change in between).
262
+ */
263
+ private assertModelAllowed;
264
+ /**
265
+ * The model a turn runs on: the send's own (that turn only — it is never stored on the thread),
266
+ * else the thread's pinned one, else none. An agent the catalog locks to one model runs on it
267
+ * whatever was sent or pinned, and a send naming another model is refused.
268
+ */
269
+ private resolveModel;
270
+ private threadModel;
271
+ /**
272
+ * Start a turn — for in-process callers that need a run id back. A send that would have to wait
273
+ * (its thread already has a turn running) is refused with `409` instead; {@link send} is the form
274
+ * that queues it.
275
+ */
276
+ chat(params: ChatParams): Promise<{
277
+ runId: string;
278
+ threadId: string;
279
+ }>;
280
+ /**
281
+ * Send a message: start a turn, or — when its thread already has one running — queue the message
282
+ * to run after it (see {@link ChatSendMode}). What `POST <base>/chat` calls.
283
+ */
284
+ send(claimed: ChatParams, sendOptions?: {
285
+ textDecisions?: boolean;
286
+ }): Promise<ChatSendResult>;
287
+ /**
288
+ * Start a turn the thread is already claimed for, under the claimed id. A runner that minted an
289
+ * id of its own anyway gets the thread re-pointed at it; one that fails to start frees the thread.
290
+ */
291
+ private startClaimed;
292
+ /** Put a send in its thread's queue, then drain it if the thread turned out to be free. */
293
+ private enqueue;
294
+ /** `GET <base>/threads/:id/queue` — the thread's waiting messages and whether it drains. */
295
+ getQueue(actor: Actor, threadId: string): Promise<ChatQueueState>;
296
+ /**
297
+ * `PATCH <base>/queue/:messageId` — change a waiting message's text and/or attachments, and/or move
298
+ * it to `position` in the queue. Answers the thread's queue.
299
+ */
300
+ updateQueuedMessage(actor: Actor, messageId: string, patch: {
301
+ message?: string;
302
+ attachments?: AttachmentRef[] | null;
303
+ position?: number;
304
+ }): Promise<ChatQueueState>;
305
+ /** `DELETE <base>/queue/:messageId` — drop a waiting message. Answers the thread's queue. */
306
+ removeQueuedMessage(actor: Actor, messageId: string): Promise<ChatQueueState>;
307
+ /**
308
+ * `POST <base>/queue/:messageId/interrupt` — run a message that is already waiting NOW: it moves
309
+ * to the head of its queue marked as an interrupt, any pause is lifted, and the running turn is
310
+ * cancelled (its settle starts this message — the same path a `mode: 'interrupt'` send takes).
311
+ * With nothing running, the message starts at once.
312
+ *
313
+ * One request, and the message never leaves the queue: a client that removed it and sent it again
314
+ * would lose it if the second call failed, and would run it twice if another tab's drain started
315
+ * it in between. Answers the queue, plus `interrupting` (the run that was cancelled) or `runId`
316
+ * (the run this message started).
317
+ */
318
+ interruptQueuedMessage(actor: Actor, messageId: string): Promise<ChatQueueState & {
319
+ runId?: string;
320
+ interrupting?: string;
321
+ }>;
322
+ /** `DELETE <base>/threads/:id/queue` — drop every waiting message (and any pause). */
323
+ clearQueue(actor: Actor, threadId: string): Promise<ChatQueueState>;
324
+ /**
325
+ * `POST <base>/threads/:id/queue/resume` — lift a pause and start the head when nothing is
326
+ * running. Answers the queue, and the run the head started under, if it did.
327
+ */
328
+ resumeQueue(actor: Actor, threadId: string): Promise<ChatQueueState & {
329
+ runId?: string;
330
+ }>;
331
+ /** The thread a queued message waits on, once the caller is shown to own it. */
332
+ private assertOwnsQueuedMessage;
333
+ /**
334
+ * Rebuild this turn's attachments from the staging store, keyed by the ids the caller claimed.
335
+ * The model provider fetches {@link MessageAttachment.url} verbatim, so that url is only ever
336
+ * allowed to come from the host's own store — a caller that could name it could point the server
337
+ * at anything it can reach (cloud metadata, internal services) and read the response back out of
338
+ * the model's answer.
339
+ *
340
+ * No store bound means no way to tell an id the actor owns from one they don't, so a turn simply
341
+ * cannot carry attachments: 501 rather than trusting the request.
342
+ */
343
+ private resolveAttachments;
344
+ /**
345
+ * The run's live token stream, WITHOUT an ownership check — for in-process callers that are
346
+ * already authorized (the MCP bridge, a host's own job code), the same split as
347
+ * {@link signalToolCall} vs {@link approve}. Anything reachable from a request must go through
348
+ * {@link subscribeAs} instead: a runId is the only thing standing between a caller and another
349
+ * actor's answer, tool arguments and tool results.
350
+ */
351
+ subscribe(runId: string): AsyncIterable<Uint8Array>;
352
+ /**
353
+ * {@link subscribe}, gated the way {@link cancel} is: only the actor whose thread is currently
354
+ * streaming this run may read it. A run that has already finished is reported missing rather than
355
+ * replayed — the runner clears the thread's active stream as the run ends, and that field is what
356
+ * ownership is derived from.
357
+ */
358
+ subscribeAs(actor: Actor, runId: string): Promise<AsyncIterable<Uint8Array>>;
359
+ /**
360
+ * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
361
+ * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
362
+ * thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
363
+ * persisted with the call.
364
+ */
365
+ approve(actor: Actor, toolCallId: string, opts?: {
366
+ remember?: boolean;
367
+ via?: string;
368
+ }): Promise<void>;
369
+ reject(actor: Actor, toolCallId: string, reason?: string, opts?: {
370
+ via?: string;
371
+ }): Promise<void>;
372
+ /**
373
+ * Answer a parked question set. An omitted question takes the request's own pre-picked default —
374
+ * resolved by the LOOP against the request it already holds, so "the user just submitted" and
375
+ * "the user picked exactly the defaults" persist as the same answers, and no client has to
376
+ * re-send what it was shown.
377
+ */
378
+ answer(actor: Actor, toolCallId: string, answers?: Record<string, string[]>, opts?: {
379
+ via?: string;
380
+ }): Promise<void>;
381
+ /**
382
+ * Decline to answer and let the agent proceed on its own assumptions. NOT the same as confirming
383
+ * them: the run records this as a rejection, so a reader auditing what the agent was told can
384
+ * tell a choice the user made from one they refused to make.
385
+ */
386
+ skip(actor: Actor, toolCallId: string, opts?: {
387
+ via?: string;
388
+ }): Promise<void>;
389
+ /**
390
+ * The decision core behind approve/reject, WITHOUT an ownership check: resolves the run
391
+ * currently awaiting `toolCallId` and signals it. `approve`/`reject` above call this after their
392
+ * own `assertOwnsToolCall` gate; the console `AgentApprovalPort` adapter (bound by `AgentModule`)
393
+ * calls it directly — a console caller is already authorized by the dashboard's own guards, and
394
+ * re-deriving thread ownership here would reject a legitimate operator decision (an operator is
395
+ * not the thread's own actor).
396
+ */
397
+ signalToolCall(toolCallId: string, reply: HumanReply): Promise<void>;
398
+ /**
399
+ * Refuse a decision addressed at a run that is over (`409 run_not_active`). The runtime would take
400
+ * the signal and buffer it for a run that never comes back — the person's "yes" is accepted, the
401
+ * card says so, and nothing runs. Settles the calls the dead run left awaiting a decision on the
402
+ * way out, so the card stops asking. A runner that cannot say whether a run is alive is taken at
403
+ * its word that it is, and so is one that fails to answer.
404
+ */
405
+ private assertRunWaiting;
406
+ /** Who owns the thread, or `null` when there is no such (live) thread. */
407
+ threadOwner(threadId: string): Promise<string | null>;
408
+ /**
409
+ * May `actor` continue the run whose stream is `runId` — the AG-UI resume? Only the actor whose
410
+ * thread is streaming it (`403` otherwise), and only while it still is: a run that is over is
411
+ * waiting on no one (`409 run_not_active`), whatever an old interrupt id says.
412
+ */
413
+ assertResumable(actor: Actor, runId: string): Promise<void>;
414
+ /**
415
+ * Everything {@link approve} / {@link reject} check before they signal — who may decide, and
416
+ * that the request has not lapsed — without signalling. For a caller that settles several
417
+ * decisions at once and must refuse all of them if one is refused (an AG-UI resume).
418
+ */
419
+ checkDecision(actor: Actor, toolCallId: string): Promise<void>;
420
+ /** Everything {@link answer} checks before it signals, without signalling — see {@link checkDecision}. */
421
+ checkAnswer(actor: Actor, toolCallId: string, answers: Record<string, string[]>): Promise<void>;
422
+ cancel(actor: Actor, runId: string): Promise<void>;
423
+ listThreads(actorRef: string): Promise<ThreadSummary[]>;
424
+ getThread(actor: Actor, threadId: string): Promise<ThreadDetail | null>;
425
+ deleteThread(actor: Actor, threadId: string): Promise<void>;
426
+ forkThread(actor: Actor, threadId: string, fromMessageId: string): Promise<ThreadSummary>;
427
+ /** Kept for source compatibility — a thin wrapper over the more general `updateThread`. */
428
+ renameThread(actor: Actor, threadId: string, title: string): Promise<void>;
429
+ /**
430
+ * Rename a thread and/or set its default agent. Title-only patches work against ANY store (via
431
+ * the required `setTitle`); a `defaultAgent` change requires the bound store to implement the
432
+ * optional `updateThread` — 501 with a clear message when it doesn't, rather than silently
433
+ * dropping the field.
434
+ */
435
+ updateThread(actor: Actor, threadId: string, patch: UpdateThreadInput): Promise<void>;
436
+ /**
437
+ * The persona this send runs under — see {@link AgentDepsFactory.resolvePersona}. The thread's
438
+ * pinned persona is read only when the send names none AND the agent has personas to pick from.
439
+ */
440
+ private resolvePersona;
441
+ /** Pin `persona` on the thread when it is not already — a no-op on a store that cannot. */
442
+ private pinThreadPersona;
443
+ private validateTitle;
444
+ /**
445
+ * Agent-selection precedence for a `chat()` call with no explicit `agentName`: the thread's own
446
+ * `defaultAgent` (if the thread exists and one is set) wins over the module's configured default.
447
+ * Skips the store entirely when the caller already named an agent or there's no thread yet.
448
+ *
449
+ * What this needs is one nullable scalar, so it asks for one. `getThread` returns the entire
450
+ * transcript — every message, every persisted tool output — and this discards all of it; on a
451
+ * long thread with large tool results that is hundreds of kilobytes read per turn, outside the
452
+ * workflow, for a string. A store predating {@link ThreadDefaultAgentReader} still answers, via
453
+ * the full read.
454
+ */
455
+ private resolveAgentName;
456
+ private threadDefaultAgent;
457
+ private toSummaryView;
458
+ private toDetailView;
459
+ /**
460
+ * Re-mint each replayed attachment's url from the staging store, by `mediaId`: the url persisted
461
+ * with the message was minted for THAT turn (a presigned url with an expiry), so an old turn would
462
+ * otherwise show a dead link. The store's own access check applies — an attachment it will not
463
+ * resolve for this actor keeps the url it was stored with.
464
+ */
465
+ private freshAttachmentUrls;
466
+ promoteThread(actor: Actor, threadId: string): Promise<void>;
467
+ /**
468
+ * Drop a message and everything after it — the "edit and resend" / "delete from here" primitive.
469
+ * The client then sends a fresh turn on the truncated thread. Ownership-gated like the other
470
+ * thread-scoped mutations.
471
+ */
472
+ truncateThreadFrom(actor: Actor, threadId: string, messageId: string): Promise<void>;
473
+ /**
474
+ * Rate a message in one of `actor`'s threads: `'up'`/`'down'` with an optional comment, or `null`
475
+ * to clear the rating. Answers `404` for an unknown message, `403` for another actor's, and `501`
476
+ * on a store without `threadOfMessage` + `setMessageFeedback`.
477
+ */
478
+ setMessageFeedback(actor: Actor, messageId: string, input: {
479
+ value: MessageFeedbackValue | null;
480
+ comment?: string;
481
+ }): Promise<MessageFeedback | null>;
482
+ /**
483
+ * Every file this actor has staged, newest first — across threads, including uploads that were
484
+ * never sent, which nothing else on this surface can see.
485
+ *
486
+ * Metadata only. A url is minted per turn by `resolve` so it can be short-lived, and handing one
487
+ * back per entry would undo that for the price of rendering a list.
488
+ */
489
+ listAttachments(actor: Actor, options?: {
490
+ limit?: number;
491
+ }): Promise<StagedAttachment[]>;
492
+ /**
493
+ * This actor's staged media that no live message references and that is old enough not to be an
494
+ * upload in flight — the candidate set for a sweep. Returns them; never deletes them. The bytes
495
+ * are the host's, and so is the decision.
496
+ *
497
+ * `olderThan` is required and has no default. How long a composer may sit open with a file
498
+ * attached is the host's knowledge, and a library-chosen grace period would eventually delete a
499
+ * file someone was about to send. The staging store is asked to apply it, and the result is
500
+ * filtered again here: a store that ignores the hint would otherwise turn this into exactly that
501
+ * bug, silently.
502
+ *
503
+ * Both halves must be answerable or this refuses. An unanswerable reference query means "cannot
504
+ * tell", and treating it as "nothing is referenced" would hand back every attachment the actor
505
+ * ever sent, marked safe to delete.
506
+ */
507
+ collectableAttachments(actor: Actor, options: {
508
+ olderThan: Date;
509
+ limit?: number;
510
+ }): Promise<StagedAttachment[]>;
511
+ /**
512
+ * The bound staging store's `list`, or a refusal. Absence is never answered with an empty list:
513
+ * "this host keeps no inventory" and "this actor has no files" are opposite facts that would
514
+ * otherwise render identically — as a clean file list, and as a sweep that collects nothing.
515
+ */
516
+ private attachmentInventory;
517
+ /**
518
+ * Authorization seam for thread-scoped endpoints: the caller must own the thread. A missing
519
+ * thread is a 404; someone else's thread is a 403 — one actor never touches another's thread.
520
+ */
521
+ private assertOwnsThread;
522
+ /**
523
+ * Authorization seam for HITL approve/reject: the caller must own the tool call's thread. Split
524
+ * out from run resolution (see {@link resolveRunForToolCall}) so the console `AgentApprovalPort`
525
+ * adapter can resolve+signal a decision WITHOUT this check — it is authorized upstream instead.
526
+ */
527
+ private assertOwnsToolCall;
528
+ /**
529
+ * Check a reply against the questions it answers before it is signalled: every submitted value a
530
+ * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
531
+ * options), and every `required` question answered — by the reply, or by the defaults an omitted
532
+ * question falls back to. A reply the loop would have to drop parts of is refused here instead, so
533
+ * the person who typed it hears why. Needs the store to hand back the call's recorded questions;
534
+ * without that seam the loop's own filtering is the only check.
535
+ */
536
+ private assertAnswersFit;
537
+ /**
538
+ * Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
539
+ * the requester (the default, and every call recorded before approvers existed) is the thread's
540
+ * own actor — the ownership check this always was; any other approver goes through the policy's
541
+ * `canDecide`, which by default asks whether the actor holds that role.
542
+ */
543
+ private assertMayDecide;
544
+ /**
545
+ * Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
546
+ * `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
547
+ * a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
548
+ * later replay of the run reads back.
549
+ */
550
+ private assertNotExpired;
551
+ /**
552
+ * Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
553
+ * never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
554
+ * call belongs to a delegated agent. The store answers from the CALL'S OWN `runId` where it has
555
+ * one; see `AgentStore.runForToolCall` for why that matters once a thread can hold more than one
556
+ * live run.
557
+ */
558
+ private resolveRunForToolCall;
559
+ /** Authorization seam for `cancel`: the caller must own the thread currently streaming this run. */
560
+ private assertOwnsActiveStream;
561
+ }
562
+
563
+ interface A2uiAdapterOptions extends A2uiStreamOptions {
564
+ /** Where the route mounts, under the agent's path. Default `'a2ui'` → `POST <path>/a2ui`. */
565
+ path?: string;
566
+ /** As `agUiAdapter`'s: how long a run waiting on a person may stay silent before the stream ends. */
567
+ quietMs?: number;
568
+ /** Largest action accepted, as JSON, in bytes. Default 8192. */
569
+ maxActionBytes?: number;
570
+ }
571
+ /**
572
+ * Serves the agent as an A2UI agent. Not mounted directly — {@link a2uiAdapter} mounts a subclass at
573
+ * the configured path — but exported so a host can extend it under a route of its own.
574
+ */
575
+ declare class A2uiRunHandler {
576
+ protected readonly agent: AgentService;
577
+ protected readonly actorResolver: ActorResolver;
578
+ protected readonly projection: A2uiStreamOptions;
579
+ protected readonly quietMs: number | undefined;
580
+ protected readonly maxActionBytes: number | undefined;
581
+ constructor(agent: AgentService, actorResolver: ActorResolver, projection: A2uiStreamOptions, quietMs: number | undefined, maxActionBytes: number | undefined);
582
+ handle(req: Request, res: Response, raw: unknown): Promise<void>;
583
+ /** Open the JSON Lines response. */
584
+ protected writeHead(res: Response, extra: Record<string, string>): void;
585
+ /** Write one run as A2UI JSON Lines, until it ends or stops to ask. */
586
+ protected stream(res: Response, runId: string, options: {
587
+ threadId?: string;
588
+ skip?: number;
589
+ answered?: string[];
590
+ }): Promise<void>;
591
+ }
592
+ /**
593
+ * Serve the agent as an A2UI (https://a2ui.org, v0.9) agent: `POST <path>/a2ui` answers with the
594
+ * run as a JSON Lines stream of A2UI server-to-client messages — `createSurface`,
595
+ * `updateComponents`, `updateDataModel`, `deleteSurface` — that any A2UI renderer draws.
596
+ *
597
+ * ```ts
598
+ * import { a2uiAdapter } from '@dudousxd/nestjs-agent/a2ui'
599
+ *
600
+ * AgentModule.forRoot({ model, adapters: [a2uiAdapter()] })
601
+ * ```
602
+ *
603
+ * The body is `{ threadId?, message?, action?, uiCapabilities?, agent? }`:
604
+ * - `message` — the user's text;
605
+ * - `action` — an A2UI client message (`{ version: 'v0.9', action: { name, surfaceId,
606
+ * sourceComponentId, timestamp, context } }`, or v0.8's `{ userAction }`): it becomes the turn
607
+ * (`uiActionText`). `agora.approve` / `agora.reject` with `context.interruptId` decide the
608
+ * approval a previous stream ended on, and stream the rest of that run (an independent
609
+ * proposal is decided through the proposal service and answered with its reply);
610
+ * - `threadId` — continue that thread (its owner is checked), or start one under that id.
611
+ *
612
+ * Response headers name the library's ids: `X-Agent-Thread-Id`, `X-Agent-Run-Id`. Authenticated by
613
+ * the module's `actorResolver`, guarded by its `guards` and owner-scoped exactly as `chat` is.
614
+ * Components nothing maps are drawn as their text, from the `AgentGenuiModule` catalog when there
615
+ * is one (or `catalog` here).
616
+ */
617
+ declare function a2uiAdapter(options?: A2uiAdapterOptions): AgentProtocolAdapter;
618
+
619
+ export { AgentService as A, type ChatParams as C, DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES as D, MAX_ATTACHMENTS_PER_MESSAGE as M, NO_USER_MESSAGE_CODE as N, type QueuedSend as Q, type ThreadDefaultAgentReader as T, ActionProposalService as a, type A2uiAdapterOptions as b, A2uiRunHandler as c, type AttachmentLimits as d, type ChatSendMode as e, type ChatSendResult as f, DEFAULT_MAX_ATTACHMENT_BYTES as g, type ThreadModelReader as h, a2uiAdapter as i, attachmentLimits as j };