@salesforce/sfdx-agent-sdk 0.14.0 → 0.16.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.
@@ -112,7 +112,14 @@ export interface AgentHarness {
112
112
  }): Promise<void>;
113
113
  /**
114
114
  * Destroy an agent and release its resources (MCP connections, workspace, memory).
115
+ *
116
+ * MUST throw if `agentId` is not registered. Symmetric with `createThread`,
117
+ * `destroyThread`, and `clearMessages`, which all reject unknown ids the
118
+ * same way; gives SDK rollback paths in `Agent.updateAgentConfig` and
119
+ * `AgentManager.installAgent` an explicit failure mode they can catch.
120
+ *
115
121
  * @param agentId - ID of the agent to destroy.
122
+ * @returns `true` after a real removal.
116
123
  */
117
124
  destroyAgent(agentId: string): Promise<boolean>;
118
125
  /**
@@ -124,8 +131,11 @@ export interface AgentHarness {
124
131
  * including connection status and discovered tool names. This is a synchronous
125
132
  * snapshot — status is updated asynchronously by background discovery promises.
126
133
  *
134
+ * MUST throw if `agentId` is not registered.
135
+ *
127
136
  * @param agentId - ID of the agent whose MCP servers to inspect.
128
- * @returns Info for each configured MCP server (empty array if none configured).
137
+ * @returns Info for each configured MCP server (empty array if the agent
138
+ * exists but has no MCP servers configured).
129
139
  */
130
140
  getMcpServerInfo(agentId: string): McpServerInfo[];
131
141
  /**
@@ -175,14 +185,31 @@ export interface AgentHarness {
175
185
  */
176
186
  getThreadIds(agentId: string): Promise<string[]>;
177
187
  /**
178
- * Clone an existing thread, creating a new thread with copied message history.
179
- * Used to implement conversation forking.
188
+ * Clone an existing thread, creating a new thread that mirrors the source
189
+ * thread's state at the moment of the call. Used to implement conversation
190
+ * forking.
191
+ *
192
+ * The harness chooses the new thread's id; consumers read it from the
193
+ * returned value. The id is unique within the agent.
194
+ *
195
+ * Two source-state shapes are observable to consumers:
196
+ *
197
+ * - **Source thread has been streamed at least once** — the new thread
198
+ * inherits the source's persisted message history; subsequent
199
+ * `getMessages()` returns it. Implementations may copy the underlying
200
+ * transcript (Mastra's libsql `cloneThread`, Claude's `forkSession`)
201
+ * or any harness-specific equivalent.
202
+ * - **Source thread has never been streamed** (`addContext`-only or
203
+ * freshly-created) — the new thread is allocated empty by design;
204
+ * `addContext`-injected messages on the source are copied forward by
205
+ * harnesses that mirror them in-process, but no persisted transcript
206
+ * exists to fork.
207
+ *
180
208
  * @param agentId - ID of the owning agent.
181
209
  * @param sourceThreadId - ID of the thread to clone.
182
- * @param targetThreadId - Optional ID for the new thread.
183
210
  * @returns The ID of the cloned thread.
184
211
  */
