@salesforce/sfdx-agent-sdk 0.66.0 → 0.67.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,11 @@
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.67.0] - 2026-09-02
7
+
8
+ ### Features
9
+ - **agent-sdk,harness-mastra,harness-claude,harness-openai**: classify context-window overflow as a typed CONTEXT_LENGTH_EXCEEDED error @W-24074717@ ([#788](https://github.com/forcedotcom/agentic-dx/pull/788))
10
+
6
11
  ## [0.66.0] - 2026-09-02
7
12
 
8
13
  _No changes — released alongside dependent packages._
package/README.md CHANGED
@@ -779,6 +779,7 @@ from `AgentSDKErrorType`:
779
779
  | `AGENT_NOT_FOUND` | `AgentManager.getAgent()`, `AgentManager.destroyAgent()`, `AgentManager.recoverAgent()` (id neither live nor a restore-failure) |
780
780
  | `CHAT_SESSION_NOT_FOUND` | `Agent.getChatSession()`, `Agent.destroyChatSession()`, `Agent.cloneChatSession()`, `Agent.compactChatSession()` |
781
781
  | `COMPACTION_FAILED` | `Agent.compactChatSession()` when the harness's underlying summarization call rejects. The original error is attached as `cause`; the source session is left intact. |
782
+ | `CONTEXT_LENGTH_EXCEEDED` | A turn's prompt exceeds the model's context window and the provider rejects it (Anthropic/Bedrock HTTP 400 `invalid_request_error` with a token-count message; OpenAI `invalid_prompt`). Surfaces **in-stream** as the `error` on an `ErrorEvent` whose `code` is `'context-window-exceeded'` (both mapped from one classification, so `error instanceof AgentSDKError && err.type === CONTEXT_LENGTH_EXCEEDED` ⟺ `code === 'context-window-exceeded'`), with the actionable message _"context window limit reached — compact this chat or start a new one."_ and the raw provider error on `.cause`. Classified uniformly by all three harnesses. Recover by compacting (`Agent.compactChatSession()`) or starting a new chat. Build/branch on it via the exported `CONTEXT_LENGTH_EXCEEDED_CODE`, `createContextLengthExceededError()`, and `isContextWindowOverflowMessage()` helpers. |
782
783
  | `DISPOSED` | `Agent` and `ChatSession` methods called after the owner has been destroyed |
783
784
  | `INCOMPATIBLE_HARNESS` | `createAgentManager()` when the factory advertises an unsupported `protocolVersion`, or the constructed harness reports a `protocolVersion` that differs from the factory's |
784
785
  | `INVALID_MESSAGE_CONTENT` | `ChatSession.chat()` / harness `stream()` when a message part is not valid as input (a `tool-call`/`tool-result` part, or non-base64-string file data); also `ChatSession.setSessionContext()` / harness `setSessionContext()` when the object exceeds a harness's size / nesting bounds (Mastra: 256 KiB serialized, depth 200). `getSessionContext()` never throws on a corrupt stored slot — it soft-skips to `{}` and logs. |
@@ -487,6 +487,26 @@ export declare class DefaultChatSession implements ChatSession {
487
487
  * input-side counts at all.
488
488
  */
489
489
  getContextUsage(): ContextUsage;
490
+ /**
491
+ * The last-step context numerator — the sum of all three input-bearing usage
492
+ * fields, or `undefined` when ALL three are absent (a fresh session or a turn
493
+ * with a gateway usage gap). This is the single definition of "how full is the
494
+ * context" shared by {@link getContextUsage} (as `usedFraction`'s numerator)
495
+ * and {@link contextUsageLogFields} (as the log's `contextTokens`), so the two
496
+ * can't drift. The cache fields are load-bearing on Bedrock-Claude — see the
497
+ * SDK ARCHITECTURE note on the `usedFraction` numerator.
498
+ */
499
+ private effectiveInputTokens;
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.
508
+ */
509
+ private contextUsageLogFields;
490
510
  /**
491
511
  * @requirements
492
512
  * - IF `message` is a `string`, it MUST be formatted into a standard `Message` object array containing exactly one message.
@@ -304,6 +304,13 @@ export class DefaultChatSession {
304
304
  durationMs,
305
305
  usage: finishUsage,
306
306
  }, 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.
307
314
  this.logBus.emitLog({
308
315
  level: 'info',
309
316
  message: 'Chat stream completed',
@@ -313,6 +320,7 @@ export class DefaultChatSession {
313
320
  threadId: this.threadId,
314
321
  durationMs,
315
322
  ...(finishUsage !== undefined ? { usage: finishUsage } : {}),
323
+ ...this.contextUsageLogFields(),
316
324
  },
317
325
  }, finishedAt);
318
326
  }
@@ -431,11 +439,7 @@ export class DefaultChatSession {
431
439
  getContextUsage() {
432
440
  this.assertNotDisposed();
433
441
  const contextWindow = this.getContextWindow();
434
- const { inputTokens, cachedInputTokens, cacheWriteInputTokens } = this.latestUsage;
435
- const allInputUndefined = inputTokens === undefined && cachedInputTokens === undefined && cacheWriteInputTokens === undefined;
436
- const effectiveInputTokens = allInputUndefined
437
- ? undefined
438
- : (inputTokens ?? 0) + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
442
+ const effectiveInputTokens = this.effectiveInputTokens();
439
443
  const usedFraction = effectiveInputTokens === undefined
440
444
  ? undefined
441
445
  : Math.min(1, Math.max(0, effectiveInputTokens / contextWindow));
@@ -445,6 +449,42 @@ export class DefaultChatSession {
445
449
  // all primitives, so a shallow copy is sufficient.
446
450
  return { usage: { ...this.latestUsage }, contextWindow, usedFraction };
447
451
  }
452
+ /**
453
+ * The last-step context numerator — the sum of all three input-bearing usage
454
+ * fields, or `undefined` when ALL three are absent (a fresh session or a turn
455
+ * with a gateway usage gap). This is the single definition of "how full is the
456
+ * context" shared by {@link getContextUsage} (as `usedFraction`'s numerator)
457
+ * and {@link contextUsageLogFields} (as the log's `contextTokens`), so the two
458
+ * can't drift. The cache fields are load-bearing on Bedrock-Claude — see the
459
+ * SDK ARCHITECTURE note on the `usedFraction` numerator.
460
+ */
461
+ effectiveInputTokens() {
462
+ const { inputTokens, cachedInputTokens, cacheWriteInputTokens } = this.latestUsage;
463
+ if (inputTokens === undefined && cachedInputTokens === undefined && cacheWriteInputTokens === undefined) {
464
+ return undefined;
465
+ }
466
+ return (inputTokens ?? 0) + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
467
+ }
468
+ /**
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.
476
+ */
477
+ contextUsageLogFields() {
478
+ const contextTokens = this.effectiveInputTokens();
479
+ if (contextTokens === undefined)
480
+ return {};
481
+ const contextWindow = this.getContextWindow();
482
+ return {
483
+ contextTokens,
484
+ contextWindow,
485
+ contextUsedPercent: Math.round(Math.min(1, Math.max(0, contextTokens / contextWindow)) * 100),
486
+ };
487
+ }
448
488
  /**
449
489
  * @requirements
450
490
  * - IF `message` is a `string`, it MUST be formatted into a standard `Message` object array containing exactly one message.
package/dist/errors.d.ts CHANGED
@@ -2,6 +2,7 @@ export declare const AgentSDKErrorType: {
2
2
  readonly AGENT_NOT_FOUND: 'AGENT_NOT_FOUND';
3
3
  readonly CHAT_SESSION_NOT_FOUND: 'CHAT_SESSION_NOT_FOUND';
4
4
  readonly COMPACTION_FAILED: 'COMPACTION_FAILED';
5
+ readonly CONTEXT_LENGTH_EXCEEDED: 'CONTEXT_LENGTH_EXCEEDED';
5
6
  readonly DISPOSED: 'DISPOSED';
6
7
  readonly INCOMPATIBLE_HARNESS: 'INCOMPATIBLE_HARNESS';
7
8
  readonly INVALID_MCP_AUTH_CONFIG: 'INVALID_MCP_AUTH_CONFIG';
@@ -20,3 +21,40 @@ export declare class AgentSDKError extends Error {
20
21
  readonly type: AgentSDKErrorType;
21
22
  constructor(message: string, type: AgentSDKErrorType, options?: ErrorOptions);
22
23
  }
24
+ /**
25
+ * Canonical machine-readable `ErrorEvent.code` for a context-window overflow —
26
+ * the prompt for the next turn exceeds the model's context window and the
27
+ * provider rejects it (Anthropic/Bedrock HTTP 400 `invalid_request_error` with a
28
+ * token-count message; OpenAI `invalid_prompt`). Shipped first on the Mastra
29
+ * harness (#782); this constant is the single source every harness now stamps so
30
+ * the wire code can't drift, and it maps 1:1 to
31
+ * {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED} (the typed error carried on
32
+ * the same {@link ErrorEvent}'s `error`; a deferred pre-flight guard would throw
33
+ * that same typed error before the turn). Both wires (native `/events`, AG-UI)
34
+ * relay `code` verbatim, so the value is a contract — do not rename it.
35
+ */
36
+ export declare const CONTEXT_LENGTH_EXCEEDED_CODE: 'context-window-exceeded';
37
+ /**
38
+ * Actionable, consumer-facing message for a context-window overflow. Carried as
39
+ * the {@link AgentSDKError} message so the same text surfaces in-stream (on
40
+ * `ErrorEvent.error`) today, and would surface identically once the deferred
41
+ * pre-flight guard in `ChatSession.chat()` lands. The underlying provider error
42
+ * is preserved on `.cause`.
43
+ */
44
+ export declare const CONTEXT_LENGTH_EXCEEDED_MESSAGE: 'context window limit reached — compact this chat or start a new one.';
45
+ /**
46
+ * Whether `message` looks like a provider context-window-overflow rejection.
47
+ * Harnesses call this on the provider error's message text to decide whether to
48
+ * classify a failure as {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED}.
49
+ */
50
+ export declare function isContextWindowOverflowMessage(message: string): boolean;
51
+ /**
52
+ * Builds the canonical typed context-window-overflow error. Every harness sets
53
+ * this as the `error` on an `ErrorEvent` whose `code` is
54
+ * {@link CONTEXT_LENGTH_EXCEEDED_CODE}; a deferred pre-flight guard in
55
+ * `ChatSession.chat()` would throw this same typed error when the next turn would
56
+ * clearly overflow — so the in-stream and pre-flight overflow signals are one
57
+ * typed error, not two. The provider error (when known) rides on `.cause` so its
58
+ * detail survives.
59
+ */
60
+ export declare function createContextLengthExceededError(cause?: unknown): AgentSDKError;
package/dist/errors.js CHANGED
@@ -6,6 +6,7 @@ export const AgentSDKErrorType = {
6
6
  AGENT_NOT_FOUND: 'AGENT_NOT_FOUND',
7
7
  CHAT_SESSION_NOT_FOUND: 'CHAT_SESSION_NOT_FOUND',
8
8
  COMPACTION_FAILED: 'COMPACTION_FAILED',
9
+ CONTEXT_LENGTH_EXCEEDED: 'CONTEXT_LENGTH_EXCEEDED',
9
10
  DISPOSED: 'DISPOSED',
10
11
  INCOMPATIBLE_HARNESS: 'INCOMPATIBLE_HARNESS',
11
12
  INVALID_MCP_AUTH_CONFIG: 'INVALID_MCP_AUTH_CONFIG',
@@ -27,4 +28,52 @@ export class AgentSDKError extends Error {
27
28
  this.type = type;
28
29
  }
29
30
  }
31
+ /**
32
+ * Canonical machine-readable `ErrorEvent.code` for a context-window overflow —
33
+ * the prompt for the next turn exceeds the model's context window and the
34
+ * provider rejects it (Anthropic/Bedrock HTTP 400 `invalid_request_error` with a
35
+ * token-count message; OpenAI `invalid_prompt`). Shipped first on the Mastra
36
+ * harness (#782); this constant is the single source every harness now stamps so
37
+ * the wire code can't drift, and it maps 1:1 to
38
+ * {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED} (the typed error carried on
39
+ * the same {@link ErrorEvent}'s `error`; a deferred pre-flight guard would throw
40
+ * that same typed error before the turn). Both wires (native `/events`, AG-UI)
41
+ * relay `code` verbatim, so the value is a contract — do not rename it.
42
+ */
43
+ export const CONTEXT_LENGTH_EXCEEDED_CODE = 'context-window-exceeded';
44
+ /**
45
+ * Actionable, consumer-facing message for a context-window overflow. Carried as
46
+ * the {@link AgentSDKError} message so the same text surfaces in-stream (on
47
+ * `ErrorEvent.error`) today, and would surface identically once the deferred
48
+ * pre-flight guard in `ChatSession.chat()` lands. The underlying provider error
49
+ * is preserved on `.cause`.
50
+ */
51
+ export const CONTEXT_LENGTH_EXCEEDED_MESSAGE = 'context window limit reached — compact this chat or start a new one.';
52
+ /**
53
+ * Provider overflow-message patterns, shared across harnesses so the recognition
54
+ * of a context-window rejection can't drift between the three implementations.
55
+ * Matches the token-count / context-length phrasings Anthropic/Bedrock
56
+ * (`ValidationException`) and OpenAI (`invalid_prompt`) use.
57
+ */
58
+ const CONTEXT_WINDOW_OVERFLOW_PATTERNS = /input is too long|prompt is too long|too many (input )?tokens|maximum context length|context (window|length)|exceed(s|ed)? (the )?(maximum )?context/i;
59
+ /**
60
+ * Whether `message` looks like a provider context-window-overflow rejection.
61
+ * Harnesses call this on the provider error's message text to decide whether to
62
+ * classify a failure as {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED}.
63
+ */
64
+ export function isContextWindowOverflowMessage(message) {
65
+ return CONTEXT_WINDOW_OVERFLOW_PATTERNS.test(message);
66
+ }
67
+ /**
68
+ * Builds the canonical typed context-window-overflow error. Every harness sets
69
+ * this as the `error` on an `ErrorEvent` whose `code` is
70
+ * {@link CONTEXT_LENGTH_EXCEEDED_CODE}; a deferred pre-flight guard in
71
+ * `ChatSession.chat()` would throw this same typed error when the next turn would
72
+ * clearly overflow — so the in-stream and pre-flight overflow signals are one
73
+ * typed error, not two. The provider error (when known) rides on `.cause` so its
74
+ * detail survives.
75
+ */
76
+ export function createContextLengthExceededError(cause) {
77
+ return new AgentSDKError(CONTEXT_LENGTH_EXCEEDED_MESSAGE, AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED, cause !== undefined ? { cause } : undefined);
78
+ }
30
79
  //# sourceMappingURL=errors.js.map
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ export type { ModelConnectivityInfo, ProviderHint } from './types/model-connecti
23
23
  export type { LlmRequestEvent, LlmResponseEvent, McpToolCallCompletedEvent, WireCommunicationEvent, WireCommunicationEventCallback, WireMonitoringNotSupportedEvent, } from './types/wire-communication-event.js';
24
24
  export { WireCommunicationFileWriter, type WireCommunicationEmitter, type WireCommunicationFileWriterOptions, } from './wire-communication-file-writer.js';
25
25
  export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './harness/index.js';
26
- export { AgentSDKError, AgentSDKErrorType } from './errors.js';
26
+ export { AgentSDKError, AgentSDKErrorType, CONTEXT_LENGTH_EXCEEDED_CODE, CONTEXT_LENGTH_EXCEEDED_MESSAGE, createContextLengthExceededError, isContextWindowOverflowMessage, } from './errors.js';
27
27
  export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, LlmRetryEvent, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, McpServerStatusChangedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalPolicyResolvedEvent, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
28
28
  export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
29
29
  export type { EnvironmentFields } from '@salesforce/agentic-common';
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ export {} from './chat-session.js';
20
20
  export { ApiKeyConnectivityResolver } from './api-key-connectivity-resolver.js';
21
21
  export { WireCommunicationFileWriter, } from './wire-communication-file-writer.js';
22
22
  // ── Errors ───────────────────────────────────────────────────────────
23
- export { AgentSDKError, AgentSDKErrorType } from './errors.js';
23
+ export { AgentSDKError, AgentSDKErrorType, CONTEXT_LENGTH_EXCEEDED_CODE, CONTEXT_LENGTH_EXCEEDED_MESSAGE, createContextLengthExceededError, isContextWindowOverflowMessage, } from './errors.js';
24
24
  export { readEnvironmentContext, resolveFeatureId } from '@salesforce/agentic-common';
25
25
  // ── MCP Auth ────────────────────────────────────────────────────────
26
26
  export { resolveMcpServerHeaders, isSalesforcePlatformMcpUrl } from './mcp-auth.js';
@@ -247,9 +247,23 @@ export type StepFinishEvent = {
247
247
  /** An error occurred during streaming. */
248
248
  export type ErrorEvent = {
249
249
  type: 'error';
250
- /** The error that occurred. */
250
+ /**
251
+ * The error that occurred. For a classified failure this is a typed
252
+ * {@link AgentSDKError} whose `type` maps 1:1 to {@link code} — e.g. a
253
+ * context-window overflow carries `AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED`
254
+ * with `code === 'context-window-exceeded'`. An unrecognized provider error
255
+ * surfaces as a plain `Error` with no `code`.
256
+ */
251
257
  error: Error;
252
- /** Machine-readable error code (e.g., `'insufficient-tokens'`). */
258
+ /**
259
+ * Stable, machine-readable classification of the error, when the harness
260
+ * recognized it — consumers branch on this instead of string-matching
261
+ * `error.message`. The cross-harness value in use today is
262
+ * `'context-window-exceeded'` (the {@link AgentSDKErrorType.CONTEXT_LENGTH_EXCEEDED}
263
+ * overflow signal, emitted uniformly by every harness). Other values are
264
+ * harness-specific (e.g. `'network-error'`, `'abort'`, `'tool-approval-timeout'`).
265
+ * `undefined` when the harness could not classify the error.
266
+ */
253
267
  code?: string;
254
268
  };
255
269
  /** The entire stream has completed. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,9 +47,9 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@eslint/js": "^10.0.1",
50
- "@salesforce/sfdx-agent-harness-claude": "0.62.0",
51
- "@salesforce/sfdx-agent-harness-mastra": "0.65.0",
52
- "@salesforce/sfdx-agent-harness-openai": "0.31.0",
50
+ "@salesforce/sfdx-agent-harness-claude": "0.63.0",
51
+ "@salesforce/sfdx-agent-harness-mastra": "0.66.0",
52
+ "@salesforce/sfdx-agent-harness-openai": "0.32.0",
53
53
  "@types/node": "^22.20.1",
54
54
  "@vitest/coverage-istanbul": "^4.1.10",
55
55
  "@vitest/eslint-plugin": "^1.6.27",