@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
package/dist/mcp-config.js
CHANGED
|
@@ -17,4 +17,102 @@ export var McpServerStatus;
|
|
|
17
17
|
*/
|
|
18
18
|
McpServerStatus["Reconnecting"] = "reconnecting";
|
|
19
19
|
})(McpServerStatus || (McpServerStatus = {}));
|
|
20
|
+
/**
|
|
21
|
+
* Structural deep-equality predicate over {@link MCPServerConfig}. Returns
|
|
22
|
+
* `true` when two configs would behave identically at the harness layer —
|
|
23
|
+
* meaning a harness handed `b` while currently bound to `a` MUST be free to
|
|
24
|
+
* preserve its existing transport, client instance, and discovered tool
|
|
25
|
+
* catalog without cycling.
|
|
26
|
+
*
|
|
27
|
+
* Used by harnesses inside `AgentHarness.updateAgent` to decide which MCP
|
|
28
|
+
* servers to preserve vs. cycle when an agent's config changes. Exported so
|
|
29
|
+
* both production harnesses use the same equality and a third harness can
|
|
30
|
+
* adopt it without duplicating the rules.
|
|
31
|
+
*
|
|
32
|
+
* **Equality rules:**
|
|
33
|
+
* - Both `undefined` ⇒ `true`. Exactly one `undefined` ⇒ `false`.
|
|
34
|
+
* - `type` mismatch ⇒ `false` (the discriminated union splits stdio vs remote).
|
|
35
|
+
* - `enabled: undefined` and `enabled: true` compare equal — both type docs
|
|
36
|
+
* declare `true` as the default.
|
|
37
|
+
* - Stdio: structural compare on `command`, `args` (order-sensitive),
|
|
38
|
+
* `env` (key-order-insensitive), `timeout`.
|
|
39
|
+
* - Remote: `url` is compared via `String(url)` so `URL` instances and
|
|
40
|
+
* strings round-trip; `headers` (key-order-insensitive); `timeout`,
|
|
41
|
+
* `reconnectionOptions` (field-wise).
|
|
42
|
+
*
|
|
43
|
+
* Two configs that pass this predicate but produce different runtime tools
|
|
44
|
+
* (e.g. an upstream stdio server whose binary was overwritten on disk) are
|
|
45
|
+
* NOT detected here — the predicate compares declared config, not runtime
|
|
46
|
+
* state. Use `Agent.reconnectMcpServer(name)` to force a per-server cycle in
|
|
47
|
+
* that case.
|
|
48
|
+
*/
|
|
49
|
+
export function mcpServerConfigEqual(a, b) {
|
|
50
|
+
if (a === b)
|
|
51
|
+
return true;
|
|
52
|
+
if (!a || !b)
|
|
53
|
+
return false;
|
|
54
|
+
if (a.type !== b.type)
|
|
55
|
+
return false;
|
|
56
|
+
if ((a.enabled ?? true) !== (b.enabled ?? true))
|
|
57
|
+
return false;
|
|
58
|
+
if (a.timeout !== b.timeout)
|
|
59
|
+
return false;
|
|
60
|
+
if (a.type === 'stdio' && b.type === 'stdio') {
|
|
61
|
+
if (a.command !== b.command)
|
|
62
|
+
return false;
|
|
63
|
+
if (!arraysEqual(a.args, b.args))
|
|
64
|
+
return false;
|
|
65
|
+
if (!recordsEqual(a.env, b.env))
|
|
66
|
+
return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (a.type === 'remote' && b.type === 'remote') {
|
|
70
|
+
if (String(a.url) !== String(b.url))
|
|
71
|
+
return false;
|
|
72
|
+
if (!recordsEqual(a.headers, b.headers))
|
|
73
|
+
return false;
|
|
74
|
+
if (!reconnectionOptionsEqual(a.reconnectionOptions, b.reconnectionOptions))
|
|
75
|
+
return false;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function arraysEqual(a, b) {
|
|
81
|
+
if (a === b)
|
|
82
|
+
return true;
|
|
83
|
+
if (!a || !b)
|
|
84
|
+
return (a?.length ?? 0) === (b?.length ?? 0);
|
|
85
|
+
if (a.length !== b.length)
|
|
86
|
+
return false;
|
|
87
|
+
for (let i = 0; i < a.length; i++) {
|
|
88
|
+
if (a[i] !== b[i])
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
function recordsEqual(a, b) {
|
|
94
|
+
if (a === b)
|
|
95
|
+
return true;
|
|
96
|
+
const aKeys = a ? Object.keys(a) : [];
|
|
97
|
+
const bKeys = b ? Object.keys(b) : [];
|
|
98
|
+
if (aKeys.length !== bKeys.length)
|
|
99
|
+
return false;
|
|
100
|
+
for (const k of aKeys) {
|
|
101
|
+
if (!Object.prototype.hasOwnProperty.call(b ?? {}, k))
|
|
102
|
+
return false;
|
|
103
|
+
if (a[k] !== b[k])
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
function reconnectionOptionsEqual(a, b) {
|
|
109
|
+
if (a === b)
|
|
110
|
+
return true;
|
|
111
|
+
if (!a || !b)
|
|
112
|
+
return !a && !b;
|
|
113
|
+
return (a.maxRetries === b.maxRetries &&
|
|
114
|
+
a.initialReconnectionDelay === b.initialReconnectionDelay &&
|
|
115
|
+
a.maxReconnectionDelay === b.maxReconnectionDelay &&
|
|
116
|
+
a.reconnectionDelayGrowFactor === b.reconnectionDelayGrowFactor);
|
|
117
|
+
}
|
|
20
118
|
//# sourceMappingURL=mcp-config.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { AgentConfig } from '../harness/harness-config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sync-or-async callback the harness invokes for every tool result before it
|
|
4
|
+
* enters the model's context. The redactor inspects the upstream output and
|
|
5
|
+
* either replaces it (returning `{ output }`) or passes it through unchanged
|
|
6
|
+
* (returning `undefined`).
|
|
7
|
+
*
|
|
8
|
+
* Wired per-agent via {@link HooksForAgent} on `createAgentManager`; the SDK
|
|
9
|
+
* surfaces it inside the harness through {@link AgentHooks.onToolResult} on
|
|
10
|
+
* `AgentHarness.createAgent`'s `options.hooks` bag. A single registration
|
|
11
|
+
* covers built-in tools (`Bash`, `Read`, `Edit`, …), MCP tools, and
|
|
12
|
+
* consumer-executed tools declared via {@link AgentConfig.tools}.
|
|
13
|
+
*
|
|
14
|
+
* ### Why this lives in the harness layer
|
|
15
|
+
*
|
|
16
|
+
* Once a tool result reaches the SDK boundary the model has already seen it —
|
|
17
|
+
* any value can then be echoed in the reply, routed into a later tool call
|
|
18
|
+
* (`Bash` arg, file write), or sent to provider logs. Redaction has to fire
|
|
19
|
+
* INSIDE the engine, before the result is folded into the model's next
|
|
20
|
+
* request. The SDK exposes a harness-agnostic shape; each harness wires its
|
|
21
|
+
* native seam (Claude Agent SDK `PostToolUse` hook,
|
|
22
|
+
* Mastra `processInputStep`).
|
|
23
|
+
*
|
|
24
|
+
* ### Audit / preserving the original
|
|
25
|
+
*
|
|
26
|
+
* The redactor sees the unmodified `output` at the call site. Consumers that
|
|
27
|
+
* need an audit trail of the original value MUST log it themselves before
|
|
28
|
+
* returning the redaction. The SDK does not put the original on its telemetry
|
|
29
|
+
* bus or persist it anywhere — that would defeat the point.
|
|
30
|
+
*
|
|
31
|
+
* ### Throw policy is the consumer's
|
|
32
|
+
*
|
|
33
|
+
* The SDK does not own fail-closed semantics. If the redactor throws, the
|
|
34
|
+
* harness re-throws on its native error path: Claude routes through the
|
|
35
|
+
* Claude Agent SDK's `PostToolUse` hook-error path (which synthesizes a
|
|
36
|
+
* `tool_result(is_error=true)`); Mastra propagates from `processInputStep`
|
|
37
|
+
* and surfaces as an `error` ChatEvent on the consumer's eventStream.
|
|
38
|
+
* Consumers requiring a richer fail-closed substitute wrap their redactor's
|
|
39
|
+
* body in `try`/`catch` themselves — see the SDK README's
|
|
40
|
+
* "Tool-Result Redaction" section for the recommended boilerplate.
|
|
41
|
+
*
|
|
42
|
+
* ### Tool-shape constraints
|
|
43
|
+
*
|
|
44
|
+
* The harness does NOT validate that the replacement `output` has the same
|
|
45
|
+
* shape as the original — the redactor knows what tool it is redacting and
|
|
46
|
+
* is responsible for honoring that tool's expected return shape. Notable
|
|
47
|
+
* cases:
|
|
48
|
+
*
|
|
49
|
+
* - **Claude built-in `Bash`** — the replacement MUST keep the
|
|
50
|
+
* `{ stdout, stderr, interrupted }` shape. A bare-string return is rejected
|
|
51
|
+
* by the Claude Agent SDK and the original leaks.
|
|
52
|
+
* - **MCP tools** — the replacement MUST be a valid MCP `CallToolResult`
|
|
53
|
+
* shape (`{ content: [...], isError? }`).
|
|
54
|
+
* - **Consumer-executed tools** — replacement passes through unchanged to
|
|
55
|
+
* `submitToolResult`, so any shape the consumer accepts is fine.
|
|
56
|
+
*
|
|
57
|
+
* ### Performance
|
|
58
|
+
*
|
|
59
|
+
* Both harnesses skip their per-result hook entirely when
|
|
60
|
+
* `hooks.onToolResult` is undefined, so the no-op overhead is exactly zero.
|
|
61
|
+
* When set, both engines await the redactor (sync redactors collapse to a
|
|
62
|
+
* microtask).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const redactor: ToolResultRedactor = ({ toolName, output, isError }) => {
|
|
67
|
+
* // Caller-side audit (consumer's responsibility — SDK does not log originals).
|
|
68
|
+
* auditLog.write({ toolName, originalLength: JSON.stringify(output).length });
|
|
69
|
+
*
|
|
70
|
+
* // Bash needs its native shape preserved.
|
|
71
|
+
* if (toolName === 'Bash') {
|
|
72
|
+
* const bash = output as { stdout: string; stderr: string; interrupted: boolean };
|
|
73
|
+
* return { output: { ...bash, stdout: scrub(bash.stdout), stderr: scrub(bash.stderr) } };
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* // Other tools: walk the structured output and scrub field-by-field.
|
|
77
|
+
* return { output: scrubDeep(output) };
|
|
78
|
+
* };
|
|
79
|
+
*
|
|
80
|
+
* const manager = await createAgentManager(storage, factory, {
|
|
81
|
+
* hooksForAgent: () => ({ onToolResult: redactor }),
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export type ToolResultRedactor = (input: ToolResultRedactionInput) => ToolResultRedactionResult | Promise<ToolResultRedactionResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Inputs the harness hands the {@link ToolResultRedactor} for each tool
|
|
88
|
+
* result. Carries enough identity for the redactor to decide what (if
|
|
89
|
+
* anything) to redact and to attribute audit log entries.
|
|
90
|
+
*/
|
|
91
|
+
export type ToolResultRedactionInput = {
|
|
92
|
+
/** Agent that produced the tool result. */
|
|
93
|
+
agentId: string;
|
|
94
|
+
/** Conversation thread the result belongs to. */
|
|
95
|
+
threadId: string;
|
|
96
|
+
/** Stable id linking this result to the originating `tool-call` event. */
|
|
97
|
+
toolCallId: string;
|
|
98
|
+
/** Tool name the model invoked. Built-in / consumer / namespaced MCP form depends on the harness. */
|
|
99
|
+
toolName: string;
|
|
100
|
+
/**
|
|
101
|
+
* Originating MCP server when the tool came from an MCP catalog.
|
|
102
|
+
* `undefined` for built-ins, consumer-executed tools, and Mastra workspace
|
|
103
|
+
* tools. Mirrors the enrichment on {@link ToolResultEvent.serverName}.
|
|
104
|
+
*/
|
|
105
|
+
serverName?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Raw upstream output, exactly as the engine received it. The redactor
|
|
108
|
+
* MUST treat this as input only — mutating it is undefined behavior.
|
|
109
|
+
*/
|
|
110
|
+
output: unknown;
|
|
111
|
+
/** `true` when the tool execution failed (engine flagged the result as an error). */
|
|
112
|
+
isError: boolean;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Return value from a {@link ToolResultRedactor} invocation.
|
|
116
|
+
*
|
|
117
|
+
* - `{ output }` — replace the original with this value.
|
|
118
|
+
* - `undefined` — pass the original through unchanged.
|
|
119
|
+
*
|
|
120
|
+
* The function signature already permits "no return" (an arrow body that
|
|
121
|
+
* doesn't `return` resolves to `undefined`), so a separate `void` variant
|
|
122
|
+
* isn't needed in the value-type union.
|
|
123
|
+
*
|
|
124
|
+
* The replacement shape MUST match what the originating tool produces. See
|
|
125
|
+
* the tool-shape notes on {@link ToolResultRedactor} for the harness-specific
|
|
126
|
+
* constraints (notably the Claude `Bash` `{ stdout, stderr, interrupted }`
|
|
127
|
+
* requirement).
|
|
128
|
+
*/
|
|
129
|
+
export type ToolResultRedactionResult = {
|
|
130
|
+
output: unknown;
|
|
131
|
+
} | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Per-agent hook bag the SDK resolves once per agent install / update via
|
|
134
|
+
* {@link HooksForAgent} and threads through to the harness on
|
|
135
|
+
* `AgentHarness.createAgent`'s `options.hooks`. Today the bag carries one
|
|
136
|
+
* field; the shape is open so future hooks (e.g. `onToolCall`, `onStep`)
|
|
137
|
+
* can be added without churning `*HarnessFactoryConfig`s.
|
|
138
|
+
*
|
|
139
|
+
* Harnesses MUST treat this object as opaque: store it on per-agent state,
|
|
140
|
+
* route the hooks they recognize to their native seam, and IGNORE unknown
|
|
141
|
+
* fields (forward-compat). Harnesses MUST NOT swallow hook throws — an
|
|
142
|
+
* exception from a hook MUST propagate on the harness's native error path
|
|
143
|
+
* so the original value never leaks to the model.
|
|
144
|
+
*
|
|
145
|
+
* The SDK never reads, persists, or surfaces this bag on its telemetry bus.
|
|
146
|
+
*/
|
|
147
|
+
export type AgentHooks = {
|
|
148
|
+
/**
|
|
149
|
+
* Optional redactor invoked for every tool result before it enters the
|
|
150
|
+
* model's context. See {@link ToolResultRedactor}. Each harness routes
|
|
151
|
+
* this to its native seam (Claude `PostToolUse`, Mastra
|
|
152
|
+
* `processInputStep`); the SDK does not enforce fail-closed semantics.
|
|
153
|
+
*/
|
|
154
|
+
onToolResult?: ToolResultRedactor;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Resolves a per-agent {@link AgentHooks} bag from the agent's id and the
|
|
158
|
+
* config the SDK currently has on file for that agent. Invoked by
|
|
159
|
+
* `AgentManager` once per agent install (`createAgent`, boot-time restore,
|
|
160
|
+
* `Agent.updateAgentConfig`); the resolved bag is handed to
|
|
161
|
+
* `AgentHarness.createAgent`'s `options.hooks`.
|
|
162
|
+
*
|
|
163
|
+
* The callback is sync — the SDK does not await. Consumers needing async
|
|
164
|
+
* setup (e.g. remote feature flags) pre-resolve before constructing the
|
|
165
|
+
* manager.
|
|
166
|
+
*
|
|
167
|
+
* Consumers with one global policy ignore both arguments and return the
|
|
168
|
+
* same bag every time; consumers wanting per-agent variation branch on
|
|
169
|
+
* `agentId` or fields of `config`.
|
|
170
|
+
*/
|
|
171
|
+
export type HooksForAgent = (agentId: string, config: AgentConfig) => AgentHooks;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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": {
|
|
@@ -32,31 +36,32 @@
|
|
|
32
36
|
"dist",
|
|
33
37
|
"!dist/**/*.map",
|
|
34
38
|
"!dist/test",
|
|
39
|
+
"CHANGELOG.md",
|
|
35
40
|
"LICENSE.txt"
|
|
36
41
|
],
|
|
37
42
|
"dependencies": {
|
|
38
|
-
"@salesforce/agentic-common": "0.
|
|
39
|
-
"@salesforce/llm-gateway-sdk": "0.
|
|
43
|
+
"@salesforce/agentic-common": "0.9.0",
|
|
44
|
+
"@salesforce/llm-gateway-sdk": "0.13.0"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
42
47
|
"@eslint/js": "^10.0.1",
|
|
43
|
-
"@salesforce/sfdx-agent-harness-claude": "0.
|
|
44
|
-
"@salesforce/sfdx-agent-harness-mastra": "0.
|
|
45
|
-
"@types/node": "^22.19.
|
|
46
|
-
"@vitest/coverage-istanbul": "^4.1.
|
|
47
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
48
|
-
"eslint": "^10.4.
|
|
48
|
+
"@salesforce/sfdx-agent-harness-claude": "0.13.0",
|
|
49
|
+
"@salesforce/sfdx-agent-harness-mastra": "0.16.0",
|
|
50
|
+
"@types/node": "^22.19.19",
|
|
51
|
+
"@vitest/coverage-istanbul": "^4.1.8",
|
|
52
|
+
"@vitest/eslint-plugin": "^1.6.19",
|
|
53
|
+
"eslint": "^10.4.1",
|
|
49
54
|
"eslint-config-prettier": "^10.1.8",
|
|
50
|
-
"eslint-import-resolver-typescript": "^4.4.
|
|
55
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
51
56
|
"eslint-plugin-import": "^2.32.0",
|
|
52
57
|
"eslint-plugin-n": "^18.0.1",
|
|
53
58
|
"globals": "^17.6.0",
|
|
54
|
-
"lint-staged": "^17.0.
|
|
59
|
+
"lint-staged": "^17.0.7",
|
|
55
60
|
"prettier": "^3.8.3",
|
|
56
61
|
"rimraf": "^6.1.3",
|
|
57
|
-
"tsx": "^4.22.
|
|
62
|
+
"tsx": "^4.22.4",
|
|
58
63
|
"typescript": "^6.0.3",
|
|
59
|
-
"typescript-eslint": "^8.
|
|
64
|
+
"typescript-eslint": "^8.60.1",
|
|
60
65
|
"vitest": "^4.1.7"
|
|
61
66
|
},
|
|
62
67
|
"engines": {
|