185
- cloneThread(agentId: string, sourceThreadId: string, targetThreadId?: string): Promise<string>;
212
+ cloneThread(agentId: string, sourceThreadId: string): Promise<string>;
186
213
  /**
187
214
  * Compacts a thread's message history to reduce context window usage.
188
215
  * Starts a new conversation thread seeded with an LLM-generated summary of the current session.
@@ -211,55 +238,65 @@ export interface AgentHarness {
211
238
  stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<ChatStreamResult>;
212
239
  /**
213
240
  * Feed the result of a **consumer-executed (client-side) tool** back into the
214
- * conversation and resume stream generation. Implements the consumer-facing
241
+ * conversation. Implements the consumer-facing
215
242
  * {@link ChatSession.submitToolResult} contract: see that JSDoc for the
216
243
  * full consumer-level semantics.
217
244
  *
218
245
  * Called only for tools declared in {@link HarnessAgentConfig.tools} (no
219
246
  * `execute` function). Tools provided via `mcpServers` are executed by the
220
- * harness directly and never reach this method. The harness resumes the
221
- * suspended agentic loop from the most recent `stream()` call on the given
222
- * thread, feeds `toolResult` into the loop, and returns a continuation
223
- * stream the consumer iterates for the model's next turn.
247
+ * harness directly and never reach this method.
248
+ *
249
+ * Returns `Promise<void>` once the harness has accepted the tool result
250
+ * (the parked bridge handler resolved). Post-resume events
251
+ * (`tool-result`, model follow-up text, terminal `finish`) flow through
252
+ * the SAME `ChatStreamResult.eventStream` returned by the most recent
253
+ * {@link stream} call on the thread — the consumer is still iterating
254
+ * that stream and sees the events arrive.
224
255
  *
225
256
  * @param agentId - ID of the agent.
226
257
  * @param threadId - ID of the conversation thread.
227
258
  * @param toolResult - The completed tool execution result; `toolCallId`
228
259
  * matches the id from the originating `tool-call` event.
229
260
  */
230
- submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<ChatStreamResult>;
261
+ submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<void>;
231
262
  /**
232
263
  * Approve a pending tool call, allowing the harness to execute it.
233
264
  * Called after receiving a `tool-approval-request` event.
234
265
  *
235
- * Returns a `ChatStreamResult` containing the continuation stream the harness
236
- * executes the approved tool, generates the model's follow-up response, and
237
- * streams both the text and events back to the caller.
266
+ * Returns `Promise<void>` once the harness has accepted the approval
267
+ * (the parked PreToolUse hook resolved on Claude; the resume call was
268
+ * dispatched on Mastra). Post-resume events flow through the SAME
269
+ * `ChatStreamResult.eventStream` returned by the most recent
270
+ * {@link stream} call on the thread.
238
271
  *
239
272
  * @param agentId - ID of the agent.
240
273
  * @param threadId - ID of the conversation thread (needed for harness-internal state lookup).
241
274
  * @param toolCallId - ID of the tool call to approve.
242
275
  */
243
- approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<ChatStreamResult>;
276
+ approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
244
277
  /**
245
- * Decline a pending tool call. The harness resumes the stream with the
246
- * model acknowledging the decline and potentially suggesting alternatives.
278
+ * Decline a pending tool call. The harness resumes the agentic loop with
279
+ * the model acknowledging the decline and potentially suggesting
280
+ * alternatives.
247
281
  *
248
- * Returns a `ChatStreamResult` containing the continuation stream the harness
249
- * cancels the pending tool call, generates the model's acknowledgement response,
250
- * and streams both the text and events back to the caller.
282
+ * Returns `Promise<void>` once the harness has accepted the decline.
283
+ * Post-resume events flow through the SAME `ChatStreamResult.eventStream`
284
+ * returned by the most recent {@link stream} call on the thread.
251
285
  *
252
286
  * @param agentId - ID of the agent.
253
287
  * @param threadId - ID of the conversation thread.
254
288
  * @param toolCallId - ID of the tool call to decline.
255
289
  */
256
- declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<ChatStreamResult>;
290
+ declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
257
291
  /**
258
292
  * Retrieve message history for a thread.
259
293
  *
294
+ * MUST populate `Message.createdAt` on every returned message. MUST return
295
+ * messages sorted ascending by `createdAt`.
296
+ *
260
297
  * @param agentId - ID of the agent.
261
298
  * @param threadId - ID of the conversation thread.
262
- * @returns All messages in chronological order (ascending by creation time).
299
+ * @returns All messages in chronological order (ascending by `createdAt`).
263
300
  */
264
301
  getMessages(agentId: string, threadId: string): Promise<Message[]>;
