@salesforce/sfdx-agent-sdk 0.15.0 → 0.17.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 +22 -0
- package/README.md +312 -99
- package/dist/agent-manager.d.ts +19 -6
- package/dist/agent-manager.js +23 -12
- package/dist/agent.d.ts +25 -8
- package/dist/agent.js +29 -20
- package/dist/chat-session.d.ts +43 -26
- package/dist/chat-session.js +34 -23
- package/dist/harness/agent-harness.d.ts +114 -17
- package/dist/harness/always-active.d.ts +60 -0
- package/dist/harness/always-active.js +58 -0
- package/dist/harness/gen-sink.d.ts +41 -0
- package/dist/harness/gen-sink.js +88 -0
- package/dist/harness/index.d.ts +1 -0
- package/dist/harness/index.js +1 -0
- package/dist/harness/public.d.ts +52 -0
- package/dist/harness/public.js +12 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -4
- package/dist/mcp-config.d.ts +30 -24
- package/dist/mcp-config.js +98 -0
- package/dist/types/redaction.d.ts +171 -0
- package/dist/types/redaction.js +6 -0
- package/package.json +18 -13
|
@@ -4,6 +4,7 @@ import type { ChatStreamResult } from '../types/events.js';
|
|
|
4
4
|
import type { Message, MessagePart } from '../types/messages.js';
|
|
5
5
|
import type { TelemetryEventCallback } from '../types/telemetry-events.js';
|
|
6
6
|
import type { ToolResultInfo } from '../types/tools.js';
|
|
7
|
+
import type { AgentHooks } from '../types/redaction.js';
|
|
7
8
|
import type { AgentConfig, HarnessAgentConfig, StreamOptions } from './harness-config.js';
|
|
8
9
|
import type { LLMGatewayClient } from '@salesforce/llm-gateway-sdk';
|
|
9
10
|
export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly [1];
|
|
@@ -105,10 +106,24 @@ export interface AgentHarness {
|
|
|
105
106
|
* @param projectRoot - Project folder the agent is allowed to manipulate files from.
|
|
106
107
|
* @param llmGatewayClient - Pre-configured LLM gateway client for this agent.
|
|
107
108
|
* @param config - Engine-facing agent configuration (org resolution omitted).
|
|
108
|
-
* @param options - Optional execution options
|
|
109
|
+
* @param options - Optional execution options.
|
|
110
|
+
* - `abortSignal` — caller-side cancellation; harnesses thread it
|
|
111
|
+
* through long-running install work (rules load, MCP discovery, …).
|
|
112
|
+
* - `hooks` — per-agent {@link AgentHooks} bag, resolved by the SDK
|
|
113
|
+
* from `createAgentManager`'s `hooksForAgent` callback. The bag is
|
|
114
|
+
* opaque to the SDK; harnesses MUST store it on per-agent state and
|
|
115
|
+
* route each recognized hook to their native seam (Claude
|
|
116
|
+
* `PostToolUse`, Mastra `processInputStep`). Harnesses MUST IGNORE
|
|
117
|
+
* hook fields they do not recognize (forward-compat). Harnesses
|
|
118
|
+
* MUST NOT swallow hook throws — exceptions MUST propagate on the
|
|
119
|
+
* native error path so the original tool output never leaks to the
|
|
120
|
+
* model. The SDK does not own fail-closed semantics; consumers
|
|
121
|
+
* wrap their hook bodies in `try`/`catch` themselves when they
|
|
122
|
+
* want a richer fail-closed substitute.
|
|
109
123
|
*/
|
|
110
124
|
createAgent(agentId: string, projectRoot: string, llmGatewayClient: LLMGatewayClient, config?: HarnessAgentConfig, options?: {
|
|
111
125
|
abortSignal?: AbortSignal;
|
|
126
|
+
hooks?: AgentHooks;
|
|
112
127
|
}): Promise<void>;
|
|
113
128
|
/**
|
|
114
129
|
* Destroy an agent and release its resources (MCP connections, workspace, memory).
|
|
@@ -122,6 +137,81 @@ export interface AgentHarness {
|
|
|
122
137
|
* @returns `true` after a real removal.
|
|
123
138
|
*/
|
|
124
139
|
destroyAgent(agentId: string): Promise<boolean>;
|
|
140
|
+
/**
|
|
141
|
+
* Apply a new configuration to a registered agent without recycling MCP
|
|
142
|
+
* clients whose `MCPServerConfig` is structurally equal to the
|
|
143
|
+
* currently-applied one.
|
|
144
|
+
*
|
|
145
|
+
* This is the load-bearing primitive behind `Agent.updateAgentConfig`'s
|
|
146
|
+
* "don't blow up live MCP clients on a model-only / instructions-only /
|
|
147
|
+
* org-connect change" contract. Pre-#541 the SDK called
|
|
148
|
+
* `destroyAgent` + `createAgent` here, which closed every MCP client and
|
|
149
|
+
* forced the model to wait through the discovery wave again. With
|
|
150
|
+
* `updateAgent` the SDK calls one method and the harness preserves
|
|
151
|
+
* unchanged servers in place.
|
|
152
|
+
*
|
|
153
|
+
* Implementors MUST:
|
|
154
|
+
*
|
|
155
|
+
* - throw `AgentSDKError(AGENT_NOT_FOUND)` on unknown `agentId`,
|
|
156
|
+
* matching the rest of the cross-harness contract.
|
|
157
|
+
* - preserve the in-memory MCP client (and its discovered tool catalog)
|
|
158
|
+
* for any server name whose config is `mcpServerConfigEqual` to the
|
|
159
|
+
* currently-applied one. No transport teardown, no `tools/list`
|
|
160
|
+
* re-run, no per-server discovery telemetry.
|
|
161
|
+
* - cycle (disconnect-then-reconnect) any server whose config differs.
|
|
162
|
+
* - disconnect any server present in the currently-applied config but
|
|
163
|
+
* absent from the next config, removing it from
|
|
164
|
+
* `getMcpServerInfo()`'s output.
|
|
165
|
+
* - connect any server present in the next config but absent from the
|
|
166
|
+
* currently-applied one, running discovery the same way `createAgent`
|
|
167
|
+
* would (background, non-blocking; failures land on
|
|
168
|
+
* `McpServerState.error`, not as a thrown rejection).
|
|
169
|
+
* - apply non-MCP changes — `instructions`, `model`, `tools`, `skills`,
|
|
170
|
+
* `rules`, harness-specific extra-config fields surviving via
|
|
171
|
+
* `toHarnessConfig`'s spread — atomically with the MCP diff. After
|
|
172
|
+
* this resolves, the agent's effective config is the one passed in.
|
|
173
|
+
* - be idempotent on a no-op call at the MCP layer: when the next
|
|
174
|
+
* config's `mcpServers` is deep-equal (per `mcpServerConfigEqual`) to
|
|
175
|
+
* the currently-applied one, no server is cycled, no `tools/list` is
|
|
176
|
+
* re-run, and no per-server discovery telemetry fires. Implementors
|
|
177
|
+
* MAY rebuild non-MCP state (e.g. Mastra reconstructs its `Agent`
|
|
178
|
+
* unconditionally) — that work is local and cheap; correct no-op
|
|
179
|
+
* detection across `orgJwt` rotation, hook-bag closures, and
|
|
180
|
+
* harness-specific extra-config fields is not.
|
|
181
|
+
* - write per-server state incrementally so a subsequent `updateAgent`
|
|
182
|
+
* call (e.g. SDK rollback against `previousConfig`) sees the harness's
|
|
183
|
+
* current truth, not a snapshot from the start of the failed update.
|
|
184
|
+
*
|
|
185
|
+
* Implementors MUST NOT:
|
|
186
|
+
*
|
|
187
|
+
* - touch persisted thread / session state. Sessions are config-
|
|
188
|
+
* independent — `Agent.updateAgentConfig` does not invalidate them.
|
|
189
|
+
* - dispose in-flight stream coordinators. In-flight turns continue
|
|
190
|
+
* executing against the agent state captured at stream-start; the
|
|
191
|
+
* next `stream()` after this resolves uses the new state.
|
|
192
|
+
* - mutate `AgentConfig` or persist anything to disk. Persistence is
|
|
193
|
+
* the SDK's responsibility (`AgentIdentityStore.write` is gated by
|
|
194
|
+
* `Agent.updateAgentConfig` after this method resolves).
|
|
195
|
+
*
|
|
196
|
+
* @param agentId - ID of the agent to update.
|
|
197
|
+
* @param llmGatewayClient - LLM gateway client bound to the next config's org / model.
|
|
198
|
+
* @param config - Engine-facing agent configuration to apply.
|
|
199
|
+
* @param options - Optional execution options.
|
|
200
|
+
* - `abortSignal` — caller-side cancellation; harnesses thread it
|
|
201
|
+
* through long-running update work (rules load, MCP discovery, …).
|
|
202
|
+
* - `hooks` — per-agent {@link AgentHooks} bag, resolved by the SDK
|
|
203
|
+
* from `createAgentManager`'s `hooksForAgent` callback against the
|
|
204
|
+
* incoming `nextConfig`. Same semantics as `createAgent.options.hooks`:
|
|
205
|
+
* opaque to the SDK; harnesses store it on per-agent state and
|
|
206
|
+
* re-route each recognized hook to its native seam. The bag is
|
|
207
|
+
* re-resolved on every `updateAgent` so consumers can vary hooks by
|
|
208
|
+
* config (and so a rollback `updateAgent(previousConfig)` restores
|
|
209
|
+
* the prior hook bag too).
|
|
210
|
+
*/
|
|
211
|
+
updateAgent(agentId: string, llmGatewayClient: LLMGatewayClient, config?: HarnessAgentConfig, options?: {
|
|
212
|
+
abortSignal?: AbortSignal;
|
|
213
|
+
hooks?: AgentHooks;
|
|
214
|
+
}): Promise<void>;
|
|
125
215
|
/**
|
|
126
216
|
* List the IDs of all currently registered agents.
|
|
127
217
|
*/
|
|
@@ -238,49 +328,56 @@ export interface AgentHarness {
|
|
|
238
328
|
stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<ChatStreamResult>;
|
|
239
329
|
/**
|
|
240
330
|
* Feed the result of a **consumer-executed (client-side) tool** back into the
|
|
241
|
-
* conversation
|
|
331
|
+
* conversation. Implements the consumer-facing
|
|
242
332
|
* {@link ChatSession.submitToolResult} contract: see that JSDoc for the
|
|
243
333
|
* full consumer-level semantics.
|
|
244
334
|
*
|
|
245
335
|
* Called only for tools declared in {@link HarnessAgentConfig.tools} (no
|
|
246
336
|
* `execute` function). Tools provided via `mcpServers` are executed by the
|
|
247
|
-
* harness directly and never reach this method.
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
337
|
+
* harness directly and never reach this method.
|
|
338
|
+
*
|
|
339
|
+
* Returns `Promise<void>` once the harness has accepted the tool result
|
|
340
|
+
* (the parked bridge handler resolved). Post-resume events
|
|
341
|
+
* (`tool-result`, model follow-up text, terminal `finish`) flow through
|
|
342
|
+
* the SAME `ChatStreamResult.eventStream` returned by the most recent
|
|
343
|
+
* {@link stream} call on the thread — the consumer is still iterating
|
|
344
|
+
* that stream and sees the events arrive.
|
|
251
345
|
*
|
|
252
346
|
* @param agentId - ID of the agent.
|
|
253
347
|
* @param threadId - ID of the conversation thread.
|
|
254
348
|
* @param toolResult - The completed tool execution result; `toolCallId`
|
|
255
349
|
* matches the id from the originating `tool-call` event.
|
|
256
350
|
*/
|
|
257
|
-
submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<
|
|
351
|
+
submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<void>;
|
|
258
352
|
/**
|
|
259
353
|
* Approve a pending tool call, allowing the harness to execute it.
|
|
260
354
|
* Called after receiving a `tool-approval-request` event.
|
|
261
355
|
*
|
|
262
|
-
* Returns
|
|
263
|
-
*
|
|
264
|
-
*
|
|
356
|
+
* Returns `Promise<void>` once the harness has accepted the approval
|
|
357
|
+
* (the parked PreToolUse hook resolved on Claude; the resume call was
|
|
358
|
+
* dispatched on Mastra). Post-resume events flow through the SAME
|
|
359
|
+
* `ChatStreamResult.eventStream` returned by the most recent
|
|
360
|
+
* {@link stream} call on the thread.
|
|
265
361
|
*
|
|
266
362
|
* @param agentId - ID of the agent.
|
|
267
363
|
* @param threadId - ID of the conversation thread (needed for harness-internal state lookup).
|
|
268
364
|
* @param toolCallId - ID of the tool call to approve.
|
|
269
365
|
*/
|
|
270
|
-
approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<
|
|
366
|
+
approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
|
|
271
367
|
/**
|
|
272
|
-
* Decline a pending tool call. The harness resumes the
|
|
273
|
-
* model acknowledging the decline and potentially suggesting
|
|
368
|
+
* Decline a pending tool call. The harness resumes the agentic loop with
|
|
369
|
+
* the model acknowledging the decline and potentially suggesting
|
|
370
|
+
* alternatives.
|
|
274
371
|
*
|
|
275
|
-
* Returns
|
|
276
|
-
*
|
|
277
|
-
*
|
|
372
|
+
* Returns `Promise<void>` once the harness has accepted the decline.
|
|
373
|
+
* Post-resume events flow through the SAME `ChatStreamResult.eventStream`
|
|
374
|
+
* returned by the most recent {@link stream} call on the thread.
|
|
278
375
|
*
|
|
279
376
|
* @param agentId - ID of the agent.
|
|
280
377
|
* @param threadId - ID of the conversation thread.
|
|
281
378
|
* @param toolCallId - ID of the tool call to decline.
|
|
282
379
|
*/
|
|
283
|
-
declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<
|
|
380
|
+
declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
|
|
284
381
|
/**
|
|
285
382
|
* Retrieve message history for a thread.
|
|
286
383
|
*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-exposure policy entry shape consumed by both
|
|
3
|
+
* {@link MastraAgentConfig.toolSearch.alwaysActive} and
|
|
4
|
+
* {@link ClaudeAgentConfig.toolSearch.alwaysActive}.
|
|
5
|
+
*
|
|
6
|
+
* One entry covers three matching patterns:
|
|
7
|
+
*
|
|
8
|
+
* | Entry shape | Matches |
|
|
9
|
+
* | --- | --- |
|
|
10
|
+
* | `{ serverName: 'X' }` | every tool advertised by server `X` (post-discovery expansion) |
|
|
11
|
+
* | `{ serverName: 'X', toolName: 'Y' }` | exactly tool `Y` on server `X` |
|
|
12
|
+
* | `{ toolName: 'Y' }` | any tool named `Y`, **regardless of source** — built-ins, workspace tools, consumer-declared tools, AND any MCP server's tool surface |
|
|
13
|
+
*
|
|
14
|
+
* The `{ toolName }` pattern is intentionally broad — it's the consumer's
|
|
15
|
+
* escape hatch for "I want this tool always-active and I don't care where it
|
|
16
|
+
* comes from." The `{ serverName, toolName }` form is the precise version for
|
|
17
|
+
* when ambiguity matters.
|
|
18
|
+
*
|
|
19
|
+
* **Validation rule:** at least one of `serverName` or `toolName` must be
|
|
20
|
+
* present. An empty `{}` is rejected at config time via
|
|
21
|
+
* {@link validateAlwaysActiveEntry} — a typo should fail loud, not silently
|
|
22
|
+
* match nothing (and definitely not silently match everything).
|
|
23
|
+
*
|
|
24
|
+
* Lives on the harness public surface (`@salesforce/sfdx-agent-sdk/harness`)
|
|
25
|
+
* because it's harness-implementation shape that both production harnesses
|
|
26
|
+
* share. Ready to graduate to `AgentConfig.toolSearch` on the SDK contract
|
|
27
|
+
* surface once a third harness exercises it — same graduation pattern PR
|
|
28
|
+
* #563 established for `skillSearch`.
|
|
29
|
+
*/
|
|
30
|
+
export type AlwaysActiveEntry = {
|
|
31
|
+
serverName: string;
|
|
32
|
+
toolName?: string;
|
|
33
|
+
} | {
|
|
34
|
+
serverName?: undefined;
|
|
35
|
+
toolName: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Throws on an entry whose `serverName` AND `toolName` are both absent. Both
|
|
39
|
+
* harness boundaries call this on every entry before reading it so a typo
|
|
40
|
+
* (`{}` / `{ severName: 'X' }` with a misspelled key) fails loud at config
|
|
41
|
+
* time rather than silently dropping the entry — or worse, silently matching
|
|
42
|
+
* nothing while looking like it should match everything.
|
|
43
|
+
*/
|
|
44
|
+
export declare function validateAlwaysActiveEntry(entry: AlwaysActiveEntry): void;
|
|
45
|
+
/**
|
|
46
|
+
* Returns `true` when `(serverName, toolName)` matches at least one entry in
|
|
47
|
+
* `entries`. Pure / deterministic; harnesses call it per-tool when building
|
|
48
|
+
* their tool catalogs.
|
|
49
|
+
*
|
|
50
|
+
* - `{ serverName: 'X' }` matches every tool from server X.
|
|
51
|
+
* - `{ serverName: 'X', toolName: 'Y' }` matches only `Y` on `X`.
|
|
52
|
+
* - `{ toolName: 'Y' }` matches `Y` from any source (built-ins, workspace,
|
|
53
|
+
* consumer-declared tools, every connected MCP server).
|
|
54
|
+
*
|
|
55
|
+
* For tools without a server (built-ins, in-process workspace), pass
|
|
56
|
+
* `serverName: undefined`. The `{ toolName }`-only entries match those;
|
|
57
|
+
* `{ serverName: 'X' }` and `{ serverName: 'X', toolName: ... }` entries do
|
|
58
|
+
* not.
|
|
59
|
+
*/
|
|
60
|
+
export declare function matchesAlwaysActive(entries: readonly AlwaysActiveEntry[] | undefined, serverName: string | undefined, toolName: string): boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
|
+
* See LICENSE.txt for license terms.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Throws on an entry whose `serverName` AND `toolName` are both absent. Both
|
|
7
|
+
* harness boundaries call this on every entry before reading it so a typo
|
|
8
|
+
* (`{}` / `{ severName: 'X' }` with a misspelled key) fails loud at config
|
|
9
|
+
* time rather than silently dropping the entry — or worse, silently matching
|
|
10
|
+
* nothing while looking like it should match everything.
|
|
11
|
+
*/
|
|
12
|
+
export function validateAlwaysActiveEntry(entry) {
|
|
13
|
+
const serverName = entry.serverName;
|
|
14
|
+
const toolName = entry.toolName;
|
|
15
|
+
if ((typeof serverName !== 'string' || serverName.length === 0) &&
|
|
16
|
+
(typeof toolName !== 'string' || toolName.length === 0)) {
|
|
17
|
+
throw new Error('AlwaysActiveEntry must declare at least one of `serverName` or `toolName` (received: ' +
|
|
18
|
+
JSON.stringify(entry) +
|
|
19
|
+
')');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns `true` when `(serverName, toolName)` matches at least one entry in
|
|
24
|
+
* `entries`. Pure / deterministic; harnesses call it per-tool when building
|
|
25
|
+
* their tool catalogs.
|
|
26
|
+
*
|
|
27
|
+
* - `{ serverName: 'X' }` matches every tool from server X.
|
|
28
|
+
* - `{ serverName: 'X', toolName: 'Y' }` matches only `Y` on `X`.
|
|
29
|
+
* - `{ toolName: 'Y' }` matches `Y` from any source (built-ins, workspace,
|
|
30
|
+
* consumer-declared tools, every connected MCP server).
|
|
31
|
+
*
|
|
32
|
+
* For tools without a server (built-ins, in-process workspace), pass
|
|
33
|
+
* `serverName: undefined`. The `{ toolName }`-only entries match those;
|
|
34
|
+
* `{ serverName: 'X' }` and `{ serverName: 'X', toolName: ... }` entries do
|
|
35
|
+
* not.
|
|
36
|
+
*/
|
|
37
|
+
export function matchesAlwaysActive(entries, serverName, toolName) {
|
|
38
|
+
if (!entries || entries.length === 0)
|
|
39
|
+
return false;
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const entryServer = entry.serverName;
|
|
42
|
+
const entryTool = entry.toolName;
|
|
43
|
+
if (entryServer !== undefined) {
|
|
44
|
+
if (entryServer !== serverName)
|
|
45
|
+
continue;
|
|
46
|
+
if (entryTool === undefined)
|
|
47
|
+
return true; // server-wide
|
|
48
|
+
if (entryTool === toolName)
|
|
49
|
+
return true; // exact server+tool
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// entryServer === undefined ⇒ entryTool MUST be defined (validated upstream)
|
|
53
|
+
if (entryTool === toolName)
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=always-active.js.map
|
|
@@ -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
|
package/dist/harness/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/harness/index.js
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
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 type { AgentHooks } from '../types/redaction.js';
|
|
47
|
+
export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
|
|
48
|
+
export { mcpServerConfigEqual } from '../mcp-config.js';
|
|
49
|
+
export { HarnessBusOwner } from './harness-bus-owner.js';
|
|
50
|
+
export { lowerStreamInput, type InputMessagePart } from './stream-input.js';
|
|
51
|
+
export { GenSink } from './gen-sink.js';
|
|
52
|
+
export { matchesAlwaysActive, validateAlwaysActiveEntry, type AlwaysActiveEntry } from './always-active.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
export { mcpServerConfigEqual } from '../mcp-config.js';
|
|
7
|
+
// ── Harness-implementation helpers ──────────────────────────────────
|
|
8
|
+
export { HarnessBusOwner } from './harness-bus-owner.js';
|
|
9
|
+
export { lowerStreamInput } from './stream-input.js';
|
|
10
|
+
export { GenSink } from './gen-sink.js';
|
|
11
|
+
export { matchesAlwaysActive, validateAlwaysActiveEntry } from './always-active.js';
|
|
12
|
+
//# sourceMappingURL=public.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@ export type { Message, MessagePart, ImagePart, FilePart } from './types/messages
|
|
|
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
4
|
export type { ContextUsage, FinishReason, UsageMetadata } from './types/usage.js';
|
|
5
|
+
export type { AgentHooks, HooksForAgent, ToolResultRedactor, ToolResultRedactionInput, ToolResultRedactionResult, } from './types/redaction.js';
|
|
5
6
|
export type { AgentConfig, HarnessAgentConfig, StreamOptions, ToolApprovalMode } from './harness/harness-config.js';
|
|
6
7
|
export { DEFAULT_MAX_STEPS, resolveToolApprovalMode } from './harness/harness-config.js';
|
|
7
8
|
export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, McpServerErrorCategory, McpServerErrorDetail, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
|
|
8
|
-
export { McpServerStatus } from './mcp-config.js';
|
|
9
|
+
export { McpServerStatus, mcpServerConfigEqual } from './mcp-config.js';
|
|
9
10
|
export { Model, ModelName, createClaudeModel } from '@salesforce/llm-gateway-sdk';
|
|
10
11
|
export type { ClaudeModelOverrides } from '@salesforce/llm-gateway-sdk';
|
|
11
12
|
export { inferSfApiEnv, SfApiEnv } from '@salesforce/agentic-common';
|
|
@@ -14,9 +15,6 @@ export { type Agent } from './agent.js';
|
|
|
14
15
|
export { type ChatSession, type ChatOptions } from './chat-session.js';
|
|
15
16
|
export type { AgentConnectivityResolver, ResolvedConnectivity } from './agent-connectivity-resolver.js';
|
|
16
17
|
export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './harness/index.js';
|
|
17
|
-
export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
|
|
18
|
-
export { HarnessBusOwner } from './harness/harness-bus-owner.js';
|
|
19
|
-
export { lowerStreamInput, type InputMessagePart } from './harness/stream-input.js';
|
|
20
18
|
export { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
21
19
|
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';
|
|
22
20
|
export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,13 @@
|
|
|
3
3
|
* See LICENSE.txt for license terms.
|
|
4
4
|
*/
|
|
5
5
|
export { DEFAULT_MAX_STEPS, resolveToolApprovalMode } from './harness/harness-config.js';
|
|
6
|
-
export { McpServerStatus } from './mcp-config.js';
|
|
6
|
+
export { McpServerStatus, mcpServerConfigEqual } from './mcp-config.js';
|
|
7
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 ────────────────────────────────────────────────────────
|
package/dist/mcp-config.d.ts
CHANGED
|
@@ -34,25 +34,6 @@ export type MCPStdioServerConfig = {
|
|
|
34
34
|
enabled?: boolean;
|
|
35
35
|
/** Timeout in milliseconds for individual requests to the server. */
|
|
36
36
|
timeout?: number;
|
|
37
|
-
/**
|
|
38
|
-
* Opt the server's tool surface out of the active runtime's tool-search
|
|
39
|
-
* deferral. When `true`, every tool advertised by this server is
|
|
40
|
-
* registered with the model up-front instead of sitting behind a
|
|
41
|
-
* search/load round-trip. Useful for small, discovery-critical surfaces
|
|
42
|
-
* (e.g. ≤ 10 tools the model needs to find without prompting). Default
|
|
43
|
-
* (`undefined` / `false`): tools may be deferred when the active runtime
|
|
44
|
-
* enables tool search.
|
|
45
|
-
*
|
|
46
|
-
* **Harness behavior:**
|
|
47
|
-
* - **Claude harness** — sets `_meta['anthropic/alwaysLoad'] = true` on
|
|
48
|
-
* each tool the bridge forwards, equivalent to
|
|
49
|
-
* `defer_loading: false` on the API. Skill-bridge and consumer-tool
|
|
50
|
-
* tools are always-load regardless of this flag (see
|
|
51
|
-
* `@salesforce/sfdx-agent-harness-claude` ARCHITECTURE.md).
|
|
52
|
-
* - **Mastra harness** — no-op; Mastra eager-loads MCP tools at every
|
|
53
|
-
* turn already, so there's no deferral to opt out of.
|
|
54
|
-
*/
|
|
55
|
-
alwaysLoad?: boolean;
|
|
56
37
|
};
|
|
57
38
|
/** MCP server accessible over HTTP/SSE at a remote URL. */
|
|
58
39
|
export type MCPRemoteServerConfig = {
|
|
@@ -91,11 +72,6 @@ export type MCPRemoteServerConfig = {
|
|
|
91
72
|
/** Factor by which the reconnection delay grows after each attempt. Default `1.5`. */
|
|
92
73
|
reconnectionDelayGrowFactor?: number;
|
|
93
74
|
};
|
|
94
|
-
/**
|
|
95
|
-
* Opt the server's tool surface out of the active runtime's tool-search
|
|
96
|
-
* deferral. See {@link MCPStdioServerConfig.alwaysLoad}.
|
|
97
|
-
*/
|
|
98
|
-
alwaysLoad?: boolean;
|
|
99
75
|
};
|
|
100
76
|
/** Connection status of a single MCP server. */
|
|
101
77
|
export declare enum McpServerStatus {
|
|
@@ -260,6 +236,36 @@ export type McpServerErrorDetail = {
|
|
|
260
236
|
*/
|
|
261
237
|
retriable: boolean;
|
|
262
238
|
};
|
|
239
|
+
/**
|
|
240
|
+
* Structural deep-equality predicate over {@link MCPServerConfig}. Returns
|
|
241
|
+
* `true` when two configs would behave identically at the harness layer —
|
|
242
|
+
* meaning a harness handed `b` while currently bound to `a` MUST be free to
|
|
243
|
+
* preserve its existing transport, client instance, and discovered tool
|
|
244
|
+
* catalog without cycling.
|
|
245
|
+
*
|
|
246
|
+
* Used by harnesses inside `AgentHarness.updateAgent` to decide which MCP
|
|
247
|
+
* servers to preserve vs. cycle when an agent's config changes. Exported so
|
|
248
|
+
* both production harnesses use the same equality and a third harness can
|
|
249
|
+
* adopt it without duplicating the rules.
|
|
250
|
+
*
|
|
251
|
+
* **Equality rules:**
|
|
252
|
+
* - Both `undefined` ⇒ `true`. Exactly one `undefined` ⇒ `false`.
|
|
253
|
+
* - `type` mismatch ⇒ `false` (the discriminated union splits stdio vs remote).
|
|
254
|
+
* - `enabled: undefined` and `enabled: true` compare equal — both type docs
|
|
255
|
+
* declare `true` as the default.
|
|
256
|
+
* - Stdio: structural compare on `command`, `args` (order-sensitive),
|
|
257
|
+
* `env` (key-order-insensitive), `timeout`.
|
|
258
|
+
* - Remote: `url` is compared via `String(url)` so `URL` instances and
|
|
259
|
+
* strings round-trip; `headers` (key-order-insensitive); `timeout`,
|
|
260
|
+
* `reconnectionOptions` (field-wise).
|
|
261
|
+
*
|
|
262
|
+
* Two configs that pass this predicate but produce different runtime tools
|
|
263
|
+
* (e.g. an upstream stdio server whose binary was overwritten on disk) are
|
|
264
|
+
* NOT detected here — the predicate compares declared config, not runtime
|
|
265
|
+
* state. Use `Agent.reconnectMcpServer(name)` to force a per-server cycle in
|
|
266
|
+
* that case.
|
|
267
|
+
*/
|
|
268
|
+
export declare function mcpServerConfigEqual(a: MCPServerConfig | undefined, b: MCPServerConfig | undefined): boolean;
|
|
263
269
|
/** Runtime status of a configured MCP server, including its discovered tools. */
|
|
264
270
|
export type McpServerInfo = {
|
|
265
271
|
name: string;
|