@dudousxd/nestjs-agent-core 0.3.2 → 0.4.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.
package/dist/index.d.cts CHANGED
@@ -48,6 +48,13 @@ interface ToolCallRequest {
48
48
  id: string;
49
49
  name: string;
50
50
  input: unknown;
51
+ /**
52
+ * The tool's declared kind (`ToolSpec.kind`), stamped by the loop from the tool registry so
53
+ * thread-read consumers know a call's kind without hardcoding a tool-name allowlist. Undefined
54
+ * only for a call the loop couldn't resolve against the registry (defensively treated as `read`
55
+ * wherever a definite value is required).
56
+ */
57
+ kind?: ToolKind;
51
58
  }
52
59
  /** Result of running a tool. */
53
60
  interface ToolResult {
@@ -82,6 +89,13 @@ interface MessageUsage {
82
89
  * non-reasoning models or providers that don't report it.
83
90
  */
84
91
  reasoningTokens?: number;
92
+ /**
93
+ * This turn's USD cost: the provider's own reported figure when it has one, else an estimate from
94
+ * the bound `AgentPricingStore` (cached once per run — see `AgentLoopDeps.pricingStore`), else
95
+ * `null` when no pricing store is bound or the model has no price row. Never `0` for an unpriced
96
+ * model — a real $0 turn and "we don't know" must stay distinguishable.
97
+ */
98
+ costUsd?: number | null;
85
99
  }
86
100
  type UsagePurpose = 'chat' | 'follow_ups';
87
101
  interface QuotaState {
@@ -107,12 +121,30 @@ interface Decision {
107
121
  reason?: string;
108
122
  }
109
123
  type MessageRole = 'user' | 'assistant' | 'system';
124
+ /**
125
+ * A file a user attached to a message so a vision-capable model sees it natively (an image, a PDF).
126
+ * The lib stays provider-agnostic: it passes {@link MessageAttachment.url} straight through as the
127
+ * model's image/file part data — making that URL reachable by the provider (presigned S3, a proxy)
128
+ * is the consumer's job. The lib never fetches bytes or talks to a store.
129
+ */
130
+ interface MessageAttachment {
131
+ /** Stable id of the stored media object in the consumer's media store. Provenance + replay key. */
132
+ mediaId: string;
133
+ /** A URL the model provider can fetch the bytes from at turn time. */
134
+ url: string;
135
+ /** MIME type — routes the part: `image/*` → image part, otherwise → file part. */
136
+ contentType: string;
137
+ /** Original filename, for display and the file part's filename. */
138
+ name: string;
139
+ }
110
140
  /** A neutral chat message exchanged with the model. */
111
141
  interface ModelMessage {
112
142
  role: MessageRole;
113
143
  content: string;
114
144
  toolCalls?: ToolCallRequest[];
115
145
  toolResults?: ToolResult[];
146
+ /** User-message attachments (image/PDF), rendered as native model content parts by the adapter. */
147
+ attachments?: MessageAttachment[];
116
148
  }
117
149
  interface PageContext {
118
150
  kind?: string;
@@ -148,6 +180,8 @@ interface AgentRunInput {
148
180
  actor: Actor;
149
181
  /** The latest user message text. */
150
182
  userText: string;
183
+ /** Files attached to the latest user message (image/PDF). Persisted with it and sent to the model. */
184
+ attachments?: MessageAttachment[];
151
185
  pageContext?: PageContext;
152
186
  /** YYYY-MM-DD stamped by the runner so quota/day stays deterministic under durable replay. */
153
187
  day?: string;
@@ -209,6 +243,18 @@ interface ThreadSummary {
209
243
  createdAt: string;
210
244
  updatedAt: string;
211
245
  lastMessagePreview?: string;
246
+ /**
247
+ * The agent a `chat()` call on this thread uses when the caller doesn't name one explicitly.
248
+ * Optional — undefined for a store that doesn't implement `AgentStore.updateThread` (the only
249
+ * way to set it). The REST/service read-model normalizes this to `null` when absent.
250
+ */
251
+ defaultAgent?: string | null;
252
+ /**
253
+ * The runId of a currently-running turn on this thread, or `null` if none is running. Optional —
254
+ * undefined for a store that doesn't implement `AgentStore.activeRunForThread`. The REST/service
255
+ * read-model normalizes this to `null` when absent, so a client can always do `?? null`.
256
+ */
257
+ activeRunId?: string | null;
212
258
  }
213
259
  interface StoredMessage {
214
260
  id: string;
@@ -218,6 +264,8 @@ interface StoredMessage {
218
264
  agentName?: string;
219
265
  toolCalls?: ToolCallRequest[];
220
266
  toolResults?: ToolResult[];
267
+ /** Files the user attached to this message (image/PDF). Persisted with the message, replayed as-is. */
268
+ attachments?: MessageAttachment[];
221
269
  followUps?: string[];
222
270
  usage?: MessageUsage;
223
271
  createdAt: string;
@@ -266,6 +314,17 @@ declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
266
314
  declare const AGENT_DEPS_FACTORY: unique symbol;
267
315
  /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
268
316
  declare const AGENT_PROMPT_CONTRIBUTORS: unique symbol;
317
+ /**
318
+ * Resolves opaque store `actorRef`s to human display labels for governance/dashboard read surfaces.
319
+ * Optional — see {@link import('./spi/actor-directory.js').ActorDirectory}.
320
+ */
321
+ declare const AGENT_ACTOR_DIRECTORY: unique symbol;
322
+ /**
323
+ * Persists an uploaded attachment somewhere the model can fetch it from and returns a
324
+ * `MessageAttachment`. Optional — see
325
+ * {@link import('./spi/attachment-staging.js').AttachmentStagingStore}.
326
+ */
327
+ declare const AGENT_ATTACHMENT_STAGING: unique symbol;
269
328
 
270
329
  /**
271
330
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -371,6 +430,67 @@ interface ModelProvider {
371
430
  runTurn(args: ModelTurnArgs): Promise<ModelTurnResult>;
372
431
  }
373
432
 
433
+ /**
434
+ * The structured live-stream vocabulary carried over the {@link SinkWriter} byte channel.
435
+ *
436
+ * The model turn (via the AI-SDK adapter) and the agent loop write these events as NDJSON — one
437
+ * `JSON.stringify(event)\n` per {@link SinkWriter.write}. The HTTP layer forwards each line as an
438
+ * SSE `data:` frame, and the client transport maps them back to the AI SDK UI-message chunk
439
+ * protocol so the browser renders text, reasoning, and tool cards (input + output) LIVE — the same
440
+ * rich rendering a raw `streamText().toUIMessageStream()` would give, but reconstructed on the
441
+ * client so the sink stays a format-agnostic byte buffer (durable buffering/replay is untouched).
442
+ *
443
+ * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
444
+ * the adapter owns model-parts → event, the transport owns event → UI-chunk.
445
+ */
446
+
447
+ type AgentStreamEvent = {
448
+ kind: 'step-start';
449
+ }
450
+ /**
451
+ * Closes the step opened by the matching `step-start`. Carries the model call's token usage and
452
+ * `costUsd` (an estimate from the bound pricing store, or `null` when unpriced/unbound — never a
453
+ * fabricated `0`) so a live client can render running cost without waiting for a thread re-fetch.
454
+ */
455
+ | {
456
+ kind: 'step-finish';
457
+ usage?: MessageUsage;
458
+ costUsd?: number | null;
459
+ } | {
460
+ kind: 'text';
461
+ text: string;
462
+ } | {
463
+ kind: 'reasoning';
464
+ text: string;
465
+ }
466
+ /** `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool. */
467
+ | {
468
+ kind: 'tool-input-start';
469
+ id: string;
470
+ name: string;
471
+ toolKind: 'read' | 'action';
472
+ } | {
473
+ kind: 'tool-input-delta';
474
+ id: string;
475
+ delta: string;
476
+ } | {
477
+ kind: 'tool-input-available';
478
+ id: string;
479
+ name: string;
480
+ input: unknown;
481
+ toolKind: 'read' | 'action';
482
+ } | {
483
+ kind: 'tool-output';
484
+ id: string;
485
+ output: unknown;
486
+ } | {
487
+ kind: 'tool-output-error';
488
+ id: string;
489
+ error: string;
490
+ };
491
+ /** Encode one event as an NDJSON line (`{...}\n`) for {@link SinkWriter.write}. */
492
+ declare function encodeStreamEvent(event: AgentStreamEvent): Uint8Array;
493
+
374
494
  interface CreateThreadInput {
375
495
  actor: Actor;
376
496
  transient?: boolean;
@@ -384,6 +504,8 @@ interface AppendMessageInput {
384
504
  agentName?: string;
385
505
  toolCalls?: ToolCallRequest[];
386
506
  toolResults?: ToolResult[];
507
+ /** Files the user attached to this message (image/PDF). Persisted verbatim. */
508
+ attachments?: MessageAttachment[];
387
509
  followUps?: string[];
388
510
  usage?: MessageUsage;
389
511
  }
@@ -403,6 +525,12 @@ interface UpdateToolCallInput {
403
525
  executionMs?: number;
404
526
  executedByRef?: string;
405
527
  }
528
+ /** Patch applied by {@link AgentStore.updateThread}. An omitted key leaves that field untouched. */
529
+ interface UpdateThreadInput {
530
+ title?: string;
531
+ /** `null` clears the thread's default agent (falls back to the module default). */
532
+ defaultAgent?: string | null;
533
+ }
406
534
  interface RecordUsageInput {
407
535
  threadId: string;
408
536
  actorRef: string;
@@ -428,6 +556,20 @@ interface AgentStore {
428
556
  */
429
557
  promoteThread(threadId: string): Promise<void>;
430
558
  setActiveStream(threadId: string, runId: string | null): Promise<void>;
559
+ /**
560
+ * OPTIONAL: rename a thread and/or set its default agent in one write. Absent on a store that
561
+ * predates this — `setTitle` still covers title-only edits, so nothing else in the lib requires
562
+ * this method; the REST `PATCH /threads/:id` endpoint responds 501 for a `defaultAgent` change
563
+ * against a store that lacks it.
564
+ */
565
+ updateThread?(threadId: string, patch: UpdateThreadInput): Promise<void>;
566
+ /**
567
+ * OPTIONAL: the runId of a currently-running turn on this thread, or `null` if none is running.
568
+ * Lets a client that reconnects (page refresh) discover a run to reattach to via the existing
569
+ * `GET /chat/:runId/stream`, instead of only being told about a run right after starting it.
570
+ * Absent on a store that predates this — thread read/list payloads report `activeRunId: null`.
571
+ */
572
+ activeRunForThread?(threadId: string): Promise<string | null>;
431
573
  /**
432
574
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
433
575
  * for thread-scoped endpoints (detail / delete / fork): the service compares this against the
@@ -695,6 +837,45 @@ interface AgentGovernanceQueries {
695
837
  recentThreads(limit: number): Promise<ThreadActivityRow[]>;
696
838
  }
697
839
 
840
+ /**
841
+ * Optional read-side lookup from opaque store `actorRef`s to human display labels.
842
+ *
843
+ * The store keeps `actorRef` opaque by design (no FK into the host's user table — hosts own their
844
+ * own identity schema). That's fine for enforcement and accounting, but a governance/dashboard read
845
+ * surface showing a raw ref ("u_8f21...") instead of a name is a bad experience. Binding an
846
+ * `ActorDirectory` lets those surfaces resolve refs to labels; leaving it unbound just means they
847
+ * render the raw ref. Consumers inject via `AGENT_ACTOR_DIRECTORY`.
848
+ */
849
+ interface ActorDirectory {
850
+ /** Resolve opaque actor refs to display labels. Missing refs may be omitted. */
851
+ resolveDisplay(refs: readonly string[]): Promise<Record<string, string>>;
852
+ }
853
+
854
+ /** Input to {@link AttachmentStagingStore.stage} — the raw bytes plus who uploaded them. */
855
+ interface StageAttachmentInput {
856
+ data: Buffer;
857
+ filename: string;
858
+ contentType: string;
859
+ sizeBytes: number;
860
+ actor: Actor;
861
+ }
862
+ /**
863
+ * Optional upload-side seam for message attachments (an image/PDF a user attaches to a chat
864
+ * message before the model ever sees it). The lib never fetches bytes itself — {@link MessageAttachment.url}
865
+ * must already be reachable by the model provider — so something has to turn an uploaded file into
866
+ * that URL first. A store adapter (or a thin wrapper over the host's own media pipeline) implements
867
+ * this; consumers inject via `AGENT_ATTACHMENT_STAGING`. Unbound, the optional `POST /agent/attachments`
868
+ * upload controller is never mounted.
869
+ */
870
+ interface AttachmentStagingStore {
871
+ /**
872
+ * Persist an uploaded file somewhere the model can later fetch (presigned URL etc.) and return the
873
+ * {@link MessageAttachment} to send with the next chat message. The lib never fetches bytes; the
874
+ * returned url must be reachable by the model provider.
875
+ */
876
+ stage(input: StageAttachmentInput): Promise<MessageAttachment>;
877
+ }
878
+
698
879
  /**
699
880
  * The pure aggregation core shared by every {@link import('../spi/governance-queries.js').AgentGovernanceQueries}
700
881
  * adapter (MikroORM, Drizzle, in-memory). An adapter's only job is the DB-specific row fetch; the
@@ -864,6 +1045,12 @@ interface AgentLoopDeps {
864
1045
  retriever?: Retriever;
865
1046
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
866
1047
  retrievalTopK?: number;
1048
+ /**
1049
+ * Prices each step's token usage into `costUsd` (on the `step-finish` stream frame and the
1050
+ * persisted assistant message's `usage`). The current price list is fetched ONCE per run (not per
1051
+ * message/step) and reused for every step's estimate. Undefined → `costUsd` is always `null`.
1052
+ */
1053
+ pricingStore?: AgentPricingStore;
867
1054
  }
868
1055
  interface AgentLoopHooks {
869
1056
  runId: string;
@@ -971,4 +1158,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
971
1158
  declare function publishAgentDelegated(payload: AgentDelegated): void;
972
1159
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
973
1160
 
974
- export { AGENT_ACTOR_RESOLVER, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorResolver, type ActorSpendRow, type AgentCatalogEntry, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type CostUsage, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type GovernanceUsageInput, type MessageRole, type MessageUsage, type ModelMessage, type ModelPrice, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type PromptBuilder, type PromptContext, type PromptContributor, QuotaExceededError, type QuotaState, type QuotaStore, type QuotaView, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadMeta, type ThreadSpendRow, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, estimateCost, filterToolsByAllowList, filterToolsByRole, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
1161
+ export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorDirectory, type ActorResolver, type ActorSpendRow, type AgentCatalogEntry, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentStreamEvent, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type AttachmentStagingStore, type CostUsage, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type GovernanceUsageInput, type MessageAttachment, type MessageRole, type MessageUsage, type ModelMessage, type ModelPrice, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type PromptBuilder, type PromptContext, type PromptContributor, QuotaExceededError, type QuotaState, type QuotaStore, type QuotaView, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StageAttachmentInput, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadMeta, type ThreadSpendRow, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateThreadInput, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, encodeStreamEvent, estimateCost, filterToolsByAllowList, filterToolsByRole, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
package/dist/index.d.ts CHANGED
@@ -48,6 +48,13 @@ interface ToolCallRequest {
48
48
  id: string;
49
49
  name: string;
50
50
  input: unknown;
51
+ /**
52
+ * The tool's declared kind (`ToolSpec.kind`), stamped by the loop from the tool registry so
53
+ * thread-read consumers know a call's kind without hardcoding a tool-name allowlist. Undefined
54
+ * only for a call the loop couldn't resolve against the registry (defensively treated as `read`
55
+ * wherever a definite value is required).
56
+ */
57
+ kind?: ToolKind;
51
58
  }
52
59
  /** Result of running a tool. */
53
60
  interface ToolResult {
@@ -82,6 +89,13 @@ interface MessageUsage {
82
89
  * non-reasoning models or providers that don't report it.
83
90
  */
84
91
  reasoningTokens?: number;
92
+ /**
93
+ * This turn's USD cost: the provider's own reported figure when it has one, else an estimate from
94
+ * the bound `AgentPricingStore` (cached once per run — see `AgentLoopDeps.pricingStore`), else
95
+ * `null` when no pricing store is bound or the model has no price row. Never `0` for an unpriced
96
+ * model — a real $0 turn and "we don't know" must stay distinguishable.
97
+ */
98
+ costUsd?: number | null;
85
99
  }
86
100
  type UsagePurpose = 'chat' | 'follow_ups';
87
101
  interface QuotaState {
@@ -107,12 +121,30 @@ interface Decision {
107
121
  reason?: string;
108
122
  }
109
123
  type MessageRole = 'user' | 'assistant' | 'system';
124
+ /**
125
+ * A file a user attached to a message so a vision-capable model sees it natively (an image, a PDF).
126
+ * The lib stays provider-agnostic: it passes {@link MessageAttachment.url} straight through as the
127
+ * model's image/file part data — making that URL reachable by the provider (presigned S3, a proxy)
128
+ * is the consumer's job. The lib never fetches bytes or talks to a store.
129
+ */
130
+ interface MessageAttachment {
131
+ /** Stable id of the stored media object in the consumer's media store. Provenance + replay key. */
132
+ mediaId: string;
133
+ /** A URL the model provider can fetch the bytes from at turn time. */
134
+ url: string;
135
+ /** MIME type — routes the part: `image/*` → image part, otherwise → file part. */
136
+ contentType: string;
137
+ /** Original filename, for display and the file part's filename. */
138
+ name: string;
139
+ }
110
140
  /** A neutral chat message exchanged with the model. */
111
141
  interface ModelMessage {
112
142
  role: MessageRole;
113
143
  content: string;
114
144
  toolCalls?: ToolCallRequest[];
115
145
  toolResults?: ToolResult[];
146
+ /** User-message attachments (image/PDF), rendered as native model content parts by the adapter. */
147
+ attachments?: MessageAttachment[];
116
148
  }
117
149
  interface PageContext {
118
150
  kind?: string;
@@ -148,6 +180,8 @@ interface AgentRunInput {
148
180
  actor: Actor;
149
181
  /** The latest user message text. */
150
182
  userText: string;
183
+ /** Files attached to the latest user message (image/PDF). Persisted with it and sent to the model. */
184
+ attachments?: MessageAttachment[];
151
185
  pageContext?: PageContext;
152
186
  /** YYYY-MM-DD stamped by the runner so quota/day stays deterministic under durable replay. */
153
187
  day?: string;
@@ -209,6 +243,18 @@ interface ThreadSummary {
209
243
  createdAt: string;
210
244
  updatedAt: string;
211
245
  lastMessagePreview?: string;
246
+ /**
247
+ * The agent a `chat()` call on this thread uses when the caller doesn't name one explicitly.
248
+ * Optional — undefined for a store that doesn't implement `AgentStore.updateThread` (the only
249
+ * way to set it). The REST/service read-model normalizes this to `null` when absent.
250
+ */
251
+ defaultAgent?: string | null;
252
+ /**
253
+ * The runId of a currently-running turn on this thread, or `null` if none is running. Optional —
254
+ * undefined for a store that doesn't implement `AgentStore.activeRunForThread`. The REST/service
255
+ * read-model normalizes this to `null` when absent, so a client can always do `?? null`.
256
+ */
257
+ activeRunId?: string | null;
212
258
  }
213
259
  interface StoredMessage {
214
260
  id: string;
@@ -218,6 +264,8 @@ interface StoredMessage {
218
264
  agentName?: string;
219
265
  toolCalls?: ToolCallRequest[];
220
266
  toolResults?: ToolResult[];
267
+ /** Files the user attached to this message (image/PDF). Persisted with the message, replayed as-is. */
268
+ attachments?: MessageAttachment[];
221
269
  followUps?: string[];
222
270
  usage?: MessageUsage;
223
271
  createdAt: string;
@@ -266,6 +314,17 @@ declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
266
314
  declare const AGENT_DEPS_FACTORY: unique symbol;
267
315
  /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
268
316
  declare const AGENT_PROMPT_CONTRIBUTORS: unique symbol;
317
+ /**
318
+ * Resolves opaque store `actorRef`s to human display labels for governance/dashboard read surfaces.
319
+ * Optional — see {@link import('./spi/actor-directory.js').ActorDirectory}.
320
+ */
321
+ declare const AGENT_ACTOR_DIRECTORY: unique symbol;
322
+ /**
323
+ * Persists an uploaded attachment somewhere the model can fetch it from and returns a
324
+ * `MessageAttachment`. Optional — see
325
+ * {@link import('./spi/attachment-staging.js').AttachmentStagingStore}.
326
+ */
327
+ declare const AGENT_ATTACHMENT_STAGING: unique symbol;
269
328
 
270
329
  /**
271
330
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -371,6 +430,67 @@ interface ModelProvider {
371
430
  runTurn(args: ModelTurnArgs): Promise<ModelTurnResult>;
372
431
  }
373
432
 
433
+ /**
434
+ * The structured live-stream vocabulary carried over the {@link SinkWriter} byte channel.
435
+ *
436
+ * The model turn (via the AI-SDK adapter) and the agent loop write these events as NDJSON — one
437
+ * `JSON.stringify(event)\n` per {@link SinkWriter.write}. The HTTP layer forwards each line as an
438
+ * SSE `data:` frame, and the client transport maps them back to the AI SDK UI-message chunk
439
+ * protocol so the browser renders text, reasoning, and tool cards (input + output) LIVE — the same
440
+ * rich rendering a raw `streamText().toUIMessageStream()` would give, but reconstructed on the
441
+ * client so the sink stays a format-agnostic byte buffer (durable buffering/replay is untouched).
442
+ *
443
+ * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
444
+ * the adapter owns model-parts → event, the transport owns event → UI-chunk.
445
+ */
446
+
447
+ type AgentStreamEvent = {
448
+ kind: 'step-start';
449
+ }
450
+ /**
451
+ * Closes the step opened by the matching `step-start`. Carries the model call's token usage and
452
+ * `costUsd` (an estimate from the bound pricing store, or `null` when unpriced/unbound — never a
453
+ * fabricated `0`) so a live client can render running cost without waiting for a thread re-fetch.
454
+ */
455
+ | {
456
+ kind: 'step-finish';
457
+ usage?: MessageUsage;
458
+ costUsd?: number | null;
459
+ } | {
460
+ kind: 'text';
461
+ text: string;
462
+ } | {
463
+ kind: 'reasoning';
464
+ text: string;
465
+ }
466
+ /** `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool. */
467
+ | {
468
+ kind: 'tool-input-start';
469
+ id: string;
470
+ name: string;
471
+ toolKind: 'read' | 'action';
472
+ } | {
473
+ kind: 'tool-input-delta';
474
+ id: string;
475
+ delta: string;
476
+ } | {
477
+ kind: 'tool-input-available';
478
+ id: string;
479
+ name: string;
480
+ input: unknown;
481
+ toolKind: 'read' | 'action';
482
+ } | {
483
+ kind: 'tool-output';
484
+ id: string;
485
+ output: unknown;
486
+ } | {
487
+ kind: 'tool-output-error';
488
+ id: string;
489
+ error: string;
490
+ };
491
+ /** Encode one event as an NDJSON line (`{...}\n`) for {@link SinkWriter.write}. */
492
+ declare function encodeStreamEvent(event: AgentStreamEvent): Uint8Array;
493
+
374
494
  interface CreateThreadInput {
375
495
  actor: Actor;
376
496
  transient?: boolean;
@@ -384,6 +504,8 @@ interface AppendMessageInput {
384
504
  agentName?: string;
385
505
  toolCalls?: ToolCallRequest[];
386
506
  toolResults?: ToolResult[];
507
+ /** Files the user attached to this message (image/PDF). Persisted verbatim. */
508
+ attachments?: MessageAttachment[];
387
509
  followUps?: string[];
388
510
  usage?: MessageUsage;
389
511
  }
@@ -403,6 +525,12 @@ interface UpdateToolCallInput {
403
525
  executionMs?: number;
404
526
  executedByRef?: string;
405
527
  }
528
+ /** Patch applied by {@link AgentStore.updateThread}. An omitted key leaves that field untouched. */
529
+ interface UpdateThreadInput {
530
+ title?: string;
531
+ /** `null` clears the thread's default agent (falls back to the module default). */
532
+ defaultAgent?: string | null;
533
+ }
406
534
  interface RecordUsageInput {
407
535
  threadId: string;
408
536
  actorRef: string;
@@ -428,6 +556,20 @@ interface AgentStore {
428
556
  */
429
557
  promoteThread(threadId: string): Promise<void>;
430
558
  setActiveStream(threadId: string, runId: string | null): Promise<void>;
559
+ /**
560
+ * OPTIONAL: rename a thread and/or set its default agent in one write. Absent on a store that
561
+ * predates this — `setTitle` still covers title-only edits, so nothing else in the lib requires
562
+ * this method; the REST `PATCH /threads/:id` endpoint responds 501 for a `defaultAgent` change
563
+ * against a store that lacks it.
564
+ */
565
+ updateThread?(threadId: string, patch: UpdateThreadInput): Promise<void>;
566
+ /**
567
+ * OPTIONAL: the runId of a currently-running turn on this thread, or `null` if none is running.
568
+ * Lets a client that reconnects (page refresh) discover a run to reattach to via the existing
569
+ * `GET /chat/:runId/stream`, instead of only being told about a run right after starting it.
570
+ * Absent on a store that predates this — thread read/list payloads report `activeRunId: null`.
571
+ */
572
+ activeRunForThread?(threadId: string): Promise<string | null>;
431
573
  /**
432
574
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
433
575
  * for thread-scoped endpoints (detail / delete / fork): the service compares this against the
@@ -695,6 +837,45 @@ interface AgentGovernanceQueries {
695
837
  recentThreads(limit: number): Promise<ThreadActivityRow[]>;
696
838
  }
697
839
 
840
+ /**
841
+ * Optional read-side lookup from opaque store `actorRef`s to human display labels.
842
+ *
843
+ * The store keeps `actorRef` opaque by design (no FK into the host's user table — hosts own their
844
+ * own identity schema). That's fine for enforcement and accounting, but a governance/dashboard read
845
+ * surface showing a raw ref ("u_8f21...") instead of a name is a bad experience. Binding an
846
+ * `ActorDirectory` lets those surfaces resolve refs to labels; leaving it unbound just means they
847
+ * render the raw ref. Consumers inject via `AGENT_ACTOR_DIRECTORY`.
848
+ */
849
+ interface ActorDirectory {
850
+ /** Resolve opaque actor refs to display labels. Missing refs may be omitted. */
851
+ resolveDisplay(refs: readonly string[]): Promise<Record<string, string>>;
852
+ }
853
+
854
+ /** Input to {@link AttachmentStagingStore.stage} — the raw bytes plus who uploaded them. */
855
+ interface StageAttachmentInput {
856
+ data: Buffer;
857
+ filename: string;
858
+ contentType: string;
859
+ sizeBytes: number;
860
+ actor: Actor;
861
+ }
862
+ /**
863
+ * Optional upload-side seam for message attachments (an image/PDF a user attaches to a chat
864
+ * message before the model ever sees it). The lib never fetches bytes itself — {@link MessageAttachment.url}
865
+ * must already be reachable by the model provider — so something has to turn an uploaded file into
866
+ * that URL first. A store adapter (or a thin wrapper over the host's own media pipeline) implements
867
+ * this; consumers inject via `AGENT_ATTACHMENT_STAGING`. Unbound, the optional `POST /agent/attachments`
868
+ * upload controller is never mounted.
869
+ */
870
+ interface AttachmentStagingStore {
871
+ /**
872
+ * Persist an uploaded file somewhere the model can later fetch (presigned URL etc.) and return the
873
+ * {@link MessageAttachment} to send with the next chat message. The lib never fetches bytes; the
874
+ * returned url must be reachable by the model provider.
875
+ */
876
+ stage(input: StageAttachmentInput): Promise<MessageAttachment>;
877
+ }
878
+
698
879
  /**
699
880
  * The pure aggregation core shared by every {@link import('../spi/governance-queries.js').AgentGovernanceQueries}
700
881
  * adapter (MikroORM, Drizzle, in-memory). An adapter's only job is the DB-specific row fetch; the
@@ -864,6 +1045,12 @@ interface AgentLoopDeps {
864
1045
  retriever?: Retriever;
865
1046
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
866
1047
  retrievalTopK?: number;
1048
+ /**
1049
+ * Prices each step's token usage into `costUsd` (on the `step-finish` stream frame and the
1050
+ * persisted assistant message's `usage`). The current price list is fetched ONCE per run (not per
1051
+ * message/step) and reused for every step's estimate. Undefined → `costUsd` is always `null`.
1052
+ */
1053
+ pricingStore?: AgentPricingStore;
867
1054
  }
868
1055
  interface AgentLoopHooks {
869
1056
  runId: string;
@@ -971,4 +1158,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
971
1158
  declare function publishAgentDelegated(payload: AgentDelegated): void;
972
1159
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
973
1160
 
974
- export { AGENT_ACTOR_RESOLVER, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorResolver, type ActorSpendRow, type AgentCatalogEntry, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type CostUsage, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type GovernanceUsageInput, type MessageRole, type MessageUsage, type ModelMessage, type ModelPrice, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type PromptBuilder, type PromptContext, type PromptContributor, QuotaExceededError, type QuotaState, type QuotaStore, type QuotaView, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadMeta, type ThreadSpendRow, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, estimateCost, filterToolsByAllowList, filterToolsByRole, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };
1161
+ export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY, AGENT_DURABLE_RUNNER, AGENT_EMBEDDING_PROVIDER, AGENT_GOVERNANCE_QUERIES, AGENT_MODEL, AGENT_OPTIONS, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_QUOTA_STORE, AGENT_REGISTRY, AGENT_RETRIEVER, AGENT_ROLES_POLICY, AGENT_RUNNER, AGENT_SINK, AGENT_STORE, AGENT_TOOL_REGISTRY, type Actor, type ActorDirectory, type ActorResolver, type ActorSpendRow, type AgentCatalogEntry, type AgentDefinition, type AgentDelegated, type AgentGovernanceQueries, type AgentLoopDeps, type AgentLoopHooks, type AgentMessageEvent, type AgentPricingStore, type AgentQuotaExceeded, AgentRegistry, type AgentRetrieved, type AgentRunFailed, type AgentRunFinished, type AgentRunInput, type AgentRunStarted, type AgentRunner, type AgentStore, AgentStreamError, type AgentStreamEvent, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, type AttachmentStagingStore, type CostUsage, type CreateThreadInput, type CurrentModelPrice, type Decision, DefaultRolesPolicy, type EmbeddingProvider, type GovernanceRange, type GovernanceUsageInput, type MessageAttachment, type MessageRole, type MessageUsage, type ModelMessage, type ModelPrice, type ModelPriceInput, type ModelProvider, type ModelSpendRow, type ModelTurnArgs, type ModelTurnResult, type PageContext, type Passage, type PromptBuilder, type PromptContext, type PromptContributor, QuotaExceededError, type QuotaState, type QuotaStore, type QuotaView, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type SinkWriter, type StageAttachmentInput, type StoredMessage, type StreamError, type ThreadActivityRow, type ThreadDetail, type ThreadMeta, type ThreadSpendRow, type ThreadSummary, type TokenStreamSink, type ToolCallActivityRow, type ToolCallRequest, type ToolCallStatus, type ToolDefinition, ToolForbiddenError, type ToolHandler, ToolInputInvalidError, type ToolKind, ToolNotFoundError, ToolRegistry, type ToolResult, type ToolSpec, type UpdateThreadInput, type UpdateToolCallInput, type UsagePurpose, type UsageTrendPoint, bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, encodeStreamEvent, estimateCost, filterToolsByAllowList, filterToolsByRole, publishAgentDelegated, publishAgentMessage, publishAgentQuotaExceeded, publishAgentRetrieved, publishAgentRunFailed, publishAgentRunFinished, publishAgentRunStarted, publishAgentToolCall, runAgentLoop, seedModelPrices };