265
302
  /**
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Buffered async-generator wrapper used by `AgentHarness` implementations to
3
+ * deliver `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`.
4
+ *
5
+ * The buffer holds events pushed before the consumer starts iterating, so the
6
+ * harness can begin populating the sink eagerly without coordinating with the
7
+ * consumer's `for await` loop. Events pushed after iteration starts unblock
8
+ * the awaiting consumer via an internal Promise waiter.
9
+ *
10
+ * # Single-iteration invariant
11
+ *
12
+ * `generator()` may be called **at most once** per sink. Sinks are stateful
13
+ * (single waiter slot, single buffer); two iterators on the same sink race on
14
+ * both, so two iterators is always a bug. Calling `generator()` a second time
15
+ * throws — the alternative (silently allowing the second call to share the
16
+ * buffer / waiter with the first) produced the deadlock-class bug catalogued
17
+ * in [issue #529](https://github.com/forcedotcom/agentic-dx/issues/529)
18
+ * Finding 4. Surface failures at construction-time, not at deadlock-time.
19
+ *
20
+ * # `isEnded()`
21
+ *
22
+ * Exposes the closed state for routing logic that needs to skip pushes onto
23
+ * a sink that's already been retired. Routing for a closed sink should
24
+ * silently no-op with a debug log rather than throw — the late event is
25
+ * upstream noise, not a programmer error.
26
+ *
27
+ * Exported from the public `index.ts` so third-party `AgentHarness`
28
+ * implementations in sibling packages can reuse it.
29
+ */
30
+ export declare class GenSink<T> {
31
+ private readonly buffered;
32
+ private ended;
33
+ private waiter;
34
+ private generated;
35
+ push(event: T): void;
36
+ end(): void;
37
+ isEnded(): boolean;
38
+ generator(): AsyncGenerator<T, void>;
39
+ private iterate;
40
+ private wake;
41
+ }
@@ -0,0 +1,88 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ /**
6
+ * Buffered async-generator wrapper used by `AgentHarness` implementations to
7
+ * deliver `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`.
8
+ *
9
+ * The buffer holds events pushed before the consumer starts iterating, so the
10
+ * harness can begin populating the sink eagerly without coordinating with the
11
+ * consumer's `for await` loop. Events pushed after iteration starts unblock
12
+ * the awaiting consumer via an internal Promise waiter.
13
+ *
14
+ * # Single-iteration invariant
15
+ *
16
+ * `generator()` may be called **at most once** per sink. Sinks are stateful
17
+ * (single waiter slot, single buffer); two iterators on the same sink race on
18
+ * both, so two iterators is always a bug. Calling `generator()` a second time
19
+ * throws — the alternative (silently allowing the second call to share the
20
+ * buffer / waiter with the first) produced the deadlock-class bug catalogued
21
+ * in [issue #529](https://github.com/forcedotcom/agentic-dx/issues/529)
22
+ * Finding 4. Surface failures at construction-time, not at deadlock-time.
23
+ *
24
+ * # `isEnded()`
25
+ *
26
+ * Exposes the closed state for routing logic that needs to skip pushes onto
27
+ * a sink that's already been retired. Routing for a closed sink should
28
+ * silently no-op with a debug log rather than throw — the late event is
29
+ * upstream noise, not a programmer error.
30
+ *
31
+ * Exported from the public `index.ts` so third-party `AgentHarness`
32
+ * implementations in sibling packages can reuse it.
33
+ */
34
+ export class GenSink {
35
+ buffered = [];
36
+ ended = false;
37
+ waiter = null;
38
+ generated = false;
39
+ push(event) {
40
+ if (this.ended)
41
+ return;
42
+ this.buffered.push(event);
43
+ this.wake();
44
+ }
45
+ end() {
46
+ if (this.ended)
47
+ return;
48
+ this.ended = true;
49
+ this.wake();
50
+ }
51
+ isEnded() {
52
+ return this.ended;
53
+ }
54
+ generator() {
55
+ // Synchronous guard. Must live outside the async generator body —
56
+ // async generators are lazy (the body does not execute until the
57
+ // first `next()`), so a `throw` inside the body would not surface
58
+ // at the second `generator()` call site as expected.
59
+ if (this.generated) {
60
+ throw new Error('GenSink.generator(): sinks are single-iteration. ' +
61
+ 'Two iterators on one sink race on the buffer and waiter slot. ' +
62
+ 'See https://github.com/forcedotcom/agentic-dx/issues/529 (Finding 4).');
63
+ }
64
+ this.generated = true;
65
+ return this.iterate();
66
+ }
67
+ async *iterate() {
68
+ while (true) {
69
+ if (this.buffered.length > 0) {
70
+ yield this.buffered.shift();
71
+ continue;
72
+ }
73
+ if (this.ended)
74
+ return;
75
+ await new Promise((resolve) => {
76
+ this.waiter = { resolve };
77
+ });
78
+ }
79
+ }
80
+ wake() {
81
+ const w = this.waiter;
82
+ if (w) {
83
+ this.waiter = null;
84
+ w.resolve();
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=gen-sink.js.map
@@ -1,6 +1,6 @@
1
1
  import type { ToolDefinition } from '../types/tools.js';
2
2
  import type { MCPConfiguration } from '../mcp-config.js';
3
- import type { JSONWebToken, ModelName } from '@salesforce/llm-gateway-sdk';
3
+ import type { JSONWebToken, Model, ModelName } from '@salesforce/llm-gateway-sdk';
4
4
  /**
5
5
  * Configuration for an agent's behavior and capabilities.
6
6
  * This excludes identity; `agentId` is handled separately.
@@ -14,8 +14,15 @@ export type AgentConfig = {
14
14
  * - Otherwise, use the default org configured on the machine.
15
15
  */
16
16
  orgAlias?: string;
17
- /** The model to use for this agent. */
18
- modelId?: ModelName;
17
+ /**
18
+ * The model to use for this agent.
19
+ *
20
+ * Accepts either a {@link ModelName} enum value (the typical case for in-tree models) or a
21
+ * pre-built {@link Model} instance. The instance form lets consumers opt into a Claude
22
+ * variant published on the gateway before the SDK has been updated — see
23
+ * `createClaudeModel(gatewayId, overrides)` from `@salesforce/llm-gateway-sdk`.
24
+ */
25
+ modelId?: ModelName | Model;
19
26
  /** Human-readable name for the agent. */
20
27
  name?: string;
21
28
  /** Description of the agent's purpose. ACP/OASF-ready metadata. */
@@ -2,3 +2,4 @@ export type { AgentHarness, WithAgentConfig, ConfigOf } from './agent-harness.js
2
2
  export type { HarnessFactory } from './harness-factory.js';
3
3
  export type { AgentConfig, StreamOptions } from './harness-config.js';
4
4
  export { toHarnessConfig, DEFAULT_MAX_STEPS } from './harness-config.js';
5
+ export { GenSink } from './gen-sink.js';
@@ -3,4 +3,5 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  export { toHarnessConfig, DEFAULT_MAX_STEPS } from './harness-config.js';
6
+ export { GenSink } from './gen-sink.js';
6
7
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Harness-implementation public surface.
3
+ *
4
+ * This module is reachable as `@salesforce/sfdx-agent-sdk/harness` —
5
+ * **never** import directly from `dist/` or via deep relative paths.
6
+ *
7
+ * # When to import from here
8
+ *
9
+ * Use this surface when implementing an `AgentHarness` (e.g.
10
+ * `@salesforce/sfdx-agent-harness-claude`,
11
+ * `@salesforce/sfdx-agent-harness-mastra`). It re-exports both the
12
+ * harness contract types (`AgentHarness`, `HarnessFactory`) and the
13
+ * harness-implementation helpers consumer applications must not touch
14
+ * (`HarnessBusOwner`, `GenSink`, `lowerStreamInput`,
15
+ * `SUPPORTED_PROTOCOL_VERSIONS`).
16
+ *
17
+ * # Why this is a separate subpath
18
+ *
19
+ * Consumer applications (anything that calls `createAgentManager` and
20
+ * iterates `ChatStreamResult`) import from the bare specifier:
21
+ *
22
+ * import { createAgentManager, Agent } from '@salesforce/sfdx-agent-sdk';
23
+ *
24
+ * Harness implementations import from the subpath:
25
+ *
26
+ * import { HarnessBusOwner, GenSink } from '@salesforce/sfdx-agent-sdk/harness';
27
+ *
28
+ * The split makes the boundary mechanical: a consumer who tries to import
29
+ * `HarnessBusOwner` from the bare specifier gets an immediate
30
+ * "not exported" error from the bundler / TS resolver, instead of
31
+ * accidentally reaching for a primitive they shouldn't be using.
32
+ *
33
+ * Type-only references to the contract interface (`AgentHarness`,
34
+ * `HarnessFactory`) ALSO appear on the bare specifier so consumer code
35
+ * that needs `AgentManager<H extends AgentHarness>` typing still works
36
+ * without a subpath import — but their runtime / constructible
37
+ * counterparts live only here.
38
+ *
39
+ * Symbols that consumers and harnesses both need (`AgentConfig`,
40
+ * `StreamOptions`, `DEFAULT_MAX_STEPS`, `resolveToolApprovalMode`,
41
+ * `ChatEvent` types, `Message` types, `MCPConfiguration`, etc.) stay on
42
+ * the bare specifier. The split is "harness-only" vs. "consumer-AND-harness",
43
+ * not "harness vs. consumer."
44
+ */
45
+ export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './index.js';
46
+ export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
47
+ export { HarnessBusOwner } from './harness-bus-owner.js';
48
+ export { lowerStreamInput, type InputMessagePart } from './stream-input.js';
49
+ export { GenSink } from './gen-sink.js';
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
6
+ // ── Harness-implementation helpers ──────────────────────────────────
7
+ export { HarnessBusOwner } from './harness-bus-owner.js';
8
+ export { lowerStreamInput } from './stream-input.js';
9
+ export { GenSink } from './gen-sink.js';
10
+ //# sourceMappingURL=public.js.map
package/dist/index.d.ts CHANGED
@@ -1,21 +1,19 @@
1
1
  export type { Message, MessagePart, ImagePart, FilePart } from './types/messages.js';
2
2
  export type { ChatEvent, StartEvent, TextDeltaEvent, ReasoningDeltaEvent, ToolCallEvent, ToolApprovalRequestEvent, ToolResultEvent, StepStartEvent, StepFinishEvent, ErrorEvent, FinishEvent, ChatStreamResult, } from './types/events.js';
3
3
  export type { ToolDefinition, ToolCallInfo, ToolResultInfo } from './types/tools.js';
4
- export type { FinishReason, UsageMetadata } from './types/usage.js';
4
+ export type { ContextUsage, FinishReason, UsageMetadata } from './types/usage.js';
5
5
  export type { AgentConfig, HarnessAgentConfig, StreamOptions, ToolApprovalMode } from './harness/harness-config.js';
6
6
  export { DEFAULT_MAX_STEPS, resolveToolApprovalMode } from './harness/harness-config.js';
7
7
  export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, McpServerErrorCategory, McpServerErrorDetail, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
8
8
  export { McpServerStatus } from './mcp-config.js';
9
- export { ModelName } from '@salesforce/llm-gateway-sdk';
9
+ export { Model, ModelName, createClaudeModel } from '@salesforce/llm-gateway-sdk';
10
+ export type { ClaudeModelOverrides } from '@salesforce/llm-gateway-sdk';
10
11
  export { inferSfApiEnv, SfApiEnv } from '@salesforce/agentic-common';
11
12
  export { type AgentManager, type RestoreFailure, createAgentManager } from './agent-manager.js';
12
13
  export { type Agent } from './agent.js';
13
14
  export { type ChatSession, type ChatOptions } from './chat-session.js';
14
15
  export type { AgentConnectivityResolver, ResolvedConnectivity } from './agent-connectivity-resolver.js';
15
16
  export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './harness/index.js';
16
- export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
17
- export { HarnessBusOwner } from './harness/harness-bus-owner.js';
18
- export { lowerStreamInput, type InputMessagePart } from './harness/stream-input.js';
19
17
  export { AgentSDKError, AgentSDKErrorType } from './errors.js';
20
18
  export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, McpServerStatusChangedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
21
19
  export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
package/dist/index.js CHANGED
@@ -4,15 +4,12 @@
4
4
  */
5
5
  export { DEFAULT_MAX_STEPS, resolveToolApprovalMode } from './harness/harness-config.js';
6
6
  export { McpServerStatus } from './mcp-config.js';
7
- export { ModelName } from '@salesforce/llm-gateway-sdk';
7
+ export { Model, ModelName, createClaudeModel } from '@salesforce/llm-gateway-sdk';
8
8
  export { inferSfApiEnv, SfApiEnv } from '@salesforce/agentic-common';
9
9
  // ── Agent Layer ─────────────────────────────────────────────────────
10
10
  export { createAgentManager } from './agent-manager.js';
11
11
  export {} from './agent.js';
12
12
  export {} from './chat-session.js';
13
- export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
14
- export { HarnessBusOwner } from './harness/harness-bus-owner.js';
15
- export { lowerStreamInput } from './harness/stream-input.js';
16
13
  // ── Errors ───────────────────────────────────────────────────────────
17
14
  export { AgentSDKError, AgentSDKErrorType } from './errors.js';
18
15
  // ── MCP Auth ────────────────────────────────────────────────────────
@@ -65,6 +65,32 @@ export type MCPRemoteServerConfig = {
65
65
  enabled?: boolean;
66
66
  /** Timeout in milliseconds for individual requests to the server. */
67
67
  timeout?: number;
68
+ /**
69
+ * Transport-level reconnection tuning for HTTP MCP servers. Forwarded to
70
+ * the underlying SDK transport (`@modelcontextprotocol/sdk`'s
71
+ * `StreamableHTTPClientTransport` on the Claude harness, and the
72
+ * equivalent plumb-through on `@mastra/mcp`'s `HttpServerDefinition`).
73
+ *
74
+ * Each field is optional. The harness mappers merge unspecified fields
75
+ * with the MCP SDK's built-in defaults (`maxRetries: 2`,
76
+ * `initialReconnectionDelay: 1000`, `maxReconnectionDelay: 30000`,
77
+ * `reconnectionDelayGrowFactor: 1.5`) so a partial override leaves the
78
+ * other fields at their defaults rather than zeroing them out — the
79
+ * underlying transport replaces the entire defaults object when
80
+ * `reconnectionOptions` is set.
81
+ *
82
+ * No-op for stdio servers — only `MCPRemoteServerConfig` carries it.
83
+ */
84
+ reconnectionOptions?: {
85
+ /** Maximum number of reconnection attempts before giving up. Default `2`. */
86
+ maxRetries?: number;
87
+ /** Initial backoff between reconnection attempts in milliseconds. Default `1000`. */
88
+ initialReconnectionDelay?: number;
89
+ /** Maximum backoff between reconnection attempts in milliseconds. Default `30000`. */
90
+ maxReconnectionDelay?: number;
91
+ /** Factor by which the reconnection delay grows after each attempt. Default `1.5`. */
92
+ reconnectionDelayGrowFactor?: number;
93
+ };
68
94
  /**
69
95
  * Opt the server's tool surface out of the active runtime's tool-search
70
96
  * deferral. See {@link MCPStdioServerConfig.alwaysLoad}.
@@ -8,7 +8,7 @@ import type { FinishReason, UsageMetadata } from './usage.js';
8
8
  * convention, with the addition of `tool-approval-request` for human-in-the-loop
9
9
  * tool approval flows.
10
10
  */
11
- export type ChatEvent = StartEvent | TextDeltaEvent | ReasoningDeltaEvent | ToolCallEvent | ToolApprovalRequestEvent | ToolResultEvent | StepStartEvent | StepFinishEvent | ErrorEvent | FinishEvent | UnmappedChunkEvent;
11
+ export type ChatEvent = StartEvent | TextDeltaEvent | ReasoningDeltaEvent | ToolCallEvent | ToolApprovalRequestEvent | ToolResultEvent | StepStartEvent | StepFinishEvent | ErrorEvent | FinishEvent;
12
12
  /**
13
13
  * The stream has begun. Symmetric counterpart to {@link FinishEvent}.
14
14
  *
@@ -155,19 +155,6 @@ export type ErrorEvent = {
155
155
  /** Machine-readable error code (e.g., `'insufficient-tokens'`). */
156
156
  code?: string;
157
157
  };
158
- /**
159
- * A stream chunk from the underlying harness that has no `ChatEvent` counterpart.
160
- *
161
- * Returned instead of silently discarding the chunk, so consumers can log or
162
- * monitor unhandled harness events for observability.
163
- */
164
- export type UnmappedChunkEvent = {
165
- type: 'unmapped-chunk';
166
- /** The original harness chunk type string (e.g., `'tool-call-suspended'`, `'raw'`). */
167
- chunkType: string;
168
- /** The raw chunk object, preserved for diagnostic logging. */
169
- rawChunk: unknown;
170
- };
171
158
  /** The entire stream has completed. */
172
159
  export type FinishEvent = {
173
160
  type: 'finish';
@@ -24,7 +24,18 @@ export type Message = {
24
24
  role: MessageRole;
25
25
  /** Message content — plain text or structured parts. */
26
26
  content: string | MessagePart[];
27
- /** Optional timestamp of when the message was created. */
27
+ /**
28
+ * Timestamp of when the message was created. **Always populated** on
29
+ * messages returned from `ChatSession.getMessageHistory()`. **Optional on
30
+ * write** — consumers constructing `Message` for `ChatSession.addContext()`
31
+ * may omit it; the SDK backfills the current time before forwarding to
32
+ * the harness, so the on-read contract still holds.
33
+ *
34
+ * The read-side guarantee lives on `AgentHarness.getMessages` — see its
35
+ * JSDoc for the contract every harness implementation upholds (populated
36
+ * `createdAt` on every returned message; array sorted ascending by
37
+ * `createdAt`). The SDK passes the harness's output through unchanged.
38
+ */
28
39
  createdAt?: Date;
29
40
  };
30
41
  /**
@@ -16,6 +16,71 @@ export type UsageMetadata = {
16
16
  /** Input tokens written to the provider cache during this interaction. */
17
17
  cacheWriteInputTokens?: number;
18
18
  };
19
+ /**
20
+ * Snapshot of how much of the model's context window the most recent
21
+ * turn used. Returned by {@link ChatSession.getContextUsage}.
22
+ *
23
+ * Consumers use this to decide when to call `compactThread()`, switch to a
24
+ * smaller model, or warn the user as the conversation approaches the
25
+ * model's context limit.
26
+ *
27
+ * `usage` carries the **last per-step** reading from the model —
28
+ * specifically the `usage` from the latest `step-finish` event whose `usage`
29
+ * was defined. This is the size of the prompt the model saw on its last
30
+ * invocation, which is the right "how full is my context" reading. This is
31
+ * **not** the per-turn billing aggregate (which sums steps and double-counts
32
+ * persistent context). For per-turn billing totals, subscribe to
33
+ * `chat-stream-completed` telemetry instead.
34
+ *
35
+ * Field shapes:
36
+ *
37
+ * - `usage` is always populated. Pre-first-turn (or post-`clearHistory()`)
38
+ * it is the empty object `{}` — i.e., a `UsageMetadata` whose token fields
39
+ * are all `undefined` — making "no reading yet" indistinguishable from
40
+ * "harness reported every field as undefined."
41
+ * - `contextWindow` is always populated, contractually. Every `Model`
42
+ * reachable via `Agent.llmGatewayClient.getModel()` must publish a
43
+ * `contextWindow`; see the `sfdx-agent-sdk` ARCHITECTURE.md Critical
44
+ * Invariant on this and issue #507.
45
+ * - `usedFraction` is `undefined` iff every input-bearing field on the
46
+ * latest reading (`inputTokens`, `cachedInputTokens`, `cacheWriteInputTokens`)
47
+ * is `undefined` — the only honest answer when we have no input-side
48
+ * reading to divide. The denominator-numerator sums all three because
49
+ * cached prompt tokens occupy real space in the context window (see the
50
+ * field-level doc on `usedFraction` for the Bedrock-Claude rationale).
51
+ * Consumers who want zero-on-empty UX can collapse with `usedFraction ?? 0`.
52
+ */
53
+ export type ContextUsage = {
54
+ /**
55
+ * Last per-step usage reading observed on this session. Pre-first-turn
56
+ * and immediately after `clearHistory()` this is `{}` (every token field
57
+ * undefined).
58
+ */
59
+ usage: UsageMetadata;
60
+ /**
61
+ * The model's total context-window size in tokens. Read live at call
62
+ * time from the agent's currently-bound `LLMGatewayClient`, so it stays
63
+ * correct across `Agent.updateAgentConfig()` model swaps.
64
+ */
65
+ contextWindow: number;
66
+ /**
67
+ * `(usage.inputTokens + usage.cachedInputTokens + usage.cacheWriteInputTokens) /
68
+ * contextWindow`, clamped to `[0, 1]`. The denominator-numerator includes
69
+ * cached prompt tokens because they are real tokens occupying the model's
70
+ * context window — Bedrock-Claude's `message_delta.usage` reports only the
71
+ * incremental `inputTokens` per delta, with the bulk of the prompt riding
72
+ * on `cachedInputTokens` / `cacheWriteInputTokens`. Counting only
73
+ * `inputTokens` would underreport "how full" by orders of magnitude on
74
+ * cache-hit paths. Mastra is unaffected because it does not populate the
75
+ * cache fields, so the sum collapses to `inputTokens` alone.
76
+ *
77
+ * `undefined` when ALL three input-bearing fields are missing on the
78
+ * latest reading (pre-first-turn, post-`clearHistory()`, or when a
79
+ * harness emits a reading without any input-side counts). Consumers
80
+ * wanting zero-on-empty: `usedFraction ?? 0`.
81
+ */
82
+ usedFraction: number | undefined;
83
+ };
19
84
  /**
20
85
  * Reason the model stopped generating.
21
86
  * Aligned with AI SDK V3's unified finish-reason set; harnesses normalize provider-specific
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
12
  },
13
+ "./harness": {
14
+ "types": "./dist/harness/public.d.ts",
15
+ "default": "./dist/harness/public.js"
16
+ },
13
17
  "./package.json": "./package.json"
14
18
  },
15
19
  "scripts": {
@@ -35,13 +39,13 @@
35
39
  "LICENSE.txt"
36
40
  ],
37
41
  "dependencies": {
38
- "@salesforce/agentic-common": "0.6.0",
39
- "@salesforce/llm-gateway-sdk": "0.10.0"
42
+ "@salesforce/agentic-common": "0.8.0",
43
+ "@salesforce/llm-gateway-sdk": "0.12.0"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@eslint/js": "^10.0.1",
43
- "@salesforce/sfdx-agent-harness-claude": "0.10.0",
44
- "@salesforce/sfdx-agent-harness-mastra": "0.13.0",
47
+ "@salesforce/sfdx-agent-harness-claude": "0.12.0",
48
+ "@salesforce/sfdx-agent-harness-mastra": "0.15.0",
45
49
  "@types/node": "^22.19.17",
46
50
  "@vitest/coverage-istanbul": "^4.1.7",
47
51
  "@vitest/eslint-plugin": "^1.6.17",