@dudousxd/nestjs-agent-core 0.3.3 → 0.5.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.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;
@@ -249,6 +275,38 @@ interface ThreadDetail extends ThreadSummary {
249
275
  activeStreamId?: string;
250
276
  }
251
277
  type ToolCallStatus = 'auto_executed' | 'pending_approval' | 'executed' | 'rejected' | 'failed';
278
+ /**
279
+ * Serializable input for a dispatched model-turn step. Carries only data — the serving worker
280
+ * re-resolves the model/sink/registry from its own DI via AGENT_DEPS_FACTORY.forAgent(agentName).
281
+ */
282
+ interface LlmStepEnvelope {
283
+ /** Undefined = default agent (same semantics as {@link AgentRunInput.agentName}). */
284
+ agentName?: string;
285
+ system: string;
286
+ messages: ModelMessage[];
287
+ /** The turn's actor — the handler re-derives tool definitions from it (definitionsFor). */
288
+ actor: Actor;
289
+ }
290
+ /**
291
+ * The serializable subset of `AiToolCtx` — everything except `host` (re-attached handler-side
292
+ * from DI).
293
+ */
294
+ interface ToolStepCtx {
295
+ actor: Actor;
296
+ threadId: string;
297
+ runId: string;
298
+ requestId: string;
299
+ agentName?: string;
300
+ pageContext?: PageContext;
301
+ }
302
+ /** Serializable input for a dispatched tool-execution step. */
303
+ interface ToolStepEnvelope {
304
+ toolName: string;
305
+ input: unknown;
306
+ ctx: ToolStepCtx;
307
+ /** Applied INSIDE the handler (`withToolTimeout`) — never as a durable step `timeoutMs`. */
308
+ timeoutMs?: number;
309
+ }
252
310
 
253
311
  /**
254
312
  * Public, cross-lib-discoverable DI tokens.
@@ -288,6 +346,17 @@ declare const AGENT_EMBEDDING_PROVIDER: unique symbol;
288
346
  declare const AGENT_DEPS_FACTORY: unique symbol;
289
347
  /** App-wide, ordered `@SystemPromptContributor()` functions the loop appends after the agent base. */
290
348
  declare const AGENT_PROMPT_CONTRIBUTORS: unique symbol;
349
+ /**
350
+ * Resolves opaque store `actorRef`s to human display labels for governance/dashboard read surfaces.
351
+ * Optional — see {@link import('./spi/actor-directory.js').ActorDirectory}.
352
+ */
353
+ declare const AGENT_ACTOR_DIRECTORY: unique symbol;
354
+ /**
355
+ * Persists an uploaded attachment somewhere the model can fetch it from and returns a
356
+ * `MessageAttachment`. Optional — see
357
+ * {@link import('./spi/attachment-staging.js').AttachmentStagingStore}.
358
+ */
359
+ declare const AGENT_ATTACHMENT_STAGING: unique symbol;
291
360
 
292
361
  /**
293
362
  * Per-invocation context handed to a tool handler. Host-supplied bits are optional. Identity lives
@@ -406,20 +475,32 @@ interface ModelProvider {
406
475
  * Keeping this vocabulary neutral (not AI-SDK `UIMessageChunk`) means core never depends on `ai`:
407
476
  * the adapter owns model-parts → event, the transport owns event → UI-chunk.
408
477
  */
