@salesforce/sfdx-agent-sdk 0.22.0 → 0.24.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 +13 -0
- package/README.md +324 -53
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
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.24.0] - 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Chores
|
|
9
|
+
- **deps-dev**: bump @types/node from 22.19.21 to 22.20.0 in the dev-dependencies group ([#613](https://github.com/forcedotcom/agentic-dx/pull/613))
|
|
10
|
+
|
|
11
|
+
## [0.23.0] - 2026-06-22
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- **agentic-common,harnesses**: route HTTPS_PROXY via injectable undici Dispatcher @W-23121596 ([#610](https://github.com/forcedotcom/agentic-dx/pull/610))
|
|
15
|
+
|
|
16
|
+
### Docs
|
|
17
|
+
- align SDK + agentic-common docs with current public surface (post-PR-#607) ([#608](https://github.com/forcedotcom/agentic-dx/pull/608))
|
|
18
|
+
|
|
6
19
|
## [0.22.0] - 2026-06-19
|
|
7
20
|
|
|
8
21
|
### Features
|
package/README.md
CHANGED
|
@@ -102,17 +102,18 @@ through a typed `manager.extensions` slot. `createAgentManager` infers `H` from
|
|
|
102
102
|
explicitly. The `createAgent` config parameter narrows automatically when the harness brands itself with
|
|
103
103
|
`WithAgentConfig` — see "Harness Extensibility" below.
|
|
104
104
|
|
|
105
|
-
| Property / Method
|
|
106
|
-
|
|
|
107
|
-
| `extensions`
|
|
108
|
-
| `createAgent`
|
|
109
|
-
| `getAgent`
|
|
110
|
-
| `getAgentIds`
|
|
111
|
-
| `destroyAgent`
|
|
112
|
-
| `shutdown`
|
|
113
|
-
| `onTelemetry`
|
|
114
|
-
| `onLog`
|
|
115
|
-
| `
|
|
105
|
+
| Property / Method | Signature | Description |
|
|
106
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
107
|
+
| `extensions` | `H['extensions']` | Harness-specific extensions namespace (read-only). Re-exposes the harness's `extensions` slot typed off `H`. Per-agent accessors take the agent id as their first argument. The SDK never reads or interprets this — see "Harness Extensibility" below. |
|
|
108
|
+
| `createAgent` | `(projectRoot: string, config?: ConfigOf<H> & { agentId?: string }, options?: { abortSignal?: AbortSignal }) => Promise<Agent<H>>` | Create and register a new agent and persist its identity triple. `projectRoot` must be an existing directory. If `agentId` is omitted a UUID is generated. The config type is inferred from the harness — see `ConfigOf<H>`. |
|
|
109
|
+
| `getAgent` | `(agentId: string) => Agent<H>` | Retrieve a live agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for unknown ids and for ids that are only present in `getRestoreFailures()`. |
|
|
110
|
+
| `getAgentIds` | `() => string[]` | List all live agent IDs (successful + successfully restored). Failed-restore agents are not included — query `getRestoreFailures()` separately. |
|
|
111
|
+
| `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent, remove its identity record from disk, and clear any matching `getRestoreFailures()` entry. Failed-restore-only ids are accepted (no harness call made). |
|
|
112
|
+
| `shutdown` | `() => Promise<void>` | Destroy all live agents and shut down the harness. Identity files survive (that's the whole point) — restart `createAgentManager` over the same root to bring them back. |
|
|
113
|
+
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
|
|
114
|
+
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. Bridge this into your host logger to observe restore-failure events + soft-skip warnings. |
|
|
115
|
+
| `onWireCommunication` | `(callback: WireCommunicationEventCallback) => Unsubscribe` | Subscribe to wire-level communication events from the harness. Opt-in diagnostic channel that surfaces outbound LLM requests, responses, and harness-specific monitoring metadata. Subscriber-gated end-to-end — harnesses pay no cost when nobody listens. See "Wire-Communication Events" below for the event-shape catalog and the harness-asymmetric coverage (Mastra emits per-call request/response pairs; Claude emits a per-stream pointer to a debug log file). |
|
|
116
|
+
| `getRestoreFailures` | `() => RestoreFailure[]` | Snapshot of agents the SDK could not restore on this boot. Each entry carries the persisted `{ agentId, projectRoot, config }` plus the underlying error. |
|
|
116
117
|
|
|
117
118
|
#### `RestoreFailure`
|
|
118
119
|
|
|
@@ -135,24 +136,24 @@ A configured AI agent. Factory for chat sessions. The optional `H` type paramete
|
|
|
135
136
|
— harness-specific features are reached through `manager.extensions`, not `agent.extensions`. The default `AgentHarness`
|
|
136
137
|
keeps unparameterized call sites working.
|
|
137
138
|
|
|
138
|
-
| Method
|
|
139
|
-
|
|
|
140
|
-
| `getId`
|
|
141
|
-
| `getProjectRoot`
|
|
142
|
-
| `
|
|
143
|
-
| `getAgentConfig`
|
|
144
|
-
| `getMcpServerInfo`
|
|
145
|
-
| `reconnectMcpServer`
|
|
146
|
-
| `updateAgentConfig`
|
|
147
|
-
| `createChatSession`
|
|
148
|
-
| `getChatSession`
|
|
149
|
-
| `getChatSessionIds`
|
|
150
|
-
| `destroyChatSession`
|
|
151
|
-
| `cloneChatSession`
|
|
152
|
-
| `compactChatSession`
|
|
153
|
-
| `destroy`
|
|
154
|
-
| `onTelemetry`
|
|
155
|
-
| `onLog`
|
|
139
|
+
| Method | Signature | Description |
|
|
140
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `getId` | `() => string` | Agent identifier. |
|
|
142
|
+
| `getProjectRoot` | `() => string` | Absolute project path. |
|
|
143
|
+
| `getOrgConnection` | `() => OrgConnection \| undefined` | The Salesforce `OrgConnection` resolved for this agent (or `undefined` if the connectivity resolver omitted it — non-Salesforce hosts on the BYOK / api-key path return `undefined`). |
|
|
144
|
+
| `getAgentConfig` | `() => AgentConfig` | Current configuration (shallow copy). |
|
|
145
|
+
| `getMcpServerInfo` | `() => McpServerInfo[]` | MCP server status and discovered tools. |
|
|
146
|
+
| `reconnectMcpServer` | `(serverName: string) => Promise<void>` | Recover one MCP server without recycling the agent. Semantics vary by harness — observe via `getMcpServerInfo()` and discovery telemetry. |
|
|
147
|
+
| `updateAgentConfig` | `(config?: AgentConfig, options?: { abortSignal?: AbortSignal; forceResolve?: boolean }) => Promise<void>` | Merge new config into the live agent. Pass `forceResolve: true` to re-run the connectivity resolver even when the partial config doesn't include `orgAlias` or `modelId` — for consumer-side state changes (BYOK toggle, feature-id flip, rate-limit gate) the resolver reads but the SDK can't observe directly. |
|
|
148
|
+
| `createChatSession` | `() => Promise<ChatSession>` | Open a new conversation thread. |
|
|
149
|
+
| `getChatSession` | `(sessionId: string) => ChatSession` | Retrieve a session. Throws `AgentSDKError` (`CHAT_SESSION_NOT_FOUND`). |
|
|
150
|
+
| `getChatSessionIds` | `() => string[]` | List active session IDs. |
|
|
151
|
+
| `destroyChatSession` | `(sessionId: string) => Promise<void>` | Destroy a session and its history. |
|
|
152
|
+
| `cloneChatSession` | `(sourceSessionId: string) => Promise<ChatSession>` | Clone a session with its message history. |
|
|
153
|
+
| `compactChatSession` | `(sessionId: string) => Promise<ChatSession>` | Compact a session into a summarized new session. |
|
|
154
|
+
| `destroy` | `() => Promise<void>` | Destroy the agent and all its sessions. |
|
|
155
|
+
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry scoped to this agent (and its sessions). |
|
|
156
|
+
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to logs scoped to this agent (and its sessions). |
|
|
156
157
|
|
|
157
158
|
### `ChatSession`
|
|
158
159
|
|
|
@@ -211,6 +212,22 @@ Discriminated union (`event.type`) of streaming events:
|
|
|
211
212
|
> with `chunkType` and `rawChunk` in the record's `context`. Subscribe via `manager.onLog` (or `agent.onLog` /
|
|
212
213
|
> `session.onLog`) at debug level to observe these. Production consumers do not need to filter for unrecognized chunks.
|
|
213
214
|
|
|
215
|
+
#### Per-variant event types
|
|
216
|
+
|
|
217
|
+
Every `ChatEvent` variant is exported as a named type so consumers can write narrowed callbacks without re-declaring the
|
|
218
|
+
shape: `StartEvent`, `TextDeltaEvent`, `ReasoningDeltaEvent`, `ToolCallEvent`, `ToolCallDeltaEvent`,
|
|
219
|
+
`ToolApprovalRequestEvent`, `ToolResultEvent`, `ToolProgressEvent`, `StepStartEvent`, `StepFinishEvent`, `ErrorEvent`,
|
|
220
|
+
`FinishEvent`. Useful when factoring per-event handlers out of a `for await` loop:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import type { ToolApprovalRequestEvent } from '@salesforce/sfdx-agent-sdk';
|
|
224
|
+
|
|
225
|
+
function onApprovalRequest(event: ToolApprovalRequestEvent): Promise<boolean> {
|
|
226
|
+
// typed access to event.toolCall, event.annotations, event.serverName
|
|
227
|
+
return promptUser(event);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
214
231
|
### Configuration Types
|
|
215
232
|
|
|
216
233
|
#### `AgentConfig`
|
|
@@ -218,7 +235,7 @@ Discriminated union (`event.type`) of streaming events:
|
|
|
218
235
|
| Field | Type | Description |
|
|
219
236
|
| --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
220
237
|
| `orgAlias?` | `string` | Salesforce org alias or username. Falls back to project/default org. |
|
|
221
|
-
| `modelId?` | `ModelName \| Model` | LLM model selector. Pass a `ModelName` enum value for an in-tree model (e.g. `'llmgateway__OpenAIGPT5'`), or a pre-built `Model` instance to opt into a Bedrock-Anthropic Claude variant the SDK has not yet released — see `createClaudeModel(gatewayId, overrides)`
|
|
238
|
+
| `modelId?` | `ModelName \| Model` | LLM model selector. Pass a `ModelName` enum value for an in-tree model (e.g. `'llmgateway__OpenAIGPT5'`), or a pre-built `Model` instance to opt into a Bedrock-Anthropic Claude variant the SDK has not yet released — see `createClaudeModel(gatewayId, overrides)` exported from this package. |
|
|
222
239
|
| `name?` | `string` | Human-readable agent name. |
|
|
223
240
|
| `description?` | `string` | Agent purpose description. |
|
|
224
241
|
| `instructions?` | `string` | System instructions for the agent. |
|
|
@@ -229,10 +246,16 @@ Discriminated union (`event.type`) of streaming events:
|
|
|
229
246
|
|
|
230
247
|
#### `StreamOptions`
|
|
231
248
|
|
|
232
|
-
| Field | Type
|
|
233
|
-
| ---------------------- |
|
|
234
|
-
| `abortSignal?` | `AbortSignal`
|
|
235
|
-
| `requireToolApproval?` | `boolean \|
|
|
249
|
+
| Field | Type | Description |
|
|
250
|
+
| ---------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
251
|
+
| `abortSignal?` | `AbortSignal` | Abort the streaming operation. |
|
|
252
|
+
| `requireToolApproval?` | `boolean \| ToolApprovalMode` | Gates native tool execution behind a `tool-approval-request` event. `true` / `'serial'` (the default) emits one approval per stream — safe for any iterator pattern. `'batch'` opts into parallel-approval UX: when the model emits parallel `tool_use` blocks, all approvals surface on the same stream so the consumer can render a batch approval card. **`'batch'` requires Pattern A iterators** (collect-all-approvals-then-settle); a `break`-on-first-approval loop will hang. See "Tool Approval Flow" below. |
|
|
253
|
+
| `maxSteps?` | `number` | Maximum number of LLM call steps the agent may take per `stream()` invocation. Each step is one LLM call (which may produce text, tool calls, or both). Must be `>= 1`. Defaults to `DEFAULT_MAX_STEPS` (1024) — high enough to be effectively unlimited for real tasks; the practical ceiling is the context window and cost. The constant is exported so consumers and harness authors share one source of truth. |
|
|
254
|
+
|
|
255
|
+
`ToolApprovalMode` is the exported type alias `'serial' | 'batch'` — useful when typing a settings struct that drives
|
|
256
|
+
`requireToolApproval`. Pair with `resolveToolApprovalMode(boolean | ToolApprovalMode | undefined)` (also exported) to
|
|
257
|
+
normalize consumer input the same way the SDK does internally (`undefined` / `false` → `undefined`, `true` → `'serial'`,
|
|
258
|
+
strings pass through, unknown strings throw).
|
|
236
259
|
|
|
237
260
|
#### `MCPConfiguration`
|
|
238
261
|
|
|
@@ -280,13 +303,13 @@ only `MCPRemoteServerConfig` carries it.
|
|
|
280
303
|
|
|
281
304
|
#### `McpServerInfo`
|
|
282
305
|
|
|
283
|
-
| Field | Type
|
|
284
|
-
| -------------- |
|
|
285
|
-
| `name` | `string`
|
|
286
|
-
| `status` | `'connected' \| 'connecting' \| 'disabled' \| 'error' \| 'reconnecting'
|
|
287
|
-
| `tools` | [`McpToolInfo[]`](#mcptoolinfo)
|
|
288
|
-
| `error?` | `string`
|
|
289
|
-
| `errorDetail?` | [`McpServerErrorDetail`](#mcpservererrordetail)
|
|
306
|
+
| Field | Type | Description |
|
|
307
|
+
| -------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `name` | `string` | Server identifier. |
|
|
309
|
+
| `status` | `McpServerStatus` | Connection state — exported enum with the values `'connected' \| 'connecting' \| 'disabled' \| 'error' \| 'reconnecting'`. `'reconnecting'` is reported during an `Agent.reconnectMcpServer(name)` call against a previously-`'connected'` server. |
|
|
310
|
+
| `tools` | [`McpToolInfo[]`](#mcptoolinfo) | Discovered tools (name + metadata). |
|
|
311
|
+
| `error?` | `string` | Sanitized human-readable error message when status is `'error'`. Stack frames and file paths are stripped at the harness boundary. |
|
|
312
|
+
| `errorDetail?` | [`McpServerErrorDetail`](#mcpservererrordetail) | Structured failure projection for programmatic routing (category / code / retriable). Populated when status is `'error'`. |
|
|
290
313
|
|
|
291
314
|
#### `McpServerErrorDetail`
|
|
292
315
|
|
|
@@ -396,6 +419,16 @@ type Message = {
|
|
|
396
419
|
|
|
397
420
|
type MessagePart = TextPart | ReasoningPart | ToolCallPart | ToolResultPart | ImagePart | FilePart;
|
|
398
421
|
|
|
422
|
+
// Plain text and reasoning segments (model chain-of-thought).
|
|
423
|
+
type TextPart = { type: 'text'; text: string };
|
|
424
|
+
type ReasoningPart = { type: 'reasoning'; text: string };
|
|
425
|
+
|
|
426
|
+
// Tool invocation / result segments persisted on `Message.content`. Extend the
|
|
427
|
+
// `ToolCallInfo` / `ToolResultInfo` shapes with a discriminator. They appear in
|
|
428
|
+
// message history; they are NOT valid input on `chat()` / `addContext()`.
|
|
429
|
+
type ToolCallPart = ToolCallInfo & { type: 'tool-call' };
|
|
430
|
+
type ToolResultPart = ToolResultInfo & { type: 'tool-result' };
|
|
431
|
+
|
|
399
432
|
// Multimodal input parts. `data` is base64-encoded bytes with no `data:` URI prefix.
|
|
400
433
|
type ImagePart = { type: 'image'; mimeType: 'image/png' | 'image/jpeg'; data: string; fileName?: string };
|
|
401
434
|
type FilePart = { type: 'file'; mimeType: 'application/pdf'; data: string; fileName?: string };
|
|
@@ -552,6 +585,8 @@ from `AgentSDKErrorType`:
|
|
|
552
585
|
| `MCP_SERVER_NOT_FOUND` | `Agent.reconnectMcpServer()` when the server name is not in the agent's `mcpServers` config |
|
|
553
586
|
| `MODEL_NOT_SUPPORTED_BY_HARNESS` | `AgentManager.createAgent()` / `Agent.updateAgentConfig()` (G8 pre-flight) when the resolved `ModelConnectivityInfo.providerHint` isn't in the harness's `supportedProviderHints`. Surfaces before any harness work runs (no MCP discovery, no subprocess spawn, no language-model construction) so the consumer can branch cleanly on `err.type` and recover without resource cleanup. |
|
|
554
587
|
| `MULTIMODAL_NOT_SUPPORTED` | `ChatSession.chat()` / harness `stream()` when a file fails pre-stream capability validation (unsupported format, too large, or too many files) |
|
|
588
|
+
| `NOT_SUPPORTED` | `ApiKeyConnectivityResolver.resolve()` when the consumer-supplied `getApiKey` returns an empty / nullish value. Surfaces locally so the consumer sees "the resolver returned an empty key" rather than chasing a 401 through provider logs after `Authorization: Bearer ` (no key) lands on the wire. |
|
|
589
|
+
| `TOOL_CALL_NOT_FOUND` | `ChatSession.approveToolCall()` / `declineToolCall()` / `submitToolResult()` when the supplied `toolCallId` doesn't match any pending tool-call request on the current session. Typically indicates a wrong-id, wrong-session, or already-settled call. Both production harnesses (Mastra, Claude) throw this from their per-turn approval coordinator. |
|
|
555
590
|
|
|
556
591
|
```typescript
|
|
557
592
|
import { AgentSDKError, AgentSDKErrorType } from '@salesforce/sfdx-agent-sdk';
|
|
@@ -902,15 +937,104 @@ is what actually replaces the value the model sees.
|
|
|
902
937
|
|
|
903
938
|
### Connectivity Resolution
|
|
904
939
|
|
|
940
|
+
The connectivity surface is the seam between the SDK and whatever-talks-to-the-LLM. It resolves three things per agent:
|
|
941
|
+
which model the harness will use, the wire shape the harness should speak, and how to authenticate each outbound
|
|
942
|
+
request. A bundled default resolver covers the Salesforce-org gateway path; a bundled api-key resolver covers BYOK /
|
|
943
|
+
direct-provider / LLMG-Express; and consumers can implement `AgentConnectivityResolver` directly for anything else.
|
|
944
|
+
|
|
945
|
+
#### `AgentConnectivityResolver`
|
|
946
|
+
|
|
947
|
+
The contract every connectivity resolver implements. `createAgentManager` calls `resolve()` once per agent install
|
|
948
|
+
(`createAgent`, boot-time restore, and `Agent.updateAgentConfig` when `orgAlias` / `modelId` / `forceResolve: true` is
|
|
949
|
+
set), then threads the returned `ResolvedConnectivity` through to the harness.
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
interface AgentConnectivityResolver {
|
|
953
|
+
resolve(projectRoot: string, config: AgentConfig): Promise<ResolvedConnectivity>;
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
The bundled `DefaultAgentConnectivityResolver` (used when `createAgentManager`'s `connectivityResolver` option is unset)
|
|
958
|
+
targets the Salesforce LLM Gateway via a self-refreshing JWT. The class is implementation detail — the public entry
|
|
959
|
+
point is implicit via `createAgentManager`.
|
|
960
|
+
|
|
905
961
|
#### `ResolvedConnectivity`
|
|
906
962
|
|
|
907
|
-
Returned by `AgentConnectivityResolver.resolve()`.
|
|
963
|
+
Returned by `AgentConnectivityResolver.resolve()`. Three fields, two of them optional for non-Salesforce hosts:
|
|
964
|
+
|
|
965
|
+
| Field | Type | Description |
|
|
966
|
+
| ----------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
967
|
+
| `modelConnectivityInfo` | `ModelConnectivityInfo` | The five-field bag the harness reads to make every outbound LLM request. See below. |
|
|
968
|
+
| `orgConnection?` | `OrgConnection` | Authenticated Salesforce org connection carrying identity and env inference. Optional — non-Salesforce hosts (MuleSoft, Commerce Vibes) omit it. |
|
|
969
|
+
| `orgJwt?` | `JSONWebToken` | Self-refreshing JWT for the resolved org. Used for MCP auth injection and any other Salesforce-platform-auth need (identity, future Apex). Optional — non-Salesforce hosts and api-key BYOK hosts that don't need org auth omit it. |
|
|
970
|
+
|
|
971
|
+
#### `ModelConnectivityInfo`
|
|
908
972
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
|
913
|
-
|
|
|
973
|
+
The connectivity facts a harness needs to make an LLM request. Five fields. Auth-kind branching lives inside
|
|
974
|
+
`getHeaders()` — the harness never inspects whether the auth is a JWT, an API key, or anything else.
|
|
975
|
+
|
|
976
|
+
| Field | Type | Description |
|
|
977
|
+
| --------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
978
|
+
| `model` | `Model` | Capability descriptor used for pre-flight multimodal validation and SDK-side context-usage reporting. |
|
|
979
|
+
| `baseUrl` | `string` | Provider-native or gateway base URL (no trailing slash, no path beyond the API root). Harnesses concat the wire-shape-specific path. |
|
|
980
|
+
| `nativeModelId` | `string` | Wire id the provider SDK sets on the request body's `model` field. May differ from `model.name` (e.g. Anthropic-direct expects `claude-sonnet-4-6`; the Salesforce gateway expects `llmgateway__BedrockAnthropicClaude46Sonnet`). |
|
|
981
|
+
| `providerHint` | `ProviderHint` | Wire shape the harness should speak. The manager validates this against the harness's `supportedProviderHints` before any harness work begins; a harness only sees hints it recognizes. |
|
|
982
|
+
| `getHeaders` | `() => Promise<Record<string, string>>` | Returns the FULL header set (Authorization included) for the NEXT outbound request. **Per-call invocation contract.** Mastra harnesses MUST call this on every HTTP call (inside their fetch wrapper); Claude harnesses MUST call this once per subprocess spawn. Caching the returned map across requests is a bug. |
|
|
983
|
+
|
|
984
|
+
`getHeaders()` is allowed to throw — the harness propagates the throw as a stream-time failure, identical to any other
|
|
985
|
+
transport error.
|
|
986
|
+
|
|
987
|
+
#### `ProviderHint`
|
|
988
|
+
|
|
989
|
+
```typescript
|
|
990
|
+
type ProviderHint =
|
|
991
|
+
| 'anthropic' // Anthropic Messages API direct (api.anthropic.com)
|
|
992
|
+
| 'bedrock-anthropic' // Anthropic via AWS Bedrock invoke (Salesforce gateway uses this shape)
|
|
993
|
+
| 'openai' // OpenAI Responses API direct (api.openai.com)
|
|
994
|
+
| 'openai-responses' // OpenAI Responses API via Salesforce gateway pass-through
|
|
995
|
+
| 'openai-compatible' // OpenAI-shaped /v1/chat/completions (LLMG Express, LiteLLM, vLLM, third-party gateways)
|
|
996
|
+
| (string & {}); // Forward-compat tail — harnesses must reject unknown hints
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
The trailing `string & {}` allows resolvers to return a hint for a future provider without a same-day type bump in the
|
|
1000
|
+
SDK. Harnesses that don't recognize a hint MUST throw `AgentSDKError(MODEL_NOT_SUPPORTED_BY_HARNESS)` rather than fall
|
|
1001
|
+
through to a wrong-shape builder.
|
|
1002
|
+
|
|
1003
|
+
#### `ApiKeyConnectivityResolver`
|
|
1004
|
+
|
|
1005
|
+
Bundled api-key resolver covering Anthropic-direct, OpenAI-direct, and any OpenAI-compatible endpoint (LLMG Express,
|
|
1006
|
+
LiteLLM, vLLM, third-party gateways). Pass an instance via `createAgentManager`'s `connectivityResolver` option.
|
|
1007
|
+
|
|
1008
|
+
```typescript
|
|
1009
|
+
import { ApiKeyConnectivityResolver, createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
1010
|
+
|
|
1011
|
+
const manager = await createAgentManager('/path/to/storage', new MastraHarnessFactory(), {
|
|
1012
|
+
connectivityResolver: new ApiKeyConnectivityResolver({
|
|
1013
|
+
getApiKey: () => process.env.LLMG_EXPRESS_API_KEY!,
|
|
1014
|
+
baseUrl: 'https://express-internal/v1',
|
|
1015
|
+
providerHint: 'openai-compatible',
|
|
1016
|
+
}),
|
|
1017
|
+
});
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
##### `ApiKeyConnectivityResolverConfig`
|
|
1021
|
+
|
|
1022
|
+
| Field | Type | Description |
|
|
1023
|
+
| -------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1024
|
+
| `getApiKey` | `() => string \| Promise<string>` | Returns the API key. Re-evaluated on every `resolve()` call, and the resulting `getHeaders()` re-invokes it on every header read so a rotating key source (env var, secret store, BYOK toggle) lands without reconstructing the resolver. |
|
|
1025
|
+
| `baseUrl` | `string` | Fully-qualified provider URL with API version segment (e.g. `https://api.openai.com/v1`, not `https://api.openai.com`). |
|
|
1026
|
+
| `providerHint` | `ProviderHint` | Wire shape. Must be one a harness in the consumer's setup advertises via `HarnessFactory.supportedProviderHints`. |
|
|
1027
|
+
| `nativeModelIdFor?` | `(model: Model) => string` | Optional translator from canonical `model.name` to the endpoint-native model id. Direct-provider endpoints expect their own naming (`claude-sonnet-4-6`); gateway-shaped endpoints accept the canonical `llmgateway__*` id verbatim. Defaults to `model.name`. |
|
|
1028
|
+
| `connectionFactory?` | `OrgConnectionFactory` | Optional. When supplied, the resolver mints an org connection + JWT alongside the api-key bag — the BYOK shape (api-key authenticates the LLM; org JWT authenticates MCP servers / identity). Without it, `orgConnection` and `orgJwt` are omitted from `ResolvedConnectivity`. |
|
|
1029
|
+
|
|
1030
|
+
The resolver throws `AgentSDKError(NOT_SUPPORTED)` when `getApiKey()` returns empty / nullish so consumers see "the
|
|
1031
|
+
resolver returned an empty key" rather than chasing a 401 through provider logs after `Authorization: Bearer ` (no key)
|
|
1032
|
+
lands on the wire.
|
|
1033
|
+
|
|
1034
|
+
`Model.customHeaders` (a vendor-label map like `{ 'anthropic-version': '2023-06-01' }`) flows through to the resolver's
|
|
1035
|
+
header bag. **Reserved auth headers are not overridable**: the resolver spreads `customHeaders` first and writes its own
|
|
1036
|
+
`Authorization` / `Content-Type` last, so a `Model` instance whose `customHeaders` includes an `Authorization` key
|
|
1037
|
+
silently shadows nothing — the resolver wins.
|
|
914
1038
|
|
|
915
1039
|
#### `HarnessAgentConfig`
|
|
916
1040
|
|
|
@@ -932,18 +1056,51 @@ Authorization header already exists. For all other URLs, returns headers unmodif
|
|
|
932
1056
|
|
|
933
1057
|
Returns `true` if the URL matches a Salesforce Hosted MCP Server endpoint (prod, dev, test, perf, stage).
|
|
934
1058
|
|
|
935
|
-
###
|
|
1059
|
+
### `Workspace`
|
|
1060
|
+
|
|
1061
|
+
Convenience helper for validating and normalizing a project path before handing it to `manager.createAgent`. The
|
|
1062
|
+
constructor is private; build one through the static async factory:
|
|
1063
|
+
|
|
1064
|
+
```typescript
|
|
1065
|
+
import { Workspace } from '@salesforce/sfdx-agent-sdk';
|
|
1066
|
+
|
|
1067
|
+
const workspace = await Workspace.create('/path/to/project');
|
|
1068
|
+
const agent = await manager.createAgent(workspace.projectPath, { instructions: '...' });
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
`Workspace.create(projectPath)` resolves the path to an absolute, real (symlink-followed) directory and throws if it
|
|
1072
|
+
doesn't exist or isn't a directory. The returned instance exposes `projectPath: string`. Pass any string to
|
|
1073
|
+
`manager.createAgent` directly if you don't need the validation step — `Workspace` is a convenience, not a requirement.
|
|
1074
|
+
|
|
1075
|
+
### Model registry
|
|
1076
|
+
|
|
1077
|
+
The `Model` family ships with this package — registered Bedrock-Anthropic Claude and OpenAI GPT variants the SDK
|
|
1078
|
+
recognizes today, plus an escape hatch for consumers who need a Claude variant the SDK hasn't released yet.
|
|
936
1079
|
|
|
937
1080
|
- `Model` — abstract base class. Returned by `Models.getByName(...)` and accepted as an `AgentConfig.modelId` value.
|
|
938
1081
|
- `ModelName` — enum of in-tree model identifiers.
|
|
1082
|
+
- `Models` — registry: `Models.getByName(name)`, `Models.getDefault()`.
|
|
939
1083
|
- `createClaudeModel(gatewayId, overrides?)` — escape-hatch factory for opting into a Bedrock-Anthropic Claude variant
|
|
940
1084
|
the SDK has not released yet (`AgentConfig.modelId` accepts the returned instance directly).
|
|
941
1085
|
- `ClaudeModelOverrides` — optional caps for `createClaudeModel`.
|
|
942
|
-
- `
|
|
943
|
-
- `
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1086
|
+
- `validateMultimodalFiles(files, model)` — helper harnesses use to enforce per-format / per-model multimodal caps
|
|
1087
|
+
pre-stream. Throws `AgentSDKError(MULTIMODAL_NOT_SUPPORTED)` on a cap-exceeding file.
|
|
1088
|
+
- `MultimodalFile`, `SupportedFileFormat`, `MimeType` — supporting types for multimodal validation.
|
|
1089
|
+
|
|
1090
|
+
### Re-exported from `@salesforce/agentic-common`
|
|
1091
|
+
|
|
1092
|
+
For consumers who name SDK public-type signatures without adding `@salesforce/agentic-common` as a direct dependency.
|
|
1093
|
+
|
|
1094
|
+
- `OrgConnection` / `OrgConnectionFactory` — surface on `Agent.getOrgConnection()`.
|
|
1095
|
+
- `JSONWebToken` / `JWTOptions` / `RequiredJWTHeaders` / `RequiredJWTPayload` — surface on `ResolvedConnectivity.orgJwt`
|
|
1096
|
+
and `HarnessAgentConfig.orgJwt`.
|
|
1097
|
+
- `LogLevel` / `LogRecord` / `Unsubscribe` — surface on `manager.onLog` / `manager.onTelemetry` /
|
|
1098
|
+
`manager.onWireCommunication` callbacks.
|
|
1099
|
+
- `SfApiEnv` — Salesforce API environment enum (`dev`, `perf`, `prod`, `stage`, `test`).
|
|
1100
|
+
- `inferSfApiEnv(instanceUrl, options?)` — maps an instance URL to a `SfApiEnv`. Useful for consumers that need the
|
|
1101
|
+
mapping without an `OrgConnection` (e.g. building a Salesforce platform MCP URL). Defaults the unrecognized-`.pc-rnd.`
|
|
1102
|
+
fallback to `SfApiEnv.Test` (`.pc-rnd.` is an internal-only OrgFarm domain — not Prod by definition); pass
|
|
1103
|
+
`{ pcRndFallback: SfApiEnv.Prod }` to override. See the
|
|
947
1104
|
[`@salesforce/agentic-common` README](../agentic-common/README.md#infersfapienvinstanceurl-options-sfapienv) for the
|
|
948
1105
|
full resolution order.
|
|
949
1106
|
|
|
@@ -952,6 +1109,112 @@ Returns `true` if the URL matches a Salesforce Hosted MCP Server endpoint (prod,
|
|
|
952
1109
|
The SDK exposes typed `TelemetryEvent` / `LogRecord` streams at the manager, agent, and session scopes. See
|
|
953
1110
|
[Telemetry & Logs](#telemetry--logs) below for the event catalog, emit contract, and structured-log table.
|
|
954
1111
|
|
|
1112
|
+
### Wire-Communication Events
|
|
1113
|
+
|
|
1114
|
+
A separate, opt-in diagnostic channel that surfaces outbound LLM communication. Distinct from `onTelemetry` (SDK-level
|
|
1115
|
+
lifecycle and routing) and `onLog` (structured log records): wire-communication events carry the request URL, parsed
|
|
1116
|
+
body (where the harness can capture it), response status, and timing — for end-to-end debugging of gateway / provider
|
|
1117
|
+
behavior.
|
|
1118
|
+
|
|
1119
|
+
Subscribe via `AgentManager.onWireCommunication(callback)`:
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
const unsubscribe = manager.onWireCommunication((event) => {
|
|
1123
|
+
switch (event.type) {
|
|
1124
|
+
case 'llm-request':
|
|
1125
|
+
// outbound HTTP request to the LLM provider
|
|
1126
|
+
break;
|
|
1127
|
+
case 'llm-response':
|
|
1128
|
+
// matching response (or terminal failure)
|
|
1129
|
+
break;
|
|
1130
|
+
case 'wire-monitoring-not-supported':
|
|
1131
|
+
// harness can't emit per-call events for this stream;
|
|
1132
|
+
// event.message points at the workaround (typically a debug log file path)
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
**Discriminated union — three variants keyed on `type`:**
|
|
1139
|
+
|
|
1140
|
+
| `type` | Fields |
|
|
1141
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1142
|
+
| `llm-request` | `timestamp`, `url`, `method`, `model`, `body?` |
|
|
1143
|
+
| `llm-response` | `timestamp`, `model`, `status`, `xClientTraceId?`, `totalDurationMs?`, `timeToFirstTokenMs?`, `usage?` (`inputTokens?`, `outputTokens?`, `totalTokens?`, `reasoningTokens?`), `responseText?`, `error?` ({ `message` }) |
|
|
1144
|
+
| `wire-monitoring-not-supported` | `timestamp`, `harnessId`, `message` (free-form text — typically explains why direct wire monitoring isn't available and points at a fallback log file the harness wrote) |
|
|
1145
|
+
|
|
1146
|
+
**Privacy contract.** Events MAY contain user prompts, tool arguments, and model output in the `body` field. They are
|
|
1147
|
+
**not** flowed to the structured-log channel by default — consumers opt in to wire-level visibility explicitly via
|
|
1148
|
+
`onWireCommunication`. Apply your own redaction / scrubbing before persisting or sharing. Note that request HTTP headers
|
|
1149
|
+
(e.g. `Authorization`) are **not** captured on the events; the harness's fetch wrapper sees them but doesn't forward
|
|
1150
|
+
them onto the channel.
|
|
1151
|
+
|
|
1152
|
+
**Subscriber-gated end-to-end.** The chain `harness.wireBus → router slice → manager.wireBus` uses
|
|
1153
|
+
`EventBus.forwardWhileSubscribed` (from `@salesforce/agentic-common`), so a manager with zero `onWireCommunication`
|
|
1154
|
+
listeners leaves every link cold. Harnesses can read `wireBus.listenerCount > 0` and skip the work entirely — the Claude
|
|
1155
|
+
harness in particular skips writing the subprocess's debug log when nobody is listening. Subscribe **before** the
|
|
1156
|
+
`chat()` you want to inspect: subscriptions added mid-stream miss the in-flight subprocess on Claude (the listener count
|
|
1157
|
+
is captured at stream-start).
|
|
1158
|
+
|
|
1159
|
+
**Cross-harness coverage.**
|
|
1160
|
+
|
|
1161
|
+
- **Mastra harness**: emits one `llm-request` + one `llm-response` per outbound HTTP call to the LLM provider (every
|
|
1162
|
+
provider — Bedrock-Anthropic, direct Anthropic, OpenAI Responses, OpenAI Chat Completions). The body is captured from
|
|
1163
|
+
the in-process fetch wrapper.
|
|
1164
|
+
- **Claude harness**: cannot emit per-call events because the Claude Agent SDK runs the model client in a subprocess
|
|
1165
|
+
with no programmatic HTTP-interception seam. Instead, when at least one consumer is subscribed at stream-start, the
|
|
1166
|
+
harness writes the subprocess's debug log to `${storageRoot}/claude-debug-logs/${agentId}__${threadId}.log` (one file
|
|
1167
|
+
per thread; turns of the same conversation accumulate, separated by greppable `# Turn started: <ISO>` banners) and
|
|
1168
|
+
emits a single `wire-monitoring-not-supported` event whose `message` field embeds the file path. The file is left in
|
|
1169
|
+
place; the consumer is responsible for retention and cleanup.
|
|
1170
|
+
|
|
1171
|
+
#### `WireCommunicationFileWriter`
|
|
1172
|
+
|
|
1173
|
+
Convenience writer that subscribes to a `WireCommunicationEmitter` (typically the `AgentManager`) and appends each event
|
|
1174
|
+
to a Markdown file. Used for debugging sessions where you want a chronological transcript without building one yourself.
|
|
1175
|
+
|
|
1176
|
+
```typescript
|
|
1177
|
+
import { WireCommunicationFileWriter } from '@salesforce/sfdx-agent-sdk';
|
|
1178
|
+
|
|
1179
|
+
const writer = new WireCommunicationFileWriter(manager, {
|
|
1180
|
+
filePath: '/tmp/wire-communication.md',
|
|
1181
|
+
// optional — invoked on appendFile failure; defaults to console.error
|
|
1182
|
+
onError: (err, event) => {
|
|
1183
|
+
/* … */
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// later
|
|
1188
|
+
writer.detach();
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
The output file is plain Markdown with one `### <iso-timestamp> [llm-request]`, `[llm-response]`, or
|
|
1192
|
+
`[wire-monitoring-not-supported]` header per event followed by the populated fields. Optional fields are emitted only
|
|
1193
|
+
when set — sparse responses do not pad the file with empty `**Field**: N/A` lines. On the Claude harness, the
|
|
1194
|
+
`wire-monitoring-not-supported` block links to the per-thread debug log file where raw wire detail can be inspected
|
|
1195
|
+
directly.
|
|
1196
|
+
|
|
1197
|
+
The same privacy contract applies: the resulting file may contain prompts, tool arguments, and model output. Treat it as
|
|
1198
|
+
sensitive and apply your own retention / scrubbing rules before sharing.
|
|
1199
|
+
|
|
1200
|
+
##### `WireCommunicationFileWriterOptions`
|
|
1201
|
+
|
|
1202
|
+
| Field | Type | Description |
|
|
1203
|
+
| ---------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1204
|
+
| `filePath` | `string` | Absolute (or cwd-relative) path the writer appends Markdown to. Required. |
|
|
1205
|
+
| `onError?` | `(error: Error, event: WireCommunicationEvent) => void` | Optional callback invoked when an `appendFile` rejection is observed (disk full, parent directory unwritable, etc.). Without this, write failures are routed to `console.error` so a debugging session can't silently lose events. |
|
|
1206
|
+
|
|
1207
|
+
##### `WireCommunicationEmitter`
|
|
1208
|
+
|
|
1209
|
+
The shape `WireCommunicationFileWriter` accepts. Anything exposing `onWireCommunication(callback)` works — typically an
|
|
1210
|
+
`AgentManager`, but tests may construct a smaller surface.
|
|
1211
|
+
|
|
1212
|
+
```typescript
|
|
1213
|
+
type WireCommunicationEmitter = {
|
|
1214
|
+
onWireCommunication(callback: (event: WireCommunicationEvent) => void): Unsubscribe;
|
|
1215
|
+
};
|
|
1216
|
+
```
|
|
1217
|
+
|
|
955
1218
|
### Harness Extensibility
|
|
956
1219
|
|
|
957
1220
|
The `AgentHarness` contract is the SDK's lowest-common-denominator surface — every harness implements it, and the SDK
|
|
@@ -1162,6 +1425,14 @@ All variants share `{ type: <discriminant>, timestamp: Date }` plus the fields b
|
|
|
1162
1425
|
| `mcp-server-discovery-failed` | `agentId`, `serverName`, `durationMs`, `error`, `errorDetail?` ([`McpServerErrorDetail`](#mcpservererrordetail)) |
|
|
1163
1426
|
| `mcp-server-status-changed` | `agentId`, `serverName`, `previousStatus`, `nextStatus`, `error?` |
|
|
1164
1427
|
|
|
1428
|
+
Each variant is also exported as a named type so consumers can write narrowed handlers: `AgentCreatedEvent`,
|
|
1429
|
+
`AgentDestroyedEvent`, `SessionCreatedEvent`, `SessionDestroyedEvent`, `ChatStreamStartedEvent`,
|
|
1430
|
+
`ChatStreamCompletedEvent`, `ChatStreamErrorEvent`, `ToolExecutionStartedEvent`, `ToolExecutionCompletedEvent`,
|
|
1431
|
+
`ToolApprovalRequestedEvent`, `ToolApprovalResolvedEvent`, `McpServerDiscoveryStartedEvent`,
|
|
1432
|
+
`McpServerDiscoveryCompletedEvent`, `McpServerDiscoveryFailedEvent`, `McpServerStatusChangedEvent`. The
|
|
1433
|
+
`ChatStreamTrigger` union (`'chat' | 'submit-tool-result' | 'approve-tool-call' | 'decline-tool-call'`) and
|
|
1434
|
+
`TelemetryEventCallback` (the `onTelemetry` argument shape) are exported alongside.
|
|
1435
|
+
|
|
1165
1436
|
`mcp-server-status-changed` fires on every per-server status transition — emitted in addition to the discovery trio so
|
|
1166
1437
|
consumers can route on the `(previousStatus, nextStatus)` pair without polling `getMcpServerInfo()`. A reconnect on a
|
|
1167
1438
|
previously-`connected` server emits, in order: `connected → reconnecting`, then on success `reconnecting → connected`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"LICENSE.txt"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@salesforce/agentic-common": "0.
|
|
43
|
+
"@salesforce/agentic-common": "0.12.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@eslint/js": "^10.0.1",
|
|
47
|
-
"@salesforce/sfdx-agent-harness-claude": "0.
|
|
48
|
-
"@salesforce/sfdx-agent-harness-mastra": "0.
|
|
49
|
-
"@types/node": "^22.
|
|
47
|
+
"@salesforce/sfdx-agent-harness-claude": "0.20.0",
|
|
48
|
+
"@salesforce/sfdx-agent-harness-mastra": "0.23.0",
|
|
49
|
+
"@types/node": "^22.20.0",
|
|
50
50
|
"@vitest/coverage-istanbul": "^4.1.8",
|
|
51
51
|
"@vitest/eslint-plugin": "^1.6.20",
|
|
52
52
|
"eslint": "^10.5.0",
|