@salesforce/sfdx-agent-sdk 0.73.0 → 0.75.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/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to `@salesforce/sfdx-agent-sdk` are documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.75.0] - 2026-09-08
7
+
8
+ _No changes — released alongside dependent packages._
9
+
10
+ ## [0.74.0] - 2026-09-08
11
+
12
+ ### Features
13
+ - **agent-sdk**: self-describing usage fields on chat-stream-completed log @W-24125082@ ([#800](https://github.com/forcedotcom/agentic-dx/pull/800))
14
+
15
+ ### Chores
16
+ - **deps-dev**: bump the dev-dependencies group with 3 updates ([#795](https://github.com/forcedotcom/agentic-dx/pull/795))
17
+
6
18
  ## [0.73.0] - 2026-09-04
7
19
 
8
20
  ### Features
@@ -498,13 +498,18 @@ export declare class DefaultChatSession implements ChatSession {
498
498
  */
499
499
  private effectiveInputTokens;
500
500
  /**
501
- * Compact context-window fields for the `chat-stream-completed` log record —
502
- * the fraction (rounded to a percent), the token amount that fraction is over,
503
- * and the window it is out of. Empty when the turn reported no input-side usage
504
- * (so the completion log stays unchanged pre-first-turn / post-`clearHistory()`).
505
- * Shares {@link getContextUsage}'s last-step numerator via
506
- * {@link effectiveInputTokens}, read from the same `latestUsage` snapshot
507
- * `wrapEventStream` captures.
501
+ * Last-step usage + compact context-window occupancy for the
502
+ * `chat-stream-completed` log record: the last model step's full usage
503
+ * (`lastStepUsage`), the token amount that occupies the window
504
+ * (`contextTokens` the last-step effective input), the window it is out of
505
+ * (`contextWindow`), and the occupancy ratio as a 0–1 float (`usedFraction`).
506
+ * `usedFraction` matches the SDK/service occupancy ratio exactly — the log no
507
+ * longer emits the redundant, lossy `contextUsedPercent` (a consumer wanting a
508
+ * percent computes `round(usedFraction * 100)`) (W-24125082, Phase 1). Empty
509
+ * when the turn reported no input-side usage, so the completion log stays
510
+ * unchanged pre-first-turn / post-`clearHistory()`. Shares
511
+ * {@link getContextUsage}'s last-step numerator via {@link effectiveInputTokens},
512
+ * read from the same `latestUsage` snapshot `wrapEventStream` captures.
508
513
  */
509
514
  private contextUsageLogFields;
510
515
  /**
@@ -202,22 +202,36 @@ export class DefaultChatSession {
202
202
  let sawFinish = false;
203
203
  let lastError;
204
204
  let finishUsage;
205
+ // Count model steps in the turn so the completion log's `turnUsage`
206
+ // (the whole-turn aggregate) is interpretable next to `lastStepUsage`:
207
+ // lastStepUsage.inputTokens ≤ turnUsage.inputTokens ≤ lastStepUsage.inputTokens × stepCount
208
+ // The lower bound is strict (<) only when stepCount > 1; a single-step
209
+ // turn's aggregate equals its one step. `stepCount` counts EVERY model
210
+ // step, including one whose `step-finish` reported undefined usage
211
+ // (W-22692131) — in that gap case `lastStepUsage` carries forward the last
212
+ // reporting step, so the upper bound just carries extra slack.
213
+ let stepCount = 0;
205
214
  try {
206
215
  for await (const event of stream) {
207
216
  this.chatEventBus.emit(event);
208
217
  this.deriveToolTelemetry(event);
209
218
  yield event;
210
- if (event.type === 'step-finish' && event.usage !== undefined) {
211
- // Snapshot the most recent per-step usage. Last-step semantics
212
- // (not the per-turn `finish.usage` aggregate) `finish.usage`
213
- // sums every step inside the turn and double-counts persistent
214
- // context, which is the wrong denominator for "how full is my
215
- // context". An undefined usage on this step is intentionally
216
- // ignored so the prior reading is carried forward gateway-side
217
- // gaps are rare but real (W-22692131) and clobbering with
218
- // undefined would surface as a transient hole consumers can't
219
- // distinguish from a fresh session.
220
- this.latestUsage = event.usage;
219
+ if (event.type === 'step-finish') {
220
+ // Every step-finish is one model step; count it for the log's
221
+ // `stepCount` regardless of whether this step reported usage.
222
+ stepCount++;
223
+ if (event.usage !== undefined) {
224
+ // Snapshot the most recent per-step usage. Last-step semantics
225
+ // (not the per-turn `finish.usage` aggregate)`finish.usage`
226
+ // sums every step inside the turn and double-counts persistent
227
+ // context, which is the wrong denominator for "how full is my
228
+ // context". An undefined usage on this step is intentionally
229
+ // ignored so the prior reading is carried forward — gateway-side
230
+ // gaps are rare but real (W-22692131) and clobbering with
231
+ // undefined would surface as a transient hole consumers can't
232
+ // distinguish from a fresh session.
233
+ this.latestUsage = event.usage;
234
+ }
221
235
  }
222
236
  if (event.type === 'finish') {
223
237
  sawFinish = true;
@@ -304,13 +318,27 @@ export class DefaultChatSession {
304
318
  durationMs,
305
319
  usage: finishUsage,
306
320
  }, finishedAt);
307
- // Fold the post-turn context-window occupancy onto the existing
308
- // completion log — % and token amount, so an operator can watch a
309
- // thread approach its limit (and correlate with a later
310
- // `context-window-exceeded` failure) without subscribing to
311
- // telemetry or adding a separate, noisier log line. Reuses the
312
- // `getContextUsage()` snapshot (last-step semantics), so it is
313
- // present only once a step reported usage; omitted otherwise.
321
+ // Fold the turn's usage + post-turn context-window occupancy onto the
322
+ // existing completion log so an operator can read both without
323
+ // subscribing to telemetry or adding a noisier log line. The two
324
+ // measurements carry self-describing names (W-24125082, Phase 1):
325
+ // - `turnUsage` the whole-turn aggregate (`finish.usage`), the
326
+ // billing/throughput total; sums every step and can exceed the window.
327
+ // - `lastStepUsage` + the occupancy fields the last model step, the
328
+ // right "how full is my context" reading (see contextUsageLogFields).
329
+ // - `stepCount` — makes `turnUsage` interpretable next to `lastStepUsage`.
330
+ // The ambiguous `usage` field is intentionally NOT emitted (the log is the
331
+ // free, unshipped surface — no alias needed; carrying a third duplicate
332
+ // object would only bloat every record). Both blocks preserve today's
333
+ // omit-when-absent behavior: `turnUsage`/`stepCount` are omitted before any
334
+ // step reports, and `contextUsageLogFields()` stays empty pre-first-turn /
335
+ // post-`clearHistory()`.
336
+ //
337
+ // `turnUsage` forwards `finishUsage` BY REFERENCE (unlike `lastStepUsage`,
338
+ // which spreads `latestUsage`): `finishUsage` is a turn-local value discarded
339
+ // when this method returns, so no consumer can observe mutation of it;
340
+ // `latestUsage` is retained session state a later `getContextUsage()` reads,
341
+ // so it must be copied.
314
342
  this.logBus.emitLog({
315
343
  level: 'info',
316
344
  message: 'Chat stream completed',
@@ -319,7 +347,8 @@ export class DefaultChatSession {
319
347
  agentId: this.agentId,
320
348
  threadId: this.threadId,
321
349
  durationMs,
322
- ...(finishUsage !== undefined ? { usage: finishUsage } : {}),
350
+ ...(finishUsage !== undefined ? { turnUsage: finishUsage } : {}),
351
+ ...(stepCount > 0 ? { stepCount } : {}),
323
352
  ...this.contextUsageLogFields(),
324
353
  },
325
354
  }, finishedAt);
@@ -466,13 +495,18 @@ export class DefaultChatSession {
466
495
  return (inputTokens ?? 0) + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
467
496
  }
468
497
  /**
469
- * Compact context-window fields for the `chat-stream-completed` log record —
470
- * the fraction (rounded to a percent), the token amount that fraction is over,
471
- * and the window it is out of. Empty when the turn reported no input-side usage
472
- * (so the completion log stays unchanged pre-first-turn / post-`clearHistory()`).
473
- * Shares {@link getContextUsage}'s last-step numerator via
474
- * {@link effectiveInputTokens}, read from the same `latestUsage` snapshot
475
- * `wrapEventStream` captures.
498
+ * Last-step usage + compact context-window occupancy for the
499
+ * `chat-stream-completed` log record: the last model step's full usage
500
+ * (`lastStepUsage`), the token amount that occupies the window
501
+ * (`contextTokens` the last-step effective input), the window it is out of
502
+ * (`contextWindow`), and the occupancy ratio as a 0–1 float (`usedFraction`).
503
+ * `usedFraction` matches the SDK/service occupancy ratio exactly — the log no
504
+ * longer emits the redundant, lossy `contextUsedPercent` (a consumer wanting a
505
+ * percent computes `round(usedFraction * 100)`) (W-24125082, Phase 1). Empty
506
+ * when the turn reported no input-side usage, so the completion log stays
507
+ * unchanged pre-first-turn / post-`clearHistory()`. Shares
508
+ * {@link getContextUsage}'s last-step numerator via {@link effectiveInputTokens},
509
+ * read from the same `latestUsage` snapshot `wrapEventStream` captures.
476
510
  */
477
511
  contextUsageLogFields() {
478
512
  const contextTokens = this.effectiveInputTokens();
@@ -480,9 +514,11 @@ export class DefaultChatSession {
480
514
  return {};
481
515
  const contextWindow = this.getContextWindow();
482
516
  return {
517
+ // Spread so a later `getContextUsage()` reader can't observe log-side mutation.
518
+ lastStepUsage: { ...this.latestUsage },
483
519
  contextTokens,
484
520
  contextWindow,
485
- contextUsedPercent: Math.round(Math.min(1, Math.max(0, contextTokens / contextWindow)) * 100),
521
+ usedFraction: Math.min(1, Math.max(0, contextTokens / contextWindow)),
486
522
  };
487
523
  }
488
524
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.73.0",
3
+ "version": "0.75.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -48,9 +48,9 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@eslint/js": "^10.0.1",
51
- "@salesforce/sfdx-agent-harness-claude": "0.69.0",
52
- "@salesforce/sfdx-agent-harness-mastra": "0.72.0",
53
- "@salesforce/sfdx-agent-harness-openai": "0.38.0",
51
+ "@salesforce/sfdx-agent-harness-claude": "0.71.0",
52
+ "@salesforce/sfdx-agent-harness-mastra": "0.74.0",
53
+ "@salesforce/sfdx-agent-harness-openai": "0.40.0",
54
54
  "@types/node": "^22.20.1",
55
55
  "@vitest/coverage-istanbul": "^4.1.11",
56
56
  "@vitest/eslint-plugin": "^1.6.27",
@@ -60,12 +60,12 @@
60
60
  "eslint-plugin-import": "^2.32.0",
61
61
  "eslint-plugin-n": "^18.3.0",
62
62
  "globals": "^17.12.0",
63
- "lint-staged": "^17.3.0",
63
+ "lint-staged": "^17.4.1",
64
64
  "prettier": "^3.9.6",
65
65
  "rimraf": "^6.1.3",
66
- "tsx": "^4.23.12",
66
+ "tsx": "^4.23.13",
67
67
  "typescript": "^7.0.2",
68
- "typescript-eslint": "^8.68.0",
68
+ "typescript-eslint": "^8.69.0",
69
69
  "vitest": "^4.1.11"
70
70
  },
71
71
  "engines": {