@memberjunction/ai-agents 5.38.0 → 5.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.
Files changed (87) hide show
  1. package/dist/ArtifactToolManager.d.ts +13 -0
  2. package/dist/ArtifactToolManager.d.ts.map +1 -1
  3. package/dist/ArtifactToolManager.js +60 -1
  4. package/dist/ArtifactToolManager.js.map +1 -1
  5. package/dist/agent-run-watchdog.d.ts +109 -0
  6. package/dist/agent-run-watchdog.d.ts.map +1 -0
  7. package/dist/agent-run-watchdog.js +278 -0
  8. package/dist/agent-run-watchdog.js.map +1 -0
  9. package/dist/agent-types/loop-agent-prompt-params.d.ts +13 -0
  10. package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
  11. package/dist/agent-types/loop-agent-prompt-params.js +3 -1
  12. package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
  13. package/dist/agent-types/loop-agent-response-type.d.ts +12 -3
  14. package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
  15. package/dist/agent-types/loop-agent-response-type.js.map +1 -1
  16. package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
  17. package/dist/agent-types/loop-agent-type.js +29 -1
  18. package/dist/agent-types/loop-agent-type.js.map +1 -1
  19. package/dist/artifact-tools/DataSnapshotToolLibrary.js +2 -2
  20. package/dist/artifact-tools/JSONToolLibrary.d.ts.map +1 -1
  21. package/dist/artifact-tools/JSONToolLibrary.js +7 -1
  22. package/dist/artifact-tools/JSONToolLibrary.js.map +1 -1
  23. package/dist/base-agent.d.ts +53 -1
  24. package/dist/base-agent.d.ts.map +1 -1
  25. package/dist/base-agent.js +209 -9
  26. package/dist/base-agent.js.map +1 -1
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/pipeline/coerce.d.ts +43 -0
  32. package/dist/pipeline/coerce.d.ts.map +1 -0
  33. package/dist/pipeline/coerce.js +92 -0
  34. package/dist/pipeline/coerce.js.map +1 -0
  35. package/dist/pipeline/index.d.ts +20 -0
  36. package/dist/pipeline/index.d.ts.map +1 -0
  37. package/dist/pipeline/index.js +20 -0
  38. package/dist/pipeline/index.js.map +1 -0
  39. package/dist/pipeline/jsonpath-eval.d.ts +29 -0
  40. package/dist/pipeline/jsonpath-eval.d.ts.map +1 -0
  41. package/dist/pipeline/jsonpath-eval.js +144 -0
  42. package/dist/pipeline/jsonpath-eval.js.map +1 -0
  43. package/dist/pipeline/operators.d.ts +15 -0
  44. package/dist/pipeline/operators.d.ts.map +1 -0
  45. package/dist/pipeline/operators.js +332 -0
  46. package/dist/pipeline/operators.js.map +1 -0
  47. package/dist/pipeline/path.d.ts +16 -0
  48. package/dist/pipeline/path.d.ts.map +1 -0
  49. package/dist/pipeline/path.js +34 -0
  50. package/dist/pipeline/path.js.map +1 -0
  51. package/dist/pipeline/pipeline-docs.d.ts +6 -0
  52. package/dist/pipeline/pipeline-docs.d.ts.map +1 -0
  53. package/dist/pipeline/pipeline-docs.js +137 -0
  54. package/dist/pipeline/pipeline-docs.js.map +1 -0
  55. package/dist/pipeline/pipeline-executor.d.ts +72 -0
  56. package/dist/pipeline/pipeline-executor.d.ts.map +1 -0
  57. package/dist/pipeline/pipeline-executor.js +326 -0
  58. package/dist/pipeline/pipeline-executor.js.map +1 -0
  59. package/dist/pipeline/pipeline-registry.d.ts +24 -0
  60. package/dist/pipeline/pipeline-registry.d.ts.map +1 -0
  61. package/dist/pipeline/pipeline-registry.js +28 -0
  62. package/dist/pipeline/pipeline-registry.js.map +1 -0
  63. package/dist/pipeline/pipeline.types.d.ts +89 -0
  64. package/dist/pipeline/pipeline.types.d.ts.map +1 -0
  65. package/dist/pipeline/pipeline.types.js +13 -0
  66. package/dist/pipeline/pipeline.types.js.map +1 -0
  67. package/dist/pipeline/predicate.d.ts +23 -0
  68. package/dist/pipeline/predicate.d.ts.map +1 -0
  69. package/dist/pipeline/predicate.js +306 -0
  70. package/dist/pipeline/predicate.js.map +1 -0
  71. package/dist/pipeline/providers/action-provider.d.ts +21 -0
  72. package/dist/pipeline/providers/action-provider.d.ts.map +1 -0
  73. package/dist/pipeline/providers/action-provider.js +24 -0
  74. package/dist/pipeline/providers/action-provider.js.map +1 -0
  75. package/dist/pipeline/providers/artifact-tool-provider.d.ts +20 -0
  76. package/dist/pipeline/providers/artifact-tool-provider.d.ts.map +1 -0
  77. package/dist/pipeline/providers/artifact-tool-provider.js +27 -0
  78. package/dist/pipeline/providers/artifact-tool-provider.js.map +1 -0
  79. package/dist/pipeline/providers/serialize.d.ts +23 -0
  80. package/dist/pipeline/providers/serialize.d.ts.map +1 -0
  81. package/dist/pipeline/providers/serialize.js +99 -0
  82. package/dist/pipeline/providers/serialize.js.map +1 -0
  83. package/dist/pipeline/template.d.ts +18 -0
  84. package/dist/pipeline/template.d.ts.map +1 -0
  85. package/dist/pipeline/template.js +47 -0
  86. package/dist/pipeline/template.js.map +1 -0
  87. package/package.json +19 -17
