@dudousxd/nestjs-agent 1.37.0 → 1.39.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.
@@ -1,5 +1,5 @@
1
1
  import { A2uiStreamOptions, a2uiThreadReplay } from '@dudousxd/nestjs-agent-core/a2ui';
2
- import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, SubscribeOptions, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
2
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, SubscribeOptions, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, AiUsageInput, AiUsageOptions, AiUsageResult, ActorResolver } from '@dudousxd/nestjs-agent-core';
3
3
  import { Request, Response } from 'express';
4
4
  import { UiCapabilities } from '@dudousxd/nestjs-agent-core/genui';
5
5
  import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-fopUN313.cjs';
@@ -588,6 +588,22 @@ declare class AgentService {
588
588
  private resolveRunForToolCall;
589
589
  /** Authorization seam for `cancel`: the caller must own the thread currently streaming this run. */
590
590
  private assertOwnsActiveStream;
591
+ /**
592
+ * Record AI spend that did not come from an agent turn — a transcription, a TTS render, a batch of
593
+ * embeddings, a direct OpenAI/OpenRouter SDK call — on the same ledger the agent's turns write, so
594
+ * cost, quota and the dashboard see one figure.
595
+ *
596
+ * Never throws into the caller's flow by default: a failed write or an invalid input is logged and
597
+ * answered with `{ recorded: false, error }`. Pass `{ throwOnError: true }` where losing the row
598
+ * must fail the caller.
599
+ *
600
+ * Counted against `actorRef` like an agent turn: the ledger is the budget (`LedgerQuotaProvider`),
601
+ * so its tokens count in the token windows (a row with no tokens adds 0 there) and its `costUsd` in
602
+ * the USD windows. A `QuotaStore` counter is bumped only where loop deps carry one (`AgentModule`
603
+ * wires none). `actorRef: null` is platform cost, recorded under `PLATFORM_ACTOR_REF` and counted
604
+ * against nobody.
605
+ */
606
+ recordUsage(input: AiUsageInput, options?: AiUsageOptions): Promise<AiUsageResult>;
591
607
  }
592
608
 
593
609
  interface A2uiAdapterOptions extends A2uiStreamOptions {
@@ -1,5 +1,5 @@
1
1
  import { A2uiStreamOptions, a2uiThreadReplay } from '@dudousxd/nestjs-agent-core/a2ui';
2
- import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, SubscribeOptions, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver } from '@dudousxd/nestjs-agent-core';
2
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalView, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, SubscribeOptions, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, AiUsageInput, AiUsageOptions, AiUsageResult, ActorResolver } from '@dudousxd/nestjs-agent-core';
3
3
  import { Request, Response } from 'express';
4
4
  import { UiCapabilities } from '@dudousxd/nestjs-agent-core/genui';
5
5
  import { b as AgentModuleOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter } from './chat-queue.service-YjiKVTKE.js';
@@ -588,6 +588,22 @@ declare class AgentService {
588
588
  private resolveRunForToolCall;
589
589
  /** Authorization seam for `cancel`: the caller must own the thread currently streaming this run. */
590
590
  private assertOwnsActiveStream;
591
+ /**
592
+ * Record AI spend that did not come from an agent turn — a transcription, a TTS render, a batch of
593
+ * embeddings, a direct OpenAI/OpenRouter SDK call — on the same ledger the agent's turns write, so
594
+ * cost, quota and the dashboard see one figure.
595
+ *
596
+ * Never throws into the caller's flow by default: a failed write or an invalid input is logged and
597
+ * answered with `{ recorded: false, error }`. Pass `{ throwOnError: true }` where losing the row
598
+ * must fail the caller.
599
+ *
600
+ * Counted against `actorRef` like an agent turn: the ledger is the budget (`LedgerQuotaProvider`),
601
+ * so its tokens count in the token windows (a row with no tokens adds 0 there) and its `costUsd` in
602
+ * the USD windows. A `QuotaStore` counter is bumped only where loop deps carry one (`AgentModule`
603
+ * wires none). `actorRef: null` is platform cost, recorded under `PLATFORM_ACTOR_REF` and counted
604
+ * against nobody.
605
+ */
606
+ recordUsage(input: AiUsageInput, options?: AiUsageOptions): Promise<AiUsageResult>;
591
607
  }
592
608
 
593
609
  interface A2uiAdapterOptions extends A2uiStreamOptions {
package/dist/index.cjs CHANGED
@@ -1723,6 +1723,7 @@ function _ts_param4(paramIndex, decorator) {
1723
1723
  };
1724
1724
  }
1725
1725
  __name(_ts_param4, "_ts_param");
1726
+ var usageLogger = new import_common6.Logger("AgentService");
1726
1727
  var NO_USER_MESSAGE_CODE = "no_user_message";
1727
1728
  function sendableParams(params) {
1728
1729
  const message = params.message;
@@ -2966,6 +2967,31 @@ var AgentService2 = class {
2966
2967
  throw new import_common6.ForbiddenException("run belongs to another actor");
2967
2968
  }
2968
2969
  }