478
+
409
479
  type AgentStreamEvent = {
410
480
  kind: 'step-start';
411
- } | {
481
+ }
482
+ /**
483
+ * Closes the step opened by the matching `step-start`. Carries the model call's token usage and
484
+ * `costUsd` (an estimate from the bound pricing store, or `null` when unpriced/unbound — never a
485
+ * fabricated `0`) so a live client can render running cost without waiting for a thread re-fetch.
486
+ */
487
+ | {
412
488
  kind: 'step-finish';
489
+ usage?: MessageUsage;
490
+ costUsd?: number | null;
413
491
  } | {
414
492
  kind: 'text';
415
493
  text: string;
416
494
  } | {
417
495
  kind: 'reasoning';
418
496
  text: string;
419
- } | {
497
+ }
498
+ /** `toolKind` collapses `ToolKind`'s `'agent'` into `'read'` — delegation tools auto-execute like a read tool. */
499
+ | {
420
500
  kind: 'tool-input-start';
421
501
  id: string;
422
502
  name: string;
503
+ toolKind: 'read' | 'action';
423
504
  } | {
424
505
  kind: 'tool-input-delta';
425
506
  id: string;
@@ -429,6 +510,7 @@ type AgentStreamEvent = {
429
510
  id: string;
430
511
  name: string;
431
512
  input: unknown;
513
+ toolKind: 'read' | 'action';
432
514
  } | {
433
515
  kind: 'tool-output';
434
516
  id: string;
@@ -475,6 +557,12 @@ interface UpdateToolCallInput {
475
557
  executionMs?: number;
476
558
  executedByRef?: string;
477
559
  }
560
+ /** Patch applied by {@link AgentStore.updateThread}. An omitted key leaves that field untouched. */
561
+ interface UpdateThreadInput {
562
+ title?: string;
563
+ /** `null` clears the thread's default agent (falls back to the module default). */
564
+ defaultAgent?: string | null;
565
+ }
478
566
  interface RecordUsageInput {
479
567
  threadId: string;
480
568
  actorRef: string;
@@ -485,6 +573,19 @@ interface RecordUsageInput {
485
573
  /** Provider-reported actual USD cost for this turn, when known (gateways report it). */
486
574
  costUsd?: number;
487
575
  }
576
+ interface RecordRunStartInput {
577
+ runId: string;
578
+ threadId: string;
579
+ actorRef: string;
580
+ agentName?: string;
581
+ }
582
+ interface RecordRunEndInput {
583
+ runId: string;
584
+ status: 'completed' | 'failed';
585
+ durationMs?: number;
586
+ errorCode?: string;
587
+ errorMessage?: string;
588
+ }
488
589
  /** ORM-agnostic persistence. Refs are string ids; adapters may add real relations. */
489
590
  interface AgentStore {
490
591
  createThread(input: CreateThreadInput): Promise<ThreadSummary>;
@@ -500,6 +601,29 @@ interface AgentStore {
500
601
  */
501
602
  promoteThread(threadId: string): Promise<void>;
502
603
  setActiveStream(threadId: string, runId: string | null): Promise<void>;
604
+ /**
605
+ * OPTIONAL: rename a thread and/or set its default agent in one write. Absent on a store that
606
+ * predates this — `setTitle` still covers title-only edits, so nothing else in the lib requires
607
+ * this method; the REST `PATCH /threads/:id` endpoint responds 501 for a `defaultAgent` change
608
+ * against a store that lacks it.
609
+ */
610
+ updateThread?(threadId: string, patch: UpdateThreadInput): Promise<void>;
611
+ /**
612
+ * OPTIONAL: the runId of a currently-running turn on this thread, or `null` if none is running.
613
+ * Lets a client that reconnects (page refresh) discover a run to reattach to via the existing
614
+ * `GET /chat/:runId/stream`, instead of only being told about a run right after starting it.
615
+ * Absent on a store that predates this — thread read/list payloads report `activeRunId: null`.
616
+ */
617
+ activeRunForThread?(threadId: string): Promise<string | null>;
618
+ /**
619
+ * OPTIONAL: persist the start of a run (turn). Replay-safe: called under a durable localStep.
620
+ * Absent on a store that predates run recording — reliability metrics degrade to zeros/empty.
621
+ */
622
+ recordRunStart?(run: RecordRunStartInput): Promise<void>;
623
+ /** OPTIONAL: settle a run's outcome. `errorCode`/`errorMessage` only when status is 'failed'. */
624
+ recordRunEnd?(end: RecordRunEndInput): Promise<void>;
625
+ /** OPTIONAL: bump the run's llm-step retry counter (dispatched-step attempt > 1). */
626
+ bumpRunRetries?(runId: string): Promise<void>;
503
627
  /**
504
628
  * The `actorRef` that owns a thread, or `null` if no such thread exists. The authorization seam
505
629
  * for thread-scoped endpoints (detail / delete / fork): the service compares this against the
@@ -752,6 +876,52 @@ interface ThreadActivityRow {
752
876
  totalTokens: number;
753
877
  lastActivityAt: string;
754
878
  }
879
+ /** Aggregated run reliability over a range. */
880
+ interface RunMetrics {
881
+ runs: number;
882
+ completed: number;
883
+ failed: number;
884
+ /** completed / runs, 0 when runs = 0. */
885
+ successRate: number;
886
+ /** Total llm-step retries across the range's runs. */
887
+ retries: number;
888
+ durationP50Ms: number | null;
889
+ durationP95Ms: number | null;
890
+ }
891
+ /** Run/failure/retry rollup for one agent over a range. */
892
+ interface RunAgentBreakdownRow {
893
+ /** '(default)' when the run had none. */
894
+ agentName: string;
895
+ runs: number;
896
+ failed: number;
897
+ retries: number;
898
+ }
899
+ /** Failed-run count for one error code over a range. */
900
+ interface RunErrorBreakdownRow {
901
+ errorCode: string;
902
+ count: number;
903
+ }
904
+ /** One point on the daily run/failure trend. */
905
+ interface RunTrendPoint {
906
+ day: string;
907
+ runs: number;
908
+ failed: number;
909
+ }
910
+ /** A recent run for the reliability feed. */
911
+ interface RecentRunRow {
912
+ runId: string;
913
+ threadId: string;
914
+ actorRef: string;
915
+ agentName: string | null;
916
+ /** 'running' | 'completed' | 'failed'. */
917
+ status: string;
918
+ durationMs: number | null;
919
+ errorCode: string | null;
920
+ errorMessage: string | null;
921
+ retries: number;
922
+ /** ISO timestamp. */
923
+ startedAt: string;
924
+ }
755
925
  /**
756
926
  * The governance read-model. Cost is `inputTokens/1e6 * inputPricePer1m + outputTokens/1e6 *
757
927
  * outputPricePer1m` against the current pricing row per model; an unpriced model contributes 0 cost
@@ -765,6 +935,50 @@ interface AgentGovernanceQueries {
765
935
  usageTrend(range: GovernanceRange): Promise<UsageTrendPoint[]>;
766
936
  recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
767
937
  recentThreads(limit: number): Promise<ThreadActivityRow[]>;
938
+ runMetrics(range: GovernanceRange): Promise<RunMetrics>;
939
+ runsByAgent(range: GovernanceRange): Promise<RunAgentBreakdownRow[]>;
940
+ runErrors(range: GovernanceRange): Promise<RunErrorBreakdownRow[]>;
941
+ runTrend(range: GovernanceRange): Promise<RunTrendPoint[]>;
942
+ recentRuns(limit: number): Promise<RecentRunRow[]>;
943
+ }
944
+
945
+ /**
946
+ * Optional read-side lookup from opaque store `actorRef`s to human display labels.
947
+ *
948
+ * The store keeps `actorRef` opaque by design (no FK into the host's user table — hosts own their
949
+ * own identity schema). That's fine for enforcement and accounting, but a governance/dashboard read
950
+ * surface showing a raw ref ("u_8f21...") instead of a name is a bad experience. Binding an
951
+ * `ActorDirectory` lets those surfaces resolve refs to labels; leaving it unbound just means they
952
+ * render the raw ref. Consumers inject via `AGENT_ACTOR_DIRECTORY`.
953
+ */
954
+ interface ActorDirectory {
955
+ /** Resolve opaque actor refs to display labels. Missing refs may be omitted. */
956
+ resolveDisplay(refs: readonly string[]): Promise<Record<string, string>>;
957
+ }
958
+
959
+ /** Input to {@link AttachmentStagingStore.stage} — the raw bytes plus who uploaded them. */
960
+ interface StageAttachmentInput {
961
+ data: Buffer;
962
+ filename: string;
963
+ contentType: string;
964
+ sizeBytes: number;
965
+ actor: Actor;
966
+ }
967
+ /**
968
+ * Optional upload-side seam for message attachments (an image/PDF a user attaches to a chat
969
+ * message before the model ever sees it). The lib never fetches bytes itself — {@link MessageAttachment.url}
970
+ * must already be reachable by the model provider — so something has to turn an uploaded file into
971
+ * that URL first. A store adapter (or a thin wrapper over the host's own media pipeline) implements
972
+ * this; consumers inject via `AGENT_ATTACHMENT_STAGING`. Unbound, the optional `POST /agent/attachments`
973
+ * upload controller is never mounted.
974
+ */
975
+ interface AttachmentStagingStore {
976
+ /**
977
+ * Persist an uploaded file somewhere the model can later fetch (presigned URL etc.) and return the
978
+ * {@link MessageAttachment} to send with the next chat message. The lib never fetches bytes; the
979
+ * returned url must be reachable by the model provider.
980
+ */
981
+ stage(input: StageAttachmentInput): Promise<MessageAttachment>;
768
982
  }
769
983
 
770
984
  /**
@@ -936,6 +1150,12 @@ interface AgentLoopDeps {
936
1150
  retriever?: Retriever;
937
1151
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
938
1152
  retrievalTopK?: number;
1153
+ /**
1154
+ * Prices each step's token usage into `costUsd` (on the `step-finish` stream frame and the
1155
+ * persisted assistant message's `usage`). The current price list is fetched ONCE per run (not per
1156
+ * message/step) and reused for every step's estimate. Undefined → `costUsd` is always `null`.
1157
+ */
1158
+ pricingStore?: AgentPricingStore;
939
1159
  }
940
1160
  interface AgentLoopHooks {
941
1161
  runId: string;
@@ -951,15 +1171,36 @@ interface AgentLoopHooks {
951
1171
  text: string;
952
1172
  }>;
953
1173
  /**
954
- * Checkpoint wrapper. Inline = call fn directly; durable = ctx.step(name, fn).
1174
+ * Checkpoint wrapper. Inline = call fn directly; durable = ctx.localStep(name, fn) — the
1175
+ * in-process checkpointed primitive (`name` is a checkpoint identity, not a worker group).
955
1176
  * EVERY side-effect and control-flow read goes through this so durable replay returns
956
1177
  * cached results (stable ids, no double-write, no re-streaming).
957
1178
  */
958
1179
  step<T>(name: string, fn: () => Promise<T>): Promise<T>;
1180
+ /**
1181
+ * Dispatch the model turn as a routed remote step. When present the loop uses it INSTEAD of
1182
+ * running the model inline under hooks.step. Must resolve to exactly what deps.model.runTurn
1183
+ * resolves. The durable runner enriches the envelope with sink routing (sinkRunId/childSink)
1184
+ * on its side — core stays sink-topology-agnostic.
1185
+ */
1186
+ dispatchLlm?(index: number, envelope: LlmStepEnvelope): Promise<ModelTurnResult>;
1187
+ /**
1188
+ * Dispatch one tool execution as a routed remote step. Replaces ONLY the registry.invoke call
1189
+ * (+ its timeout, applied handler-side); all persist steps around it stay local.
1190
+ */
1191
+ dispatchTool?(call: ToolCallRequest, envelope: ToolStepEnvelope): Promise<unknown>;
1192
+ /**
1193
+ * Recognizes the runner's control-flow signals (durable suspend / continue-as-new) so the loop
1194
+ * rethrows them untouched instead of recording a failure. Inline runners omit it — they have no
1195
+ * control-flow exceptions.
1196
+ */
1197
+ isControlFlowError?(error: unknown): boolean;
959
1198
  }
960
1199
  declare class QuotaExceededError extends Error {
961
1200
  constructor();
962
1201
  }
1202
+ /** Reject if `work` doesn't settle within `ms`. The underlying work is left to finish on its own. */
1203
+ declare function withToolTimeout<T>(work: Promise<T>, ms: number, toolName: string): Promise<T>;
963
1204
  /**
964
1205
  * The provider-agnostic agent turn, reused by both the inline and durable runners.
965
1206
  * It drives the model→tools→model iteration; the runner supplies the `step`/`awaitApproval`
@@ -1043,4 +1284,4 @@ declare function publishAgentRunFailed(payload: AgentRunFailed): void;
1043
1284
  declare function publishAgentDelegated(payload: AgentDelegated): void;
1044
1285
  declare function publishAgentRetrieved(payload: AgentRetrieved): void;
1045
1286
 
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 };
1287
+ 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 LlmStepEnvelope, 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 RecentRunRow, type RecordRunEndInput, type RecordRunStartInput, type RecordToolCallInput, type RecordUsageInput, type RerankOptions, type Reranker, type RetrieveOptions, type Retriever, type RolesPolicy, type RunAgentBreakdownRow, type RunErrorBreakdownRow, type RunMetrics, type RunTrendPoint, 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 ToolStepCtx, type ToolStepEnvelope, 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, withToolTimeout };
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})` : "";
@@ -427,7 +436,7 @@ var ToolTimeoutError = class ToolTimeoutError2 extends Error {
427
436
  this.name = "ToolTimeoutError";
428
437
  }
429
438
  };
430
- function withTimeout(work, ms, toolName) {
439
+ function withToolTimeout(work, ms, toolName) {
431
440
  return new Promise((resolve, reject) => {
432
441
  const timer = setTimeout(() => reject(new ToolTimeoutError(toolName, ms)), ms);
433
442
  work.then((value) => {
@@ -439,7 +448,7 @@ function withTimeout(work, ms, toolName) {
439
448
  });
440
449
  });
441
450
  }
442
- __name(withTimeout, "withTimeout");
451
+ __name(withToolTimeout, "withToolTimeout");
443
452
  function parseFollowUps(text, count) {
444
453
  const source = text.match(/\[[\s\S]*\]/)?.[0] ?? text;
445
454
  try {
@@ -544,6 +553,17 @@ async function runAgentLoop(deps, input, hooks) {
544
553
  agentName: input.agentName
545
554
  } : {}
546
555
  });
556
+ const startedAt = await hooks.step("run:started-at", () => Promise.resolve(Date.now()));
557
+ await hooks.step("persist:run:start", async () => {
558
+ await deps.store.recordRunStart?.({
559
+ runId: hooks.runId,
560
+ threadId: input.threadId,
561
+ actorRef: input.actor.id,
562
+ ...input.agentName !== void 0 ? {
563
+ agentName: input.agentName
564
+ } : {}
565
+ });
566
+ });
547
567
  let injectedPassages;
548
568
  if (deps.retriever !== void 0) {
549
569
  const retriever = deps.retriever;
@@ -562,24 +582,50 @@ ${buildContextBlock(passages)}`;
562
582
  count: passages.length
563
583
  });
564
584
  }
585
+ let prices = [];
586
+ if (deps.pricingStore !== void 0) {
587
+ const pricingStore = deps.pricingStore;
588
+ prices = await hooks.step("pricing:list", () => pricingStore.listCurrentPrices());
589
+ }
590
+ const priceByModel = new Map(prices.map((price) => [
591
+ price.modelId,
592
+ price
593
+ ]));
565
594
  for (let i = 0; i < maxSteps; i += 1) {
566
595
  await hooks.step(`stream:step-start:${i}`, async () => {
567
596
  await writer.write(encodeStreamEvent({
568
597
  kind: "step-start"
569
598
  }));
570
599
  });
571
- const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList);
572
- const turn = await hooks.step(`llm:${i}`, () => deps.model.runTurn({
573
- system,
574
- messages: modelMessages,
575
- tools,
576
- sink: writer
600
+ let turn;
601
+ if (hooks.dispatchLlm) {
602
+ turn = await hooks.dispatchLlm(i, {
603
+ ...input.agentName !== void 0 ? {
604
+ agentName: input.agentName
605
+ } : {},
606
+ system,
607
+ messages: modelMessages,
608
+ actor: input.actor
609
+ });
610
+ } else {
611
+ const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList);
612
+ turn = await hooks.step(`llm:${i}`, () => deps.model.runTurn({
613
+ system,
614
+ messages: modelMessages,
615
+ tools,
616
+ sink: writer
617
+ }));
618
+ }
619
+ const resolvedModelId = turn.modelId ?? deps.modelId ?? "unknown";
620
+ const costUsd = resolveCostUsd(turn.usage, turn.costUsd, priceByModel.get(resolvedModelId));
621
+ const toolCallsWithKind = turn.toolCalls.map((call) => ({
622
+ ...call,
623
+ kind: deps.registry.spec(call.name)?.kind ?? "read"
577
624
  }));
578
625
  await hooks.step(`persist:usage:${i}`, () => deps.store.recordUsage({
579
626
  threadId: input.threadId,
580
627
  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",
628
+ modelId: resolvedModelId,
583
629
  purpose: "chat",
584
630
  usage: turn.usage,
585
631
  // persist the provider's actual cost when reported; the read-model prefers it over pricing
@@ -627,12 +673,15 @@ ${buildContextBlock(passages)}`;
627
673
  threadId: input.threadId,
628
674
  role: "assistant",
629
675
  content: turn.text,
630
- usage: turn.usage,
676
+ usage: {
677
+ ...turn.usage,
678
+ costUsd
679
+ },
631
680
  ...input.agentName !== void 0 ? {
632
681
  agentName: input.agentName
633
682
  } : {},
634
- ...turn.toolCalls.length > 0 ? {
635
- toolCalls: turn.toolCalls
683
+ ...toolCallsWithKind.length > 0 ? {
684
+ toolCalls: toolCallsWithKind
636
685
  } : {},
637
686
  ...followUps !== void 0 ? {
638
687
  followUps
@@ -641,8 +690,8 @@ ${buildContextBlock(passages)}`;
641
690
  const assistantMessage = {
642
691
  role: "assistant",
643
692
  content: turn.text,
644
- ...turn.toolCalls.length > 0 ? {
645
- toolCalls: turn.toolCalls
693
+ ...toolCallsWithKind.length > 0 ? {
694
+ toolCalls: toolCallsWithKind
646
695
  } : {}
647
696
  };
648
697
  modelMessages.push(assistantMessage);
@@ -672,15 +721,17 @@ ${buildContextBlock(passages)}`;
672
721
  if (isFinalTurn) {
673
722
  await hooks.step(`stream:step-finish:${i}`, async () => {
674
723
  await writer.write(encodeStreamEvent({
675
- kind: "step-finish"
724
+ kind: "step-finish",
725
+ usage: turn.usage,
726
+ costUsd
676
727
  }));
677
728
  });
678
729
  break;
679
730
  }
680
731
  const results = [];
681
- for (const call of turn.toolCalls) {
732
+ for (const call of toolCallsWithKind) {
682
733
  const spec = deps.registry.spec(call.name);
683
- const toolType = spec?.kind ?? "read";
734
+ const toolType = call.kind ?? "read";
684
735
  const ctx = {
685
736
  actor: input.actor,
686
737
  threadId: input.threadId,
@@ -784,11 +835,36 @@ ${buildContextBlock(passages)}`;
784
835
  status: "auto_executed"
785
836
  }));
786
837
  }
787
- const startedAt = Date.now();
838
+ const startedAt2 = Date.now();
788
839
  try {
789
- const invocation = hooks.step(`tool:${call.id}`, () => deps.registry.invoke(call.name, call.input, ctx, deps.rolesPolicy));
790
- const output = deps.toolTimeoutMs !== void 0 ? await withTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
791
- const executionMs = Date.now() - startedAt;
840
+ let output;
841
+ if (hooks.dispatchTool) {
842
+ const stepCtx = {
843
+ actor: input.actor,
844
+ threadId: input.threadId,
845
+ runId: hooks.runId,
846
+ requestId: hooks.runId,
847
+ ...input.agentName !== void 0 ? {
848
+ agentName: input.agentName
849
+ } : {},
850
+ ...input.pageContext !== void 0 ? {
851
+ pageContext: input.pageContext
852
+ } : {}
853
+ };
854
+ const envelope = {
855
+ toolName: call.name,
856
+ input: call.input,
857
+ ctx: stepCtx,
858
+ ...deps.toolTimeoutMs !== void 0 ? {
859
+ timeoutMs: deps.toolTimeoutMs
860
+ } : {}
861
+ };
862
+ output = await hooks.dispatchTool(call, envelope);
863
+ } else {
864
+ const invocation = hooks.step(`tool:${call.id}`, () => deps.registry.invoke(call.name, call.input, ctx, deps.rolesPolicy));
865
+ output = deps.toolTimeoutMs !== void 0 ? await withToolTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
866
+ }
867
+ const executionMs = Date.now() - startedAt2;
792
868
  await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
793
869
  toolCallId: call.id,
794
870
  status: "executed",
@@ -811,7 +887,10 @@ ${buildContextBlock(passages)}`;
811
887
  durationMs: executionMs
812
888
  });
813
889
  } catch (error) {
814
- const executionMs = Date.now() - startedAt;
890
+ if (hooks.isControlFlowError?.(error) === true) {
891
+ throw error;
892
+ }
893
+ const executionMs = Date.now() - startedAt2;
815
894
  const message = error instanceof Error ? error.message : String(error);
816
895
  await hooks.step(`persist:toolfail:${call.id}`, () => deps.store.updateToolCall({
817
896
  toolCallId: call.id,
@@ -850,13 +929,22 @@ ${buildContextBlock(passages)}`;
850
929
  });
851
930
  await hooks.step(`stream:step-finish:${i}`, async () => {
852
931
  await writer.write(encodeStreamEvent({
853
- kind: "step-finish"
932
+ kind: "step-finish",
933
+ usage: turn.usage,
934
+ costUsd
854
935
  }));
855
936
  });
856
937
  }
857
938
  if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
858
939
  await hooks.step("persist:title", () => deps.store.setTitle(input.threadId, deriveTitle(input.userText)));
859
940
  }
941
+ await hooks.step("persist:run:end", async () => {
942
+ await deps.store.recordRunEnd?.({
943
+ runId: hooks.runId,
944
+ status: "completed",
945
+ durationMs: Date.now() - startedAt
946
+ });
947
+ });
860
948
  await writer.end();
861
949
  publishAgentRunFinished({
862
950
  runId: hooks.runId,
@@ -871,7 +959,9 @@ ${buildContextBlock(passages)}`;
871
959
  }
872
960
  __name(runAgentLoop, "runAgentLoop");
873
961
  export {
962
+ AGENT_ACTOR_DIRECTORY,
874
963
  AGENT_ACTOR_RESOLVER,
964
+ AGENT_ATTACHMENT_STAGING,
875
965
  AGENT_DEPS_FACTORY,
876
966
  AGENT_DURABLE_RUNNER,
877
967
  AGENT_EMBEDDING_PROVIDER,
@@ -914,6 +1004,7 @@ export {
914
1004
  publishAgentRunStarted,
915
1005
  publishAgentToolCall,
916
1006
  runAgentLoop,
917
- seedModelPrices
1007
+ seedModelPrices,
1008
+ withToolTimeout
918
1009
  };
919
1010
  //# sourceMappingURL=index.js.map