@dudousxd/nestjs-agent-core 0.3.3 → 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 {
@@ -229,6 +243,18 @@ interface ThreadSummary {
229
243
  createdAt: string;
230
244
  updatedAt: string;
231
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;
232
258
  }
233
259
  interface StoredMessage {
234
260
  id: string;
@@ -288,6 +314,17 @@ declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
288
314
  declare const AGENT_DEPS_FACTORY: unique symbol;
289
315
  /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
290
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;
291
328
 
292
329
  /**
293
330
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -406,20 +443,32 @@ interface ModelProvider {
406
443
  * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
407
444
  * the adapter owns model-parts → event, the transport owns event → UI-chunk.
408
445
  */
446
+
409
447
  type AgentStreamEvent = {
410
448
  kind: 'step-start';
411
- } | {
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
+ | {
412
456
  kind: 'step-finish';
457
+ usage?: MessageUsage;
458
+ costUsd?: number | null;
413
459
  } | {
414
460
  kind: 'text';
415
461
  text: string;
416
462
  } | {
417
463
  kind: 'reasoning';
418
464
  text: string;
419
- } | {
465
+ }
466
+ /** `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool. */
467
+ | {
420
468
  kind: 'tool-input-start';
421
469
  id: string;
422
470
  name: string;
471
+ toolKind: 'read' | 'action';
423
472
  } | {
424
473
  kind: 'tool-input-delta';
425
474
  id: string;
@@ -429,6 +478,7 @@ type AgentStreamEvent = {
429
478
  id: string;
430
479
  name: string;
431
480
  input: unknown;
481
+ toolKind: 'read' | 'action';
432
482
  } | {
433
483
  kind: 'tool-output';
434
484
  id: string;
@@ -475,6 +525,12 @@ interface UpdateToolCallInput {
475
525
  executionMs?: number;
476
526
  executedByRef?: string;
477
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
+ }
478
534
  interface RecordUsageInput {
479
535
  threadId: string;
480
536
  actorRef: string;
@@ -500,6 +556,20 @@ interface AgentStore {
500
556
  */
501
557
  promoteThread(threadId: string): Promise<void>;
502
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>;
503
573
  /**
504
574
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
505
575
  * for thread-scoped endpoints (detail / delete / fork): the service compares this against the
@@ -767,6 +837,45 @@ interface AgentGovernanceQueries {
767
837
  recentThreads(limit: number): Promise<ThreadActivityRow[]>;
768
838
  }
769
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
+
770
879
  /**
771
880
  * The pure aggregation core shared by every {@link import('../spi/governance-queries.js').AgentGovernanceQueries}
772
881
  * adapter (MikroORM, Drizzle, in-memory). An adapter's only job is the DB-specific row fetch; the
@@ -936,6 +1045,12 @@ interface AgentLoopDeps {
936
1045
  retriever?: Retriever;
937
1046
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
938
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;
939
1054
  }
940
1055
  interface AgentLoopHooks {
941
1056
  runId: string;
@@ -1043,4 +1158,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
1043
1158
  declare function publishAgentDelegated(payload: AgentDelegated): void;
1044
1159
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
1045
1160
 
1046
- 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 AgentStreamEvent, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, 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 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, encodeStreamEvent, 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 {
@@ -229,6 +243,18 @@ interface ThreadSummary {
229
243
  createdAt: string;
230
244
  updatedAt: string;
231
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;
232
258
  }
233
259
  interface StoredMessage {
234
260
  id: string;
@@ -288,6 +314,17 @@ declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
288
314
  declare const AGENT_DEPS_FACTORY: unique symbol;
289
315
  /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
290
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;
291
328
 
292
329
  /**
293
330
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -406,20 +443,32 @@ interface ModelProvider {
406
443
  * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
407
444
  * the adapter owns model-parts → event, the transport owns event → UI-chunk.
408
445
  */
446
+
409
447
  type AgentStreamEvent = {
410
448
  kind: 'step-start';
411
- } | {
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
+ | {
412
456
  kind: 'step-finish';
457
+ usage?: MessageUsage;
458
+ costUsd?: number | null;
413
459
  } | {
414
460
  kind: 'text';
415
461
  text: string;
416
462
  } | {
417
463
  kind: 'reasoning';
418
464
  text: string;
419
- } | {
465
+ }
466
+ /** `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool. */
467
+ | {
420
468
  kind: 'tool-input-start';
421
469
  id: string;
422
470
  name: string;
471
+ toolKind: 'read' | 'action';
423
472
  } | {
424
473
  kind: 'tool-input-delta';
425
474
  id: string;
@@ -429,6 +478,7 @@ type AgentStreamEvent = {
429
478
  id: string;
430
479
  name: string;
431
480
  input: unknown;
481
+ toolKind: 'read' | 'action';
432
482
  } | {
433
483
  kind: 'tool-output';
434
484
  id: string;
@@ -475,6 +525,12 @@ interface UpdateToolCallInput {
475
525
  executionMs?: number;
476
526
  executedByRef?: string;
477
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
+ }
478
534
  interface RecordUsageInput {
479
535
  threadId: string;
480
536
  actorRef: string;
@@ -500,6 +556,20 @@ interface AgentStore {
500
556
  */
501
557
  promoteThread(threadId: string): Promise<void>;
502
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>;
503
573
  /**
504
574
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
505
575
  * for thread-scoped endpoints (detail / delete / fork): the service compares this against the
@@ -767,6 +837,45 @@ interface AgentGovernanceQueries {
767
837
  recentThreads(limit: number): Promise<ThreadActivityRow[]>;
768
838
  }
769
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
+
770
879
  /**
771
880
  * The pure aggregation core shared by every {@link import('../spi/governance-queries.js').AgentGovernanceQueries}
772
881
  * adapter (MikroORM, Drizzle, in-memory). An adapter's only job is the DB-specific row fetch; the
@@ -936,6 +1045,12 @@ interface AgentLoopDeps {
936
1045
  retriever?: Retriever;
937
1046
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
938
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;
939
1054
  }
940
1055
  interface AgentLoopHooks {
941
1056
  runId: string;
@@ -1043,4 +1158,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
1043
1158
  declare function publishAgentDelegated(payload: AgentDelegated): void;
1044
1159
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
1045
1160
 
1046
- 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 AgentStreamEvent, type AgentToolCallEvent, type AiToolCtx, type AppendMessageInput, 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 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, encodeStreamEvent, 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.js CHANGED
@@ -19,6 +19,8 @@ var AGENT_RETRIEVER = Symbol.for("@dudousxd/nestjs-agent:retriever");
19
19
  var AGENT_EMBEDDING_PROVIDER = Symbol.for("@dudousxd/nestjs-agent:embedding-provider");
20
20
  var AGENT_DEPS_FACTORY = Symbol.for("@dudousxd/nestjs-agent:deps-factory");
21
21
  var AGENT_PROMPT_CONTRIBUTORS = Symbol.for("@dudousxd/nestjs-agent:prompt-contributors");
22
+ var AGENT_ACTOR_DIRECTORY = Symbol.for("@dudousxd/nestjs-agent:actor-directory");
23
+ var AGENT_ATTACHMENT_STAGING = Symbol.for("@dudousxd/nestjs-agent:attachment-staging");
22
24
 
23
25
  // src/spi/token-stream-sink.ts
24
26
  var AgentStreamError = class extends Error {
@@ -358,6 +360,13 @@ function publishAgentRetrieved(payload) {
358
360
  __name(publishAgentRetrieved, "publishAgentRetrieved");
359
361
 
360
362
  // src/agent-loop.ts
363
+ function resolveCostUsd(usage, reportedCostUsd, price) {
364
+ if (reportedCostUsd !== void 0) {
365
+ return reportedCostUsd;
366
+ }
367
+ return price === void 0 ? null : estimateCost(usage, price);
368
+ }
369
+ __name(resolveCostUsd, "resolveCostUsd");
361
370
  function buildContextBlock(passages) {
362
371
  const items = passages.map((passage, index) => {
363
372
  const label = passage.source !== void 0 ? ` (${passage.source})` : "";
@@ -562,6 +571,15 @@ ${buildContextBlock(passages)}`;
562
571
  count: passages.length
563
572
  });
564
573
  }
574
+ let prices = [];
575
+ if (deps.pricingStore !== void 0) {
576
+ const pricingStore = deps.pricingStore;
577
+ prices = await hooks.step("pricing:list", () => pricingStore.listCurrentPrices());
578
+ }
579
+ const priceByModel = new Map(prices.map((price) => [
580
+ price.modelId,
581
+ price
582
+ ]));
565
583
  for (let i = 0; i < maxSteps; i += 1) {
566
584
  await hooks.step(`stream:step-start:${i}`, async () => {
567
585
  await writer.write(encodeStreamEvent({
@@ -575,11 +593,16 @@ ${buildContextBlock(passages)}`;
575
593
  tools,
576
594
  sink: writer
577
595
  }));
596
+ const resolvedModelId = turn.modelId ?? deps.modelId ?? "unknown";
597
+ const costUsd = resolveCostUsd(turn.usage, turn.costUsd, priceByModel.get(resolvedModelId));
598
+ const toolCallsWithKind = turn.toolCalls.map((call) => ({
599
+ ...call,
600
+ kind: deps.registry.spec(call.name)?.kind ?? "read"
601
+ }));
578
602
  await hooks.step(`persist:usage:${i}`, () => deps.store.recordUsage({
579
603
  threadId: input.threadId,
580
604
  actorRef: input.actor.id,
581
- // provider-reported model wins over the configured fallback, so cost can't misattribute
582
- modelId: turn.modelId ?? deps.modelId ?? "unknown",
605
+ modelId: resolvedModelId,
583
606
  purpose: "chat",
584
607
  usage: turn.usage,
585
608
  // persist the provider's actual cost when reported; the read-model prefers it over pricing
@@ -627,12 +650,15 @@ ${buildContextBlock(passages)}`;
627
650
  threadId: input.threadId,
628
651
  role: "assistant",
629
652
  content: turn.text,
630
- usage: turn.usage,
653
+ usage: {
654
+ ...turn.usage,
655
+ costUsd
656
+ },
631
657
  ...input.agentName !== void 0 ? {
632
658
  agentName: input.agentName
633
659
  } : {},
634
- ...turn.toolCalls.length > 0 ? {
635
- toolCalls: turn.toolCalls
660
+ ...toolCallsWithKind.length > 0 ? {
661
+ toolCalls: toolCallsWithKind
636
662
  } : {},
637
663
  ...followUps !== void 0 ? {
638
664
  followUps
@@ -641,8 +667,8 @@ ${buildContextBlock(passages)}`;
641
667
  const assistantMessage = {
642
668
  role: "assistant",
643
669
  content: turn.text,
644
- ...turn.toolCalls.length > 0 ? {
645
- toolCalls: turn.toolCalls
670
+ ...toolCallsWithKind.length > 0 ? {
671
+ toolCalls: toolCallsWithKind
646
672
  } : {}
647
673
  };
648
674
  modelMessages.push(assistantMessage);
@@ -672,15 +698,17 @@ ${buildContextBlock(passages)}`;
672
698
  if (isFinalTurn) {
673
699
  await hooks.step(`stream:step-finish:${i}`, async () => {
674
700
  await writer.write(encodeStreamEvent({
675
- kind: "step-finish"
701
+ kind: "step-finish",
702
+ usage: turn.usage,
703
+ costUsd
676
704
  }));
677
705
  });
678
706
  break;
679
707
  }
680
708
  const results = [];
681
- for (const call of turn.toolCalls) {
709
+ for (const call of toolCallsWithKind) {
682
710
  const spec = deps.registry.spec(call.name);
683
- const toolType = spec?.kind ?? "read";
711
+ const toolType = call.kind ?? "read";
684
712
  const ctx = {
685
713
  actor: input.actor,
686
714
  threadId: input.threadId,
@@ -850,7 +878,9 @@ ${buildContextBlock(passages)}`;
850
878
  });
851
879
  await hooks.step(`stream:step-finish:${i}`, async () => {
852
880
  await writer.write(encodeStreamEvent({
853
- kind: "step-finish"
881
+ kind: "step-finish",
882
+ usage: turn.usage,
883
+ costUsd
854
884
  }));
855
885
  });
856
886
  }
@@ -871,7 +901,9 @@ ${buildContextBlock(passages)}`;
871
901
  }
872
902
  __name(runAgentLoop, "runAgentLoop");
873
903
  export {
904
+ AGENT_ACTOR_DIRECTORY,
874
905
  AGENT_ACTOR_RESOLVER,
906
+ AGENT_ATTACHMENT_STAGING,
875
907
  AGENT_DEPS_FACTORY,
876
908
  AGENT_DURABLE_RUNNER,
877
909
  AGENT_EMBEDDING_PROVIDER,