2970
+ /**
2971
+ * Record AI spend that did not come from an agent turn — a transcription, a TTS render, a batch of
2972
+ * embeddings, a direct OpenAI/OpenRouter SDK call — on the same ledger the agent's turns write, so
2973
+ * cost, quota and the dashboard see one figure.
2974
+ *
2975
+ * Never throws into the caller's flow by default: a failed write or an invalid input is logged and
2976
+ * answered with `{ recorded: false, error }`. Pass `{ throwOnError: true }` where losing the row
2977
+ * must fail the caller.
2978
+ *
2979
+ * Counted against `actorRef` like an agent turn: the ledger is the budget (`LedgerQuotaProvider`),
2980
+ * so its tokens count in the token windows (a row with no tokens adds 0 there) and its `costUsd` in
2981
+ * the USD windows. A `QuotaStore` counter is bumped only where loop deps carry one (`AgentModule`
2982
+ * wires none). `actorRef: null` is platform cost, recorded under `PLATFORM_ACTOR_REF` and counted
2983
+ * against nobody.
2984
+ */
2985
+ recordUsage(input, options = {}) {
2986
+ return (0, import_nestjs_agent_core9.recordAiUsage)({
2987
+ store: this.store,
2988
+ quota: this.deps.forAgent().quota,
2989
+ day: utcDay(),
2990
+ input,
2991
+ options,
2992
+ warn: /* @__PURE__ */ __name((message) => usageLogger.warn(message), "warn")
2993
+ });
2994
+ }
2969
2995
  };
2970
2996
  AgentService2 = _ts_decorate5([
2971
2997
  (0, import_common6.Injectable)(),
@@ -6176,10 +6202,12 @@ var InlineAgentRunner = class _InlineAgentRunner {
6176
6202
  }
6177
6203
  async start(input, options = {}) {
6178
6204
  const runId = options.runId ?? crypto.randomUUID();
6205
+ const acceptedAt = Date.now();
6179
6206
  const day = input.day ?? utcDay();
6180
6207
  const deps = this.factory.forAgent(input.agentName);
6181
6208
  this.live.add(runId);
6182
6209
  const hooks = this.topLevelHooks({
6210
+ acceptedAt,
6183
6211
  runId,
6184
6212
  deps,
6185
6213
  actor: input.actor,
@@ -6201,8 +6229,9 @@ var InlineAgentRunner = class _InlineAgentRunner {
6201
6229
  const timed = durationMs !== void 0 ? {
6202
6230
  durationMs
6203
6231
  } : {};
6232
+ const measured = (0, import_nestjs_agent_core32.runEndTimings)(error);
6204
6233
  if (error instanceof import_nestjs_agent_core32.RunCancelledError) {
6205
- await this.settleCancelled(runId, input, deps, durationMs);
6234
+ await this.settleCancelled(runId, input, deps, durationMs, measured);
6206
6235
  return;
6207
6236
  }
6208
6237
  const message = error instanceof Error ? error.message : String(error);
@@ -6214,6 +6243,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6214
6243
  code,
6215
6244
  message,
6216
6245
  ...timed,
6246
+ ...measured,
6217
6247
  context: (0, import_nestjs_agent_core32.agentRunContext)(input)
6218
6248
  });
6219
6249
  await this.store.recordRunEnd?.({
@@ -6291,7 +6321,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6291
6321
  * the stream ENDED after a `cancelled` frame rather than failed, so a client that retries failed
6292
6322
  * streams does not retry this one.
6293
6323
  */
6294
- async settleCancelled(runId, input, deps, durationMs) {
6324
+ async settleCancelled(runId, input, deps, durationMs, measured = {}) {
6295
6325
  this.logger.log(`agent run ${runId} cancelled`);
6296
6326
  const timed = durationMs !== void 0 ? {
6297
6327
  durationMs
@@ -6300,6 +6330,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6300
6330
  runId,
6301
6331
  threadId: input.threadId,
6302
6332
  ...timed,
6333
+ ...measured,
6303
6334
  context: (0, import_nestjs_agent_core32.agentRunContext)(input)
6304
6335
  });
6305
6336
  const writer = await deps.sink.open(runId);
@@ -6392,9 +6423,10 @@ var InlineAgentRunner = class _InlineAgentRunner {
6392
6423
  });
6393
6424
  }
6394
6425
  topLevelHooks(args) {
6395
- const { runId, deps, actor, day, threadId, chainBelow, input } = args;
6426
+ const { runId, deps, actor, day, threadId, chainBelow, input, acceptedAt } = args;
6396
6427
  return {
6397
6428
  runId,
6429
+ queueMs: /* @__PURE__ */ __name(() => Date.now() - acceptedAt, "queueMs"),
6398
6430
  // The loop ends the stream when the turn completes. Just before, the thread passes to the next
6399
6431
  // queued message (or is released), and the reader is told which — so by the time a client
6400
6432
  // sees the run end, the thread is already free or already running what it queued.
@@ -6519,10 +6551,12 @@ var InlineAgentRunner = class _InlineAgentRunner {
6519
6551
  const timed = durationMs !== void 0 ? {
6520
6552
  durationMs
6521
6553
  } : {};
6554
+ const measured = (0, import_nestjs_agent_core32.runEndTimings)(error);
6522
6555
  const settled = {
6523
6556
  runId,
6524
6557
  threadId: subThread.id,
6525
6558
  ...timed,
6559
+ ...measured,
6526
6560
  context: (0, import_nestjs_agent_core32.agentRunContext)(childInput)
6527
6561
  };
6528
6562
  if (cancelled) {