@@ -11,7 +11,8 @@
11
11
  * @since 2.49.0
12
12
  */
13
13
  import { FileStorageEngineBase } from '@memberjunction/core-entities';
14
- import { Metadata, RunView, LogStatus, LogStatusEx, LogError, LogErrorEx, IsVerboseLoggingEnabled } from '@memberjunction/core';
14
+ import { Metadata, RunView, LogStatus, LogStatusEx, LogError, LogErrorEx, IsVerboseLoggingEnabled, DatabaseProviderBase } from '@memberjunction/core';
15
+ import { AgentRunWatchdog } from './agent-run-watchdog.js';
15
16
  import { AIPromptRunner } from '@memberjunction/ai-prompts';
16
17
  import { BaseAgentType } from './agent-types/base-agent-type.js';
17
18
  import { CopyScalarsAndArrays, JSONValidator, SafeExpressionEvaluator, UUIDsEqual } from '@memberjunction/global';
@@ -26,6 +27,7 @@ import { AgentRunner } from './AgentRunner.js';
26
27
  import { PayloadManager } from './PayloadManager.js';
27
28
  import { ScratchpadManager } from './ScratchpadManager.js';
28
29
  import { ArtifactToolManager } from './ArtifactToolManager.js';
30
+ import { PipelineExecutor, PipelineToolRegistry, ActionInvocable, ArtifactToolInvocable, BuildPipelineToolDocs, formatFinalOutput, summarizePipelineStages, } from './pipeline/index.js';
29
31
  import { AgentDataPreloader } from './AgentDataPreloader.js';
30
32
  import { ClientToolRequestManager } from './ClientToolRequestManager.js';
31
33
  import { ConversationMessageResolver } from './utils/ConversationMessageResolver.js';
