@salesforce/sfdx-agent-sdk 0.51.0 → 0.53.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 +10 -0
- package/README.md +96 -35
- package/dist/agent-manager.d.ts +18 -4
- package/dist/agent-manager.js +24 -10
- package/dist/agent.d.ts +70 -7
- package/dist/agent.js +86 -13
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +1 -0
- package/dist/harness/agent-harness.d.ts +22 -5
- package/dist/harness/harness-config.d.ts +57 -1
- package/dist/harness/public.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/internal/agent-identity-store.d.ts +3 -1
- package/dist/internal/agent-identity-store.js +7 -3
- package/dist/mcp-auth.d.ts +20 -0
- package/dist/mcp-auth.js +37 -0
- package/dist/mcp-config.d.ts +80 -2
- package/dist/mcp-config.js +6 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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.53.0] - 2026-08-24
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **agent-sdk**: persist per-agent consumer metadata for governed PATCH @W-23972709@ ([#760](https://github.com/forcedotcom/agentic-dx/pull/760))
|
|
10
|
+
|
|
11
|
+
## [0.52.0] - 2026-08-24
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- **agent-sdk,harness-claude,harness-mastra,harness-openai**: support OAuth authProvider on remote MCP servers @W-23939243@ ([#757](https://github.com/forcedotcom/agentic-dx/pull/757))
|
|
15
|
+
|
|
6
16
|
## [0.51.0] - 2026-08-18
|
|
7
17
|
|
|
8
18
|
### Chores
|
package/README.md
CHANGED
|
@@ -102,18 +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 | Signature
|
|
106
|
-
| --------------------- |
|
|
107
|
-
| `extensions` | `H['extensions']`
|
|
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>`
|
|
110
|
-
| `getAgentIds` | `() => string[]`
|
|
111
|
-
| `destroyAgent` | `(agentId: string) => Promise<void>`
|
|
112
|
-
| `shutdown` | `() => Promise<void>`
|
|
113
|
-
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe`
|
|
114
|
-
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe`
|
|
115
|
-
| `onWireCommunication` | `(callback: WireCommunicationEventCallback) => Unsubscribe`
|
|
116
|
-
| `getRestoreFailures` | `() => RestoreFailure[]`
|
|
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; consumerMetadata?: JsonValue }) => 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>`. `options.consumerMetadata` seeds the agent's opaque consumer metadata in the same persistence write as the config (create and initial metadata land together); it is never forwarded to the harness. |
|
|
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. |
|
|
117
117
|
|
|
118
118
|
#### `RestoreFailure`
|
|
119
119
|
|
|
@@ -136,24 +136,26 @@ A configured AI agent. Factory for chat sessions. The optional `H` type paramete
|
|
|
136
136
|
— harness-specific features are reached through `manager.extensions`, not `agent.extensions`. The default `AgentHarness`
|
|
137
137
|
keeps unparameterized call sites working.
|
|
138
138
|
|
|
139
|
-
| Method
|
|
140
|
-
|
|
|
141
|
-
| `getId`
|
|
142
|
-
| `getProjectRoot`
|
|
143
|
-
| `getOrgConnection`
|
|
144
|
-
| `getAgentConfig`
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
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
|
+
| `getConsumerMetadata` | `() => JsonValue \| undefined` | Deep copy of the agent's SDK-owned opaque consumer metadata, or `undefined` if unset. Persisted beside the config; never forwarded to the harness and never on the wire. Set it via `updateAgentState`. |
|
|
146
|
+
| `getMcpServerInfo` | `() => McpServerInfo[]` | MCP server status and discovered tools. |
|
|
147
|
+
| `reconnectMcpServer` | `(serverName: string) => Promise<void>` | Recover one MCP server without recycling the agent. Semantics vary by harness — observe via `getMcpServerInfo()` and discovery telemetry. |
|
|
148
|
+
| `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. A thin wrapper over `updateAgentState({ config }, options)`. |
|
|
149
|
+
| `updateAgentState` | `(update: AgentStateUpdate, options?: { abortSignal?: AbortSignal; forceResolve?: boolean }) => Promise<void>` | Atomically apply a config partial and/or a consumer-metadata mutation. When both are present they commit in one persistence write, so a failed update leaves config and metadata at their previous values together. A metadata-only update never calls the harness. See `AgentStateUpdate` / `ConsumerMetadataMutation` below. |
|
|
150
|
+
| `createChatSession` | `() => Promise<ChatSession>` | Open a new conversation thread. |
|
|
151
|
+
| `getChatSession` | `(sessionId: string) => ChatSession` | Retrieve a session. Throws `AgentSDKError` (`CHAT_SESSION_NOT_FOUND`). |
|
|
152
|
+
| `getChatSessionIds` | `() => string[]` | List active session IDs. |
|
|
153
|
+
| `destroyChatSession` | `(sessionId: string) => Promise<void>` | Destroy a session and its history. |
|
|
154
|
+
| `cloneChatSession` | `(sourceSessionId: string) => Promise<ChatSession>` | Clone a session with its message history. |
|
|
155
|
+
| `compactChatSession` | `(sessionId: string) => Promise<ChatSession>` | Compact a session into a summarized new session. |
|
|
156
|
+
| `destroy` | `() => Promise<void>` | Destroy the agent and all its sessions. |
|
|
157
|
+
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry scoped to this agent (and its sessions). |
|
|
158
|
+
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to logs scoped to this agent (and its sessions). |
|
|
157
159
|
|
|
158
160
|
### `ChatSession`
|
|
159
161
|
|
|
@@ -280,6 +282,25 @@ telemetry for a service of your own.
|
|
|
280
282
|
| `toolPolicies?` | `ToolPolicyRule[]` | Ordered per-tool approval rules resolved by `resolveToolApprovalPolicy` (cross-tier deny-wins / within-tier last-wins). Author directly or via `definePolicy(...)`. See "Tool Approval Policy" below. |
|
|
281
283
|
| `defaultToolDecision?` | `Decision` | Fallback decision when no rule matches. Defaults to `'allow'` (no policy ⇒ no gating). Set to `'require-approval'` for a fail-closed posture (recommended for catalogs with un-annotated MCP servers). |
|
|
282
284
|
|
|
285
|
+
#### `AgentStateUpdate` and `ConsumerMetadataMutation`
|
|
286
|
+
|
|
287
|
+
Inputs to `Agent.updateAgentState`. Consumer metadata is SDK-owned opaque JSON persisted beside the agent config — it
|
|
288
|
+
never sits on `AgentConfig`, never reaches the harness, and never appears on the REST wire.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
type ConsumerMetadataMutation = { action: 'replace'; value: JsonValue } | { action: 'clear' };
|
|
292
|
+
|
|
293
|
+
type AgentStateUpdate = {
|
|
294
|
+
config?: AgentConfig;
|
|
295
|
+
consumerMetadata?: ConsumerMetadataMutation;
|
|
296
|
+
};
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`replace` stores a deep copy of `value`; `clear` removes the metadata; omitting `consumerMetadata` leaves it unchanged.
|
|
300
|
+
When `update.config` is present, `updateAgentState` applies it to the harness exactly as `updateAgentConfig` does and
|
|
301
|
+
persists config plus metadata in one write; when it is absent, the update is metadata-only and never calls the harness.
|
|
302
|
+
`getConsumerMetadata()` reads the current value back as a deep copy.
|
|
303
|
+
|
|
283
304
|
#### `StreamOptions`
|
|
284
305
|
|
|
285
306
|
| Field | Type | Description |
|
|
@@ -386,6 +407,45 @@ type MCPRemoteServerConfig = {
|
|
|
386
407
|
};
|
|
387
408
|
```
|
|
388
409
|
|
|
410
|
+
#### OAuth-protected remote MCP servers
|
|
411
|
+
|
|
412
|
+
To connect an OAuth-protected third-party remote MCP server (Figma, Linear, Notion, Sentry, Neon, Hugging Face, …),
|
|
413
|
+
supply an `McpOAuthClientProvider` through the manager's **`mcpAuthProviderResolver`**, not on the server config. The
|
|
414
|
+
provider is a credential-bearing runtime object, so it is deliberately kept **off** the persisted `AgentConfig` — it is
|
|
415
|
+
resolved per agent at install / restore / update and threaded to the harness at runtime, never serialized to disk.
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
const manager = await createAgentManager(storageRoot, harnessFactory, {
|
|
419
|
+
// (agentId, config) => Record<remoteServerName, McpOAuthClientProvider> | undefined
|
|
420
|
+
mcpAuthProviderResolver: (_agentId, _config) => ({ figma: myFigmaProvider }),
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The harness forwards the provider into the underlying HTTP transport (`@modelcontextprotocol/sdk`'s
|
|
425
|
+
`StreamableHTTPClientTransport` on Claude / OpenAI, `@mastra/mcp`'s `HttpServerDefinition` on Mastra). The transport
|
|
426
|
+
attaches existing tokens and silently refreshes them when possible. If user interaction is required, it invokes the
|
|
427
|
+
provider's `redirectToAuthorization` callback and surfaces `McpServerErrorDetail.category === 'oauth-required'`; ADX
|
|
428
|
+
does not open a browser or accept the authorization callback code. The static `headers` seam cannot provide this
|
|
429
|
+
recovery because headers are snapshotted at publish time.
|
|
430
|
+
|
|
431
|
+
- **`McpOAuthClientProvider`** (exported) is a harness-neutral interface that structurally mirrors the required core of
|
|
432
|
+
the MCP SDK's `OAuthClientProvider`, so a real provider assigns to it with no cast and the SDK stays free of a direct
|
|
433
|
+
`@modelcontextprotocol/sdk` dependency. **`McpAuthProviders`** (exported) is the
|
|
434
|
+
`Readonly<Record<serverName, McpOAuthClientProvider>>` the resolver returns.
|
|
435
|
+
- **One provider instance per server**, and return the **same** instance across calls — the upstream contract forbids
|
|
436
|
+
tokens / codes / verifiers crossing sessions, and a harness cycles a server when its provider reference changes (so
|
|
437
|
+
returning a fresh instance each update reconnects that server every time).
|
|
438
|
+
- **One auth owner per server (enforced centrally).** The SDK normalizes the resolver's output before handing it to a
|
|
439
|
+
harness: a provider is **dropped** for a Salesforce Platform URL (the rotating org JWT owns auth there), a stdio /
|
|
440
|
+
disabled / unknown server; and a provider **paired with a static `Authorization` header** is **rejected**
|
|
441
|
+
(`AgentSDKErrorType.INVALID_MCP_AUTH_CONFIG`) because the MCP SDK transport would let the static header silently win
|
|
442
|
+
over the provider token. For a provider-owned non-Salesforce server the harness attaches the proxy-aware inner `fetch`
|
|
443
|
+
(no Salesforce JWT injected, `HTTPS_PROXY` preserved) while the provider drives auth.
|
|
444
|
+
|
|
445
|
+
> **Interactive authorization stays with the consumer.** AFV's provider owns the Sign in UI, browser, VS Code callback,
|
|
446
|
+
> and code exchange. After it saves the new tokens, call `Agent.reconnectMcpServer` to connect. ADX owns runtime
|
|
447
|
+
> provider forwarding and silent refresh only; the transport's `finishAuth(code)` seam is not part of the public API.
|
|
448
|
+
|
|
389
449
|
**Tool-exposure policy** (which tools bypass the active runtime's tool-search deferral) is configured per-agent on the
|
|
390
450
|
harness extension surface, not per-server here. See `MastraAgentConfig.toolSearch.alwaysActive` and
|
|
391
451
|
`ClaudeAgentConfig.toolSearch.alwaysActive` for the entry shape that covers "all tools from server X", "tool Y on server
|
|
@@ -411,17 +471,18 @@ only `MCPRemoteServerConfig` carries it.
|
|
|
411
471
|
|
|
412
472
|
#### `McpServerErrorDetail`
|
|
413
473
|
|
|
414
|
-
Structured projection of an MCP server failure.
|
|
415
|
-
|
|
474
|
+
Structured projection of an MCP server failure. Route interactive authorization on `category === 'oauth-required'`; an
|
|
475
|
+
ordinary missing or invalid bearer remains `http-401`. The same detail is attached to `mcp-server-discovery-failed`
|
|
476
|
+
telemetry so subscribers can route on it without pattern-matching `error.message`.
|
|
416
477
|
|
|
417
478
|
| Field | Type | Description |
|
|
418
479
|
| ----------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
419
480
|
| `category` | `McpServerErrorCategory` | Stable category for routing logic. Set is additive across minor versions — values are added but never renamed or removed. |
|
|
420
481
|
| `code?` | `number` | JSON-RPC error code from the underlying `McpError`, when the failure originated as a JSON-RPC error. Undefined for transport-level failures and for harnesses whose underlying SDK does not surface the code (e.g. Claude). |
|
|
421
|
-
| `retriable` | `boolean` | Whether the SDK considers the failure transient (worth `Agent.reconnectMcpServer`
|
|
482
|
+
| `retriable` | `boolean` | Whether the SDK considers the failure transient (worth `Agent.reconnectMcpServer`) versus fatal. |
|
|
422
483
|
|
|
423
|
-
`McpServerErrorCategory` values: `'connect-timeout'`, `'
|
|
424
|
-
`'transport-eof'`, `'protocol-error'`, `'config-error'`, `'aborted'`, `'unknown'`.
|
|
484
|
+
`McpServerErrorCategory` values: `'connect-timeout'`, `'oauth-required'`, `'http-401'`, `'http-403'`, `'http-4xx'`,
|
|
485
|
+
`'http-5xx'`, `'transport-eof'`, `'protocol-error'`, `'config-error'`, `'aborted'`, `'unknown'`.
|
|
425
486
|
|
|
426
487
|
#### `McpToolInfo`
|
|
427
488
|
|
package/dist/agent-manager.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type Clock, LogBus, type LogRecord, type Unsubscribe, type UniqueIDGenerator } from '@salesforce/agentic-common';
|
|
2
2
|
import { type AgentHarness, type ConfigOf } from './harness/agent-harness.js';
|
|
3
3
|
import type { HarnessFactory } from './harness/harness-factory.js';
|
|
4
|
-
import { type AgentConfig } from './harness/harness-config.js';
|
|
5
|
-
import {
|
|
4
|
+
import { type AgentConfig, type McpAuthProviderResolver } from './harness/harness-config.js';
|
|
5
|
+
import type { JsonValue } from './types/session-context.js';
|
|
6
|
+
import { type Agent, type AgentRuntimeResolvers } from './agent.js';
|
|
6
7
|
import type { HooksForAgent } from './types/redaction.js';
|
|
7
8
|
import { type TelemetryEventCallback } from './types/telemetry-events.js';
|
|
8
9
|
import type { WireCommunicationEventCallback } from './types/wire-communication-event.js';
|
|
@@ -50,6 +51,10 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
|
|
|
50
51
|
* without an explicit generic at the call site. Extra fields thread
|
|
51
52
|
* through opaquely to the harness, which narrows what it cares about.
|
|
52
53
|
*
|
|
54
|
+
* `options.consumerMetadata` seeds the agent's SDK-owned opaque consumer
|
|
55
|
+
* metadata, persisted in the same identity write as the config so create
|
|
56
|
+
* and initial metadata land together. It is never forwarded to the harness.
|
|
57
|
+
*
|
|
53
58
|
* @throws If `projectRoot` does not exist or is not a directory.
|
|
54
59
|
* @throws If `config.agentId` is provided and an agent with that ID is
|
|
55
60
|
* already registered (live or in restore-failure state).
|
|
@@ -58,6 +63,7 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
|
|
|
58
63
|
agentId?: string;
|
|
59
64
|
}, options?: {
|
|
60
65
|
abortSignal?: AbortSignal;
|
|
66
|
+
consumerMetadata?: JsonValue;
|
|
61
67
|
}): Promise<Agent>;
|
|
62
68
|
/**
|
|
63
69
|
* Returns the live {@link Agent} for the given id.
|
|
@@ -138,7 +144,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
138
144
|
private readonly harnessSupportedProviderHints;
|
|
139
145
|
private readonly agentIdGenerator;
|
|
140
146
|
private readonly agentConnectivityResolver;
|
|
141
|
-
private readonly
|
|
147
|
+
private readonly resolvers;
|
|
142
148
|
private readonly clock;
|
|
143
149
|
private readonly identityStore;
|
|
144
150
|
private readonly agents;
|
|
@@ -161,13 +167,14 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
161
167
|
* is private, so this is the only way to obtain an instance, but
|
|
162
168
|
* consumers should always go through {@link createAgentManager}.
|
|
163
169
|
*/
|
|
164
|
-
static __build<H extends AgentHarness>(harness: H, harnessSupportedProviderHints: readonly ProviderHint[], agentConnectivityResolver: AgentConnectivityResolver,
|
|
170
|
+
static __build<H extends AgentHarness>(harness: H, harnessSupportedProviderHints: readonly ProviderHint[], agentConnectivityResolver: AgentConnectivityResolver, resolvers: AgentRuntimeResolvers, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
|
|
165
171
|
private init;
|
|
166
172
|
shutdown(): Promise<void>;
|
|
167
173
|
createAgent(projectRoot: string, config?: ConfigOf<H> & {
|
|
168
174
|
agentId?: string;
|
|
169
175
|
}, options?: {
|
|
170
176
|
abortSignal?: AbortSignal;
|
|
177
|
+
consumerMetadata?: JsonValue;
|
|
171
178
|
}): Promise<Agent>;
|
|
172
179
|
/**
|
|
173
180
|
* Shared install path for {@link createAgent} and the boot-time restore
|
|
@@ -228,4 +235,11 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
228
235
|
export declare function createAgentManager<H extends AgentHarness = AgentHarness>(storageRootFolder: string, harnessFactory: HarnessFactory<H>, options?: {
|
|
229
236
|
connectivityResolver?: AgentConnectivityResolver;
|
|
230
237
|
hooksForAgent?: HooksForAgent;
|
|
238
|
+
/**
|
|
239
|
+
* Resolves the per-agent OAuth provider map for remote MCP servers.
|
|
240
|
+
* See {@link McpAuthProviderResolver}. Providers are supplied here at
|
|
241
|
+
* runtime and never persisted; required to connect OAuth-protected
|
|
242
|
+
* third-party remote MCP servers.
|
|
243
|
+
*/
|
|
244
|
+
mcpAuthProviderResolver?: McpAuthProviderResolver;
|
|
231
245
|
}): Promise<AgentManager<H>>;
|
package/dist/agent-manager.js
CHANGED
|
@@ -7,6 +7,7 @@ import { resolve } from 'node:path';
|
|
|
7
7
|
import { stat } from 'node:fs/promises';
|
|
8
8
|
import { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
|
|
9
9
|
import { toHarnessConfig } from './harness/harness-config.js';
|
|
10
|
+
import { normalizeMcpAuthProviders } from './mcp-auth.js';
|
|
10
11
|
import { DefaultAgent } from './agent.js';
|
|
11
12
|
import { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
12
13
|
import { TelemetryRouter } from './internal/telemetry-router.js';
|
|
@@ -28,7 +29,7 @@ export class DefaultAgentManager {
|
|
|
28
29
|
harnessSupportedProviderHints;
|
|
29
30
|
agentIdGenerator;
|
|
30
31
|
agentConnectivityResolver;
|
|
31
|
-
|
|
32
|
+
resolvers;
|
|
32
33
|
clock;
|
|
33
34
|
identityStore;
|
|
34
35
|
agents = new Map();
|
|
@@ -43,11 +44,11 @@ export class DefaultAgentManager {
|
|
|
43
44
|
wireRouter;
|
|
44
45
|
unroutedUnsubs;
|
|
45
46
|
disposed = false;
|
|
46
|
-
constructor(harness, harnessSupportedProviderHints, agentConnectivityResolver,
|
|
47
|
+
constructor(harness, harnessSupportedProviderHints, agentConnectivityResolver, resolvers, identityStore, agentIdGenerator, clock, logBus) {
|
|
47
48
|
this.harness = harness;
|
|
48
49
|
this.harnessSupportedProviderHints = harnessSupportedProviderHints;
|
|
49
50
|
this.agentConnectivityResolver = agentConnectivityResolver;
|
|
50
|
-
this.
|
|
51
|
+
this.resolvers = resolvers;
|
|
51
52
|
this.identityStore = identityStore;
|
|
52
53
|
this.agentIdGenerator = agentIdGenerator;
|
|
53
54
|
this.clock = clock;
|
|
@@ -75,9 +76,9 @@ export class DefaultAgentManager {
|
|
|
75
76
|
* is private, so this is the only way to obtain an instance, but
|
|
76
77
|
* consumers should always go through {@link createAgentManager}.
|
|
77
78
|
*/
|
|
78
|
-
static async __build(harness, harnessSupportedProviderHints, agentConnectivityResolver,
|
|
79
|
+
static async __build(harness, harnessSupportedProviderHints, agentConnectivityResolver, resolvers, storageRootFolder, agentIdGenerator, clock, logBus) {
|
|
79
80
|
const identityStore = new AgentIdentityStore(storageRootFolder, harness.harnessId, logBus);
|
|
80
|
-
const manager = new DefaultAgentManager(harness, harnessSupportedProviderHints, agentConnectivityResolver,
|
|
81
|
+
const manager = new DefaultAgentManager(harness, harnessSupportedProviderHints, agentConnectivityResolver, resolvers, identityStore, agentIdGenerator, clock, logBus);
|
|
81
82
|
await manager.init();
|
|
82
83
|
return manager;
|
|
83
84
|
}
|
|
@@ -88,6 +89,7 @@ export class DefaultAgentManager {
|
|
|
88
89
|
try {
|
|
89
90
|
await this.installAgent(record.agentId, record.projectRoot, record.config, {
|
|
90
91
|
rehydrateThreads: true,
|
|
92
|
+
consumerMetadata: record.consumerMetadata,
|
|
91
93
|
});
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
@@ -130,19 +132,26 @@ export class DefaultAgentManager {
|
|
|
130
132
|
const resolvedProjectRoot = resolve(projectRoot);
|
|
131
133
|
const { agentId: providedAgentId, ...agentConfig } = config;
|
|
132
134
|
const agentId = providedAgentId ?? this.agentIdGenerator.getUniqueId();
|
|
135
|
+
const consumerMetadata = options?.consumerMetadata;
|
|
133
136
|
if (this.agents.has(agentId)) {
|
|
134
137
|
throw new Error(`Agent with id "${agentId}" already exists`);
|
|
135
138
|
}
|
|
136
139
|
// installAgent validates projectRoot existence — same path as the restore loop.
|
|
137
140
|
const agent = await this.installAgent(agentId, resolvedProjectRoot, agentConfig, {
|
|
138
141
|
abortSignal: options?.abortSignal,
|
|
142
|
+
consumerMetadata,
|
|
139
143
|
});
|
|
140
144
|
// If the disk write fails (disk full, permissions, fs error), the in-memory install
|
|
141
145
|
// is now stale — the harness has the agent, the manager has it in `agents`, but no
|
|
142
146
|
// record on disk means a subsequent restart loses it. Roll back the install so the
|
|
143
147
|
// failure is observable and the id stays reusable, then rethrow.
|
|
144
148
|
try {
|
|
145
|
-
|
|
149
|
+
if (consumerMetadata === undefined) {
|
|
150
|
+
await this.identityStore.write(agentId, resolvedProjectRoot, agentConfig);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
await this.identityStore.write(agentId, resolvedProjectRoot, agentConfig, consumerMetadata);
|
|
154
|
+
}
|
|
146
155
|
}
|
|
147
156
|
catch (err) {
|
|
148
157
|
await this.rollbackInstall(agentId, agent);
|
|
@@ -183,8 +192,13 @@ export class DefaultAgentManager {
|
|
|
183
192
|
if (!this.harnessSupportedProviderHints.includes(providerHint)) {
|
|
184
193
|
throw new AgentSDKError(`Harness "${this.harness.harnessId}" does not support providerHint "${providerHint}". Supported: ${this.harnessSupportedProviderHints.join(', ')}.`, AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS);
|
|
185
194
|
}
|
|
186
|
-
const hooks = this.hooksForAgent?.(agentId, config) ?? {};
|
|
187
|
-
|
|
195
|
+
const hooks = this.resolvers.hooksForAgent?.(agentId, config) ?? {};
|
|
196
|
+
const mcpAuthProviders = normalizeMcpAuthProviders(config.mcpServers, this.resolvers.mcpAuthProviderResolver?.(agentId, config));
|
|
197
|
+
await this.harness.createAgent(agentId, projectRoot, runtime.modelConnectivityInfo, toHarnessConfig(config, runtime.orgJwt), {
|
|
198
|
+
...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}),
|
|
199
|
+
hooks,
|
|
200
|
+
mcpAuthProviders,
|
|
201
|
+
});
|
|
188
202
|
const agentSlice = this.router.registerAgent(agentId);
|
|
189
203
|
// Forward-compat: register the agent against the wire-communication router
|
|
190
204
|
// too. Today every WireCommunicationEvent lands on the unrouted slice (no
|
|
@@ -192,7 +206,7 @@ export class DefaultAgentManager {
|
|
|
192
206
|
// symmetrically with the telemetry router so the wiring is in place if/when
|
|
193
207
|
// the event shape grows an agentId field.
|
|
194
208
|
this.wireRouter.registerAgent(agentId);
|
|
195
|
-
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.
|
|
209
|
+
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, options.consumerMetadata, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.resolvers, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
196
210
|
this.agents.set(agentId, agent);
|
|
197
211
|
const agentCreatedAt = this.clock.now();
|
|
198
212
|
const modelName = runtime.modelConnectivityInfo.model.name;
|
|
@@ -359,7 +373,7 @@ export async function createAgentManager(storageRootFolder, harnessFactory, opti
|
|
|
359
373
|
}
|
|
360
374
|
const agentConnectivityResolver = options?.connectivityResolver ?? new DefaultAgentConnectivityResolver();
|
|
361
375
|
const clock = new RealClock();
|
|
362
|
-
return DefaultAgentManager.__build(harness, harnessFactory.supportedProviderHints, agentConnectivityResolver, options?.hooksForAgent, storageRootFolder, new UUIDGenerator(), clock,
|
|
376
|
+
return DefaultAgentManager.__build(harness, harnessFactory.supportedProviderHints, agentConnectivityResolver, { hooksForAgent: options?.hooksForAgent, mcpAuthProviderResolver: options?.mcpAuthProviderResolver }, storageRootFolder, new UUIDGenerator(), clock,
|
|
363
377
|
// The manager's root log bus shares the manager clock and self-reads the process environment context.
|
|
364
378
|
new LogBus(clock));
|
|
365
379
|
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type Clock, type JSONWebToken, LogBus, type LogRecord, type OrgConnection, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
|
|
2
2
|
import type { AgentHarness } from './harness/agent-harness.js';
|
|
3
|
-
import { type AgentConfig } from './harness/harness-config.js';
|
|
3
|
+
import { type AgentConfig, type AgentStateUpdate, type McpAuthProviderResolver } from './harness/harness-config.js';
|
|
4
4
|
import { type ChatSession } from './chat-session.js';
|
|
5
|
+
import type { JsonValue } from './types/session-context.js';
|
|
5
6
|
import type { McpServerInfo } from './mcp-config.js';
|
|
6
7
|
import type { AgentConnectivityResolver } from './agent-connectivity-resolver.js';
|
|
7
8
|
import type { ModelConnectivityInfo, ProviderHint } from './types/model-connectivity-info.js';
|
|
@@ -40,6 +41,13 @@ export interface Agent {
|
|
|
40
41
|
getOrgConnection(): OrgConnection | undefined;
|
|
41
42
|
/** Returns the current agent configuration. */
|
|
42
43
|
getAgentConfig(): AgentConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Returns a deep copy of the agent's persisted consumer metadata, or
|
|
46
|
+
* `undefined` if none has been set. This is SDK-owned opaque JSON: it is
|
|
47
|
+
* never placed on {@link AgentConfig}, never forwarded to the harness, and
|
|
48
|
+
* never appears on the REST wire. Set it via {@link updateAgentState}.
|
|
49
|
+
*/
|
|
50
|
+
getConsumerMetadata(): JsonValue | undefined;
|
|
43
51
|
/**
|
|
44
52
|
* Returns runtime information about MCP servers attached to this agent,
|
|
45
53
|
* including connection status and discovered tool names. This is a synchronous
|
|
@@ -97,6 +105,21 @@ export interface Agent {
|
|
|
97
105
|
abortSignal?: AbortSignal;
|
|
98
106
|
forceResolve?: boolean;
|
|
99
107
|
}): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Atomically update the agent's configuration and/or its persisted consumer
|
|
110
|
+
* metadata. `update.config` is merged and applied to the harness exactly as
|
|
111
|
+
* {@link updateAgentConfig} does; `update.consumerMetadata` mutates the
|
|
112
|
+
* SDK-owned opaque metadata. When both are present they commit in one
|
|
113
|
+
* persistence write, so a failed update leaves config and metadata at their
|
|
114
|
+
* previous values together. A metadata-only update never calls the harness.
|
|
115
|
+
*
|
|
116
|
+
* @param update - The config partial and/or consumer-metadata mutation to apply.
|
|
117
|
+
* @param options - Same execution options as {@link updateAgentConfig}.
|
|
118
|
+
*/
|
|
119
|
+
updateAgentState(update: AgentStateUpdate, options?: {
|
|
120
|
+
abortSignal?: AbortSignal;
|
|
121
|
+
forceResolve?: boolean;
|
|
122
|
+
}): Promise<void>;
|
|
100
123
|
/**
|
|
101
124
|
* Create a new chat session (conversation thread) for this agent.
|
|
102
125
|
* @returns A new {@link ChatSession} with a unique thread ID.
|
|
@@ -144,6 +167,15 @@ export interface Agent {
|
|
|
144
167
|
/** Subscribe to structured log records scoped to this agent (and its sessions). Returns an unsubscribe function. */
|
|
145
168
|
onLog(callback: (record: LogRecord) => void): Unsubscribe;
|
|
146
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* The per-agent runtime resolvers the SDK threads from `createAgentManager` into each agent — callbacks the consumer
|
|
172
|
+
* supplies once and the SDK re-invokes per install / restore / update. Bundled so a new runtime binding is one field
|
|
173
|
+
* here, not another parameter through every manager → agent seam. Each member is optional (unset by the consumer).
|
|
174
|
+
*/
|
|
175
|
+
export type AgentRuntimeResolvers = {
|
|
176
|
+
hooksForAgent?: HooksForAgent;
|
|
177
|
+
mcpAuthProviderResolver?: McpAuthProviderResolver;
|
|
178
|
+
};
|
|
147
179
|
/**
|
|
148
180
|
* Default implementation of {@link Agent} that delegates
|
|
149
181
|
* agent and thread operations to an {@link AgentHarness}.
|
|
@@ -153,12 +185,13 @@ export declare class DefaultAgent implements Agent {
|
|
|
153
185
|
private readonly agentId;
|
|
154
186
|
private readonly projectRoot;
|
|
155
187
|
private config;
|
|
188
|
+
private consumerMetadata;
|
|
156
189
|
private modelConnectivityInfo;
|
|
157
190
|
private orgConnection;
|
|
158
191
|
private orgJwt;
|
|
159
192
|
private readonly agentConnectivityResolver;
|
|
160
193
|
private readonly harnessSupportedProviderHints;
|
|
161
|
-
private readonly
|
|
194
|
+
private readonly resolvers;
|
|
162
195
|
private readonly identityStore;
|
|
163
196
|
private readonly sessions;
|
|
164
197
|
private readonly sessionSliceUnregisters;
|
|
@@ -175,23 +208,26 @@ export declare class DefaultAgent implements Agent {
|
|
|
175
208
|
* @param agentId - Unique identifier for this agent.
|
|
176
209
|
* @param projectRoot - Project folder this agent is allowed to operate within.
|
|
177
210
|
* @param config - Initial agent configuration (instructions, model, tools, etc.).
|
|
211
|
+
* @param consumerMetadata - Initial SDK-owned opaque consumer metadata (or `undefined`). Persisted beside
|
|
212
|
+
* `config`; never forwarded to the harness. Mutated via `updateAgentState`.
|
|
178
213
|
* @param modelConnectivityInfo - Connectivity bag (model, baseUrl, nativeModelId,
|
|
179
214
|
* providerHint, getHeaders) the harness uses to talk to the LLM. Replaced
|
|
180
215
|
* on every `updateAgentConfig` re-resolve.
|
|
181
216
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
182
217
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
183
218
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
184
|
-
* @param
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* the
|
|
219
|
+
* @param resolvers - Per-agent runtime resolvers ({@link AgentRuntimeResolvers}: `hooksForAgent`,
|
|
220
|
+
* `mcpAuthProviderResolver`) supplied by the SDK consumer at `createAgentManager` time. The agent re-invokes
|
|
221
|
+
* them on every `updateAgentConfig` (with `nextConfig`, and again with `previousConfig` on the rollback path)
|
|
222
|
+
* so what the harness sees always reflects the current persisted config. Each member is `undefined` when the
|
|
223
|
+
* consumer didn't pass it.
|
|
188
224
|
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
189
225
|
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
190
226
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
191
227
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
192
228
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
193
229
|
*/
|
|
194
|
-
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[],
|
|
230
|
+
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, consumerMetadata: JsonValue | undefined, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[], resolvers: AgentRuntimeResolvers, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
195
231
|
/**
|
|
196
232
|
* @requirements
|
|
197
233
|
* - MUST return the agent's ID.
|
|
@@ -205,6 +241,12 @@ export declare class DefaultAgent implements Agent {
|
|
|
205
241
|
* - MUST return a shallow copy of the internal `config` object to prevent external mutation of the agent's state.
|
|
206
242
|
*/
|
|
207
243
|
getAgentConfig(): AgentConfig;
|
|
244
|
+
/**
|
|
245
|
+
* @requirements
|
|
246
|
+
* - MUST return a deep copy so a caller cannot mutate the agent's persisted consumer metadata in place.
|
|
247
|
+
* - MUST return `undefined` when no metadata has been set.
|
|
248
|
+
*/
|
|
249
|
+
getConsumerMetadata(): JsonValue | undefined;
|
|
208
250
|
getMcpServerInfo(): McpServerInfo[];
|
|
209
251
|
reconnectMcpServer(serverName: string): Promise<void>;
|
|
210
252
|
/**
|
|
@@ -227,6 +269,27 @@ export declare class DefaultAgent implements Agent {
|
|
|
227
269
|
abortSignal?: AbortSignal;
|
|
228
270
|
forceResolve?: boolean;
|
|
229
271
|
}): Promise<void>;
|
|
272
|
+
/**
|
|
273
|
+
* @requirements
|
|
274
|
+
* - MUST merge `update.config` (when present) with the internal `config` object; `agentId` stays unchanged.
|
|
275
|
+
* - MUST apply the merged config to the harness via `this.harness.updateAgent(...)` ONLY when `update.config`
|
|
276
|
+
* is present. A metadata-only update MUST NOT call the harness at all (no server cycling, no resolver run).
|
|
277
|
+
* - MUST compute the next consumer metadata from `update.consumerMetadata`: `replace` stores a deep copy of the
|
|
278
|
+
* value; `clear` removes it; an absent mutation leaves it unchanged.
|
|
279
|
+
* - MUST persist config + metadata together in ONE `this.identityStore.write(...)` after any `harness.updateAgent`
|
|
280
|
+
* succeeds and before the in-memory swaps, so a write failure rolls back through the same catch path.
|
|
281
|
+
* - MUST preserve BOTH the previous config AND the previous metadata if `updateAgent` or persistence fails.
|
|
282
|
+
*/
|
|
283
|
+
updateAgentState(update: AgentStateUpdate, options?: {
|
|
284
|
+
abortSignal?: AbortSignal;
|
|
285
|
+
forceResolve?: boolean;
|
|
286
|
+
}): Promise<void>;
|
|
287
|
+
/**
|
|
288
|
+
* Writes the identity record, calling `identityStore.write` with a 4th argument only when consumer metadata is
|
|
289
|
+
* present. Omitting the argument (rather than passing `undefined`) keeps a config-only write byte-identical to the
|
|
290
|
+
* pre-metadata call shape and clears any previously-persisted metadata.
|
|
291
|
+
*/
|
|
292
|
+
private writeIdentity;
|
|
230
293
|
/**
|
|
231
294
|
* @requirements
|
|
232
295
|
* - MUST delegate to `this.harness.createThread(this.config.agentId)` to generate a new thread ID.
|