@@ -94,6 +96,11 @@ export class BaseAgent {
94
96
  /**
95
97
  * Queue map to chain database saves sequentially per step entity.
96
98
  * Prevents UPDATE queries running before INSERT queries on quick steps.
99
+ *
100
+ * Keyed by the step ENTITY INSTANCE, not its `ID`: a new step's `ID` is empty at create time and
101
+ * only gets populated during its INSERT `Save()`, so keying by `ID` would file the create and the
102
+ * finalize under different buckets and defeat the chain — letting the UPDATE race ahead of the
103
+ * INSERT on millisecond-fast steps (e.g. pipelines), which left them stuck at `Running`.
97
104
  */
98
105
  this._stepSavePromises = new Map();
99
106
  /**
@@ -1813,6 +1820,21 @@ export class BaseAgent {
1813
1820
  else if (this._artifactToolManager.HasArtifacts()) {
1814
1821
  this.logStatus(`[ArtifactTools] Artifacts present but tools disabled by agent config (includeArtifactToolsDocs=false)`, true, params);
1815
1822
  }
1823
+ // Inject pipeline tool docs when pipelines are enabled and at least one source exists.
1824
+ // A pipeline's first step must be a source (Action or artifact tool); with none
1825
+ // available pipelines are impossible, so BuildPipelineToolDocs returns '' and the
1826
+ // template's `{{ _PIPELINE_TOOLS }}` block stays empty.
1827
+ const pipelineDocsEnabled = agentTypePromptParams?.includePipelineDocs !== false;
1828
+ if (pipelineDocsEnabled) {
1829
+ const sourceNames = [
1830
+ ...this.getEffectiveActionsForValidation(params.agent.ID).map((a) => a.Name),
1831
+ ...this._artifactToolManager.GetAvailableToolNames(),
1832
+ ];
1833
+ const pipelineDocs = BuildPipelineToolDocs(sourceNames);
1834
+ if (pipelineDocs) {
1835
+ promptParams.data['_PIPELINE_TOOLS'] = pipelineDocs;
1836
+ }
1837
+ }
1816
1838
  // Pass file artifacts as candidate native file inputs.
1817
1839
  // The AIPromptRunner will check these against the resolved driver's
1818
1840
  // FileCapabilities and attach qualifying files as native content blocks.
@@ -3305,9 +3327,10 @@ The context is now within limits. Please retry your request with the recovered c
3305
3327
  const body = toolResults.map((r, i) => {
3306
3328
  const heading = `### ${i + 1}. ${r.artifactId}.${r.tool}(${JSON.stringify(r.input)})`;
3307
3329
  if (r.result.success) {
3308
- const data = typeof r.result.data === 'string'
3330
+ const raw = typeof r.result.data === 'string'
3309
3331
  ? r.result.data
3310
3332
  : JSON.stringify(r.result.data, null, 2);
3333
+ const data = this.capStandaloneToolResultText(raw);
3311
3334
  return `${heading}\n\`\`\`json\n${data}\n\`\`\``;
3312
3335
  }
3313
3336
  return `${heading}\n**Error:** ${r.result.errorMessage}`;
@@ -3331,6 +3354,154 @@ The context is now within limits. Please retry your request with the recovered c
3331
3354
  };
3332
3355
  params.conversationMessages.push(message);
3333
3356
  }
3357
+ /**
3358
+ * Character budget (~4 chars/token) for a SINGLE standalone artifact-tool result injected into
3359
+ * the conversation. A `get_full` on a large artifact can otherwise dump the whole thing into
3360
+ * context and overflow the model's window — the exact failure pipelines exist to avoid. Override
3361
+ * in a subclass to tune. Pipelines are unaffected: their intermediate results never flow through
3362
+ * here, and the executor already caps a pipeline's final output.
3363
+ *
3364
+ * @protected
3365
+ */
3366
+ get maxStandaloneToolResultChars() {
3367
+ return 100_000; // ~25k tokens
3368
+ }
3369
+ /**
3370
+ * Bound a standalone tool result to {@link maxStandaloneToolResultChars}: return a head slice
3371
+ * plus a redirect that teaches the agent to page (`get_rows`) or reduce (`pipeline`) instead of
3372
+ * reading a whole large artifact. Mirrors how read/search tools cap output at the tool boundary.
3373
+ *
3374
+ * @protected
3375
+ */
3376
+ capStandaloneToolResultText(text) {
3377
+ const budget = this.maxStandaloneToolResultChars;
3378
+ if (text.length <= budget) {
3379
+ return text;
3380
+ }
3381
+ const omitted = text.length - budget;
3382
+ return (text.slice(0, budget) +
3383
+ `\n\n…[truncated ${omitted.toLocaleString()} chars. This artifact is too large to read whole ` +
3384
+ `(~${Math.round(text.length / 4000)}k tokens) — reading it in full overflows the context window. ` +
3385
+ `Instead: page it with get_rows(start, count), or run a pipeline that filters/aggregates it ` +
3386
+ `server-side (where / select / groupBy → only the small final result returns to you).]`);
3387
+ }
3388
+ /**
3389
+ * Builds a per-run {@link PipelineToolRegistry} that unifies the three pipeline-able
3390
+ * substrates behind one namespace: built-in transforms, the agent's effective Actions, and
3391
+ * the run's artifact tools. Transforms register first so their reserved names win; a source
3392
+ * whose name collides with a transform is skipped for pipeline use (still callable normally)
3393
+ * and logged, rather than aborting the whole pipeline.
3394
+ *
3395
+ * @protected
3396
+ */
3397
+ buildPipelineRegistry(params) {
3398
+ const registry = new PipelineToolRegistry();
3399
+ const register = (invocable) => {
3400
+ try {
3401
+ registry.Register(invocable);
3402
+ }
3403
+ catch (e) {
3404
+ this.logStatus(`[Pipeline] Skipped tool "${invocable.toolName}": ${e.message}`, true, params);
3405
+ }
3406
+ };
3407
+ // Operators (where/select/map/…) are pure code-defined verbs, not registry tools — only
3408
+ // capabilities (Actions + artifact tools) live here as pipeline sources/stages.
3409
+ // Actions — each wrapped to run via the existing single-action execution path.
3410
+ this.getEffectiveActionsForValidation(params.agent.ID).forEach((actionEntity) => register(new ActionInvocable(actionEntity.Name, (p) => this.ExecuteSingleAction(params, { name: actionEntity.Name, params: p }, actionEntity, params.contextUser))));
3411
+ // Artifact tools — one invocable per distinct tool name; `artifactId` is supplied as a
3412
+ // call-time param so the same `{ tool, params }` step shape works across all substrates.
3413
+ this._artifactToolManager.GetAvailableToolNames().forEach((toolName) => register(new ArtifactToolInvocable(toolName, async (tool, p) => {
3414
+ const stored = await this._artifactToolManager.ExecuteSingleToolCall({
3415
+ artifactId: String(p.artifactId ?? ''),
3416
+ tool,
3417
+ input: p,
3418
+ });
3419
+ return stored.result;
3420
+ })));
3421
+ return registry;
3422
+ }
3423
+ /**
3424
+ * Runs a tool pipeline as a single `Tool` step in the run tree (sibling of the prompt step that
3425
+ * requested it, matching artifact-tool steps). ALL pipeline observability lives in this step's
3426
+ * `OutputData` — the per-stage breakdown, totals, bytes saved, and the tool chain — so there are
3427
+ * no dedicated pipeline entities and no extra SQL I/O; the run tree alone carries everything a
3428
+ * debug UI needs.
3429
+ *
3430
+ * @protected
3431
+ */
3432
+ async executePipelineAsStep(pipeline, params) {
3433
+ const stepEntity = await this.createStepEntity({
3434
+ stepType: 'Tool',
3435
+ stepName: `Pipeline: ${pipeline.steps.length} step(s)`,
3436
+ contextUser: params.contextUser,
3437
+ inputData: { steps: pipeline.steps },
3438
+ });
3439
+ const registry = this.buildPipelineRegistry(params);
3440
+ // The executor converts stage-level errors into a failed RESULT (it doesn't throw for those),
3441
+ // but an unexpected throw — e.g. a tool returning a non-serializable value (BigInt/circular)
3442
+ // that trips JSON.stringify in the executor's byte-accounting — must NEVER leave this step
3443
+ // stuck on 'Running'. Catch it and materialize a failed result so finalize always runs and the
3444
+ // failure surfaces as a 'Failed' step (answering "do pipeline errors show as errors?": yes).
3445
+ let result;
3446
+ try {
3447
+ result = await new PipelineExecutor(registry).Execute(pipeline.steps);
3448
+ }
3449
+ catch (e) {
3450
+ result = {
3451
+ success: false,
3452
+ finalOutput: null,
3453
+ steps: [],
3454
+ error: `Pipeline crashed: ${e?.message ?? String(e)}`,
3455
+ contextBytesSaved: 0,
3456
+ };
3457
+ }
3458
+ // A pipeline is ONE run-step — not a parent + a child step per stage. It runs server-side in a
3459
+ // single fast pass, so the full per-stage breakdown + totals live in this step's OutputData for
3460
+ // a debug UI to visualize — no separate entities, no extra DB writes.
3461
+ await this.finalizeStepEntity(stepEntity, result.success, result.success ? undefined : result.error, {
3462
+ success: result.success,
3463
+ toolChain: summarizePipelineStages(result.steps),
3464
+ steps: result.steps,
3465
+ contextBytesSaved: result.contextBytesSaved,
3466
+ totalBytesStreamed: result.steps.reduce((sum, s) => sum + s.outputSize, 0),
3467
+ totalDurationMs: result.steps.reduce((sum, s) => sum + s.durationMs, 0),
3468
+ failedStepIndex: result.failedStepIndex,
3469
+ });
3470
+ return result;
3471
+ }
3472
+ /**
3473
+ * Pushes the pipeline's final output (or its failure message) into the conversation for the
3474
+ * LLM's next turn, mirroring the artifact-tool "inject once, then expire" pattern. Only the
3475
+ * final output is surfaced — intermediate step outputs never enter the context window.
3476
+ *
3477
+ * @protected
3478
+ */
3479
+ injectPipelineResultMessage(params, result) {
3480
+ const diagnostic = result.success && result.diagnostic
3481
+ ? `\n⚠ Empty result — ${result.diagnostic}`
3482
+ : '';
3483
+ // Identify which pipeline this result belongs to (stage chain, e.g. `get_rows → where →
3484
+ // select`). Without it, multiple pipeline results across turns are indistinguishable once
3485
+ // compacted — mirrors how artifact-tool results name their tool/artifact.
3486
+ const label = summarizePipelineStages(result.steps);
3487
+ const content = result.success
3488
+ ? `Pipeline result [${label}] (final stage value — intermediate stages stayed out of context, ~${result.contextBytesSaved} bytes saved):\n\`\`\`\n${formatFinalOutput(result.finalOutput)}\n\`\`\`${diagnostic}`
3489
+ : `Pipeline failed [${label}].\n${result.error}`;
3490
+ const message = {
3491
+ role: 'user',
3492
+ content,
3493
+ metadata: {
3494
+ turnAdded: this._promptTurnCount,
3495
+ messageType: 'tool-result',
3496
+ expirationTurns: 3,
3497
+ expirationMode: 'Compact',
3498
+ compactMode: 'First N Chars',
3499
+ compactLength: 500,
3500
+ compactPromptId: '',
3501
+ },
3502
+ };
3503
+ params.conversationMessages.push(message);
3504
+ }
3334
3505
  /**
3335
3506
  * Creates a chat message containing sub-agent execution results.
3336
3507
  *
@@ -3523,7 +3694,8 @@ The context is now within limits. Please retry your request with the recovered c
3523
3694
  { docsFlag: 'includeForEachDocs', responseTypeKey: 'forEach' },
3524
3695
  { docsFlag: 'includeWhileDocs', responseTypeKey: 'while' },
3525
3696
  { docsFlag: 'includeScratchpadDocs', responseTypeKey: 'scratchpad' },
3526
- { docsFlag: 'includeArtifactToolsDocs', responseTypeKey: 'artifactToolCalls' }
3697
+ { docsFlag: 'includeArtifactToolsDocs', responseTypeKey: 'artifactToolCalls' },
3698
+ { docsFlag: 'includePipelineDocs', responseTypeKey: 'pipeline' }
3527
3699
  ];
3528
3700
  for (const { docsFlag, responseTypeKey } of alignmentMappings) {
3529
3701
  // Check if the user explicitly set this response type property
@@ -4468,6 +4640,13 @@ The context is now within limits. Please retry your request with the recovered c
4468
4640
  const errorMessage = JSON.stringify(CopyScalarsAndArrays(this._agentRun.LatestResult));
4469
4641
  throw new Error(`Failed to create agent run record: Details: ${errorMessage}`);
4470
4642
  }
4643
+ // Hand the now-persisted run (it has a stable ID) to the watchdog so a process restart,
4644
+ // crash, or failed terminal-state write can't leave it stuck 'Running' forever. Only the
4645
+ // server-side DB provider can heartbeat via SQL; client/non-DB providers simply opt out.
4646
+ const runProvider = params.provider || this._activeProvider;
4647
+ if (runProvider instanceof DatabaseProviderBase && params.contextUser) {
4648
+ AgentRunWatchdog.Instance.Track(this._agentRun.ID, runProvider, params.contextUser);
4649
+ }
4471
4650
  // Invoke callback if provided
4472
4651
  if (modifiedParams.onAgentRunCreated) {
4473
4652
  try {
@@ -4672,15 +4851,16 @@ The context is now within limits. Please retry your request with the recovered c
4672
4851
  * @protected
4673
4852
  */
4674
4853
  queueStepSave(stepEntity) {
4675
- const id = stepEntity.ID;
4676
- const previousSave = this._stepSavePromises.get(id) ?? Promise.resolve();
4854
+ // Chain on the entity INSTANCE (stable), NOT stepEntity.ID — the ID is empty until the INSERT
4855
+ // Save() assigns it, so an ID-keyed chain breaks for fast create→finalize sequences.
4856
+ const previousSave = this._stepSavePromises.get(stepEntity) ?? Promise.resolve();
4677
4857
  const currentSave = previousSave.then(() => stepEntity.Save()).then((ok) => {
4678
4858
  if (!ok) {
4679
- LogError(`Failed to save agent run step record ${id}: ${stepEntity.LatestResult?.CompleteMessage ?? 'unknown error'}`);
4859
+ LogError(`Failed to save agent run step record ${stepEntity.ID || '(unsaved)'}: ${stepEntity.LatestResult?.CompleteMessage ?? 'unknown error'}`);
4680
4860
  }
4681
4861
  return ok;
4682
4862
  });
4683
- this._stepSavePromises.set(id, currentSave);
4863
+ this._stepSavePromises.set(stepEntity, currentSave);
4684
4864
  this._pendingSaves.push(currentSave);
4685
4865
  }
4686
4866
  /**
@@ -5183,6 +5363,14 @@ The context is now within limits. Please retry your request with the recovered c
5183
5363
  else if (this._artifactToolManager.HasArtifacts()) {
5184
5364
  this.logStatus(`[ArtifactTools] LLM did not use artifact tools this turn (artifacts available but not accessed)`, true, params);
5185
5365
  }
5366
+ // Execute a tool pipeline if provided (zero turn cost — processed inline). Each step's
5367
+ // output is threaded into the next server-side; only the final step's output returns to
5368
+ // the LLM, so intermediate payloads never enter the context window.
5369
+ if (initialNextStep.pipeline?.steps?.length) {
5370
+ this.logStatus(`[Pipeline] LLM requested a ${initialNextStep.pipeline.steps.length}-stage pipeline: ${initialNextStep.pipeline.steps.map(s => s.tool ?? Object.keys(s)[0]).join(' | ')}`, true, params);
5371
+ const pipelineResult = await this.executePipelineAsStep(initialNextStep.pipeline, params);
5372
+ this.injectPipelineResultMessage(params, pipelineResult);
5373
+ }
5186
5374
  // now that we have processed the payload, we can process the next step which does validation and changes the next step if
5187
5375
  // validation fails
5188
5376
  const updatedNextStep = await this.processNextStep(initialNextStep, params, config.agentType, promptResult, finalPayload, stepEntity);
@@ -7901,6 +8089,8 @@ The context is now within limits. Please retry your request with the recovered c
7901
8089
  this._agentRun.TotalTokensUsed = tokenStats.totalTokens;
7902
8090
  this._agentRun.TotalPromptTokensUsed = tokenStats.promptTokens;
7903
8091
  this._agentRun.TotalCompletionTokensUsed = tokenStats.completionTokens;
8092
+ this._agentRun.TotalCacheReadTokensUsed = tokenStats.cacheReadTokens;
8093
+ this._agentRun.TotalCacheWriteTokensUsed = tokenStats.cacheWriteTokens;
7904
8094
  this._agentRun.TotalCost = tokenStats.totalCost;
7905
8095
  await this._agentRun.Save();
7906
8096
  }
@@ -7927,6 +8117,8 @@ The context is now within limits. Please retry your request with the recovered c
7927
8117
  this._agentRun.TotalTokensUsed = tokenStats.totalTokens;
7928
8118
  this._agentRun.TotalPromptTokensUsed = tokenStats.promptTokens;
7929
8119
  this._agentRun.TotalCompletionTokensUsed = tokenStats.completionTokens;
8120
+ this._agentRun.TotalCacheReadTokensUsed = tokenStats.cacheReadTokens;
8121
+ this._agentRun.TotalCacheWriteTokensUsed = tokenStats.cacheWriteTokens;
7930
8122
  this._agentRun.TotalCost = tokenStats.totalCost;
7931
8123
  await this._agentRun.Save();
7932
8124
  }
@@ -8008,6 +8200,8 @@ The context is now within limits. Please retry your request with the recovered c
8008
8200
  this._agentRun.TotalTokensUsed = tokenStats.totalTokens;
8009
8201
  this._agentRun.TotalPromptTokensUsed = tokenStats.promptTokens;
8010
8202
  this._agentRun.TotalCompletionTokensUsed = tokenStats.completionTokens;
8203
+ this._agentRun.TotalCacheReadTokensUsed = tokenStats.cacheReadTokens;
8204
+ this._agentRun.TotalCacheWriteTokensUsed = tokenStats.cacheWriteTokens;
8011
8205
  this._agentRun.TotalCost = tokenStats.totalCost;
8012
8206
  const ok = await this._agentRun.Save();
8013
8207
  if (!ok) {
@@ -8046,15 +8240,19 @@ The context is now within limits. Please retry your request with the recovered c
8046
8240
  let totalTokens = 0;
8047
8241
  let promptTokens = 0;
8048
8242
  let completionTokens = 0;
8243
+ let cacheReadTokens = 0;
8244
+ let cacheWriteTokens = 0;
8049
8245
  let totalCost = 0;
8050
8246
  // Iterate through the agent run's steps to sum up tokens
8051
8247
  if (this._agentRun?.Steps) {
8052
8248
  for (const step of this._agentRun.Steps) {
8053
8249
  if (step.StepType === 'Prompt' && step.PromptRun) {
8054
- // Add tokens from prompt runs
8250
+ // Add tokens from prompt runs (rollup fields include any nested child prompt runs)
8055
8251
  totalTokens += step.PromptRun.TokensUsedRollup || 0;
8056
8252
  promptTokens += step.PromptRun.TokensPromptRollup || 0;
8057
8253
  completionTokens += step.PromptRun.TokensCompletionRollup || 0;
8254
+ cacheReadTokens += step.PromptRun.TokensCacheReadRollup || 0;
8255
+ cacheWriteTokens += step.PromptRun.TokensCacheWriteRollup || 0;
8058
8256
  totalCost += step.PromptRun.TotalCost || 0;
8059
8257
  }
8060
8258
  else if (step.StepType === 'Sub-Agent' && step.SubAgentRun) {
@@ -8062,11 +8260,13 @@ The context is now within limits. Please retry your request with the recovered c
8062
8260
  totalTokens += step.SubAgentRun.TotalTokensUsed || 0;
8063
8261
  promptTokens += step.SubAgentRun.TotalPromptTokensUsed || 0;
8064
8262
  completionTokens += step.SubAgentRun.TotalCompletionTokensUsed || 0;
8263
+ cacheReadTokens += step.SubAgentRun.TotalCacheReadTokensUsed || 0;
8264
+ cacheWriteTokens += step.SubAgentRun.TotalCacheWriteTokensUsed || 0;
8065
8265
  totalCost += step.SubAgentRun.TotalCost || 0;
8066
8266
  }
8067
8267
  }
8068
8268
  }
8069
- return { totalTokens, promptTokens, completionTokens, totalCost };
8269
+ return { totalTokens, promptTokens, completionTokens, cacheReadTokens, cacheWriteTokens, totalCost };
8070
8270
  }
8071
8271
  /**
8072
8272
  * Gets the count of how many times a specific action has been executed in this agent run.