@salesforce/sfdx-agent-sdk 0.20.0 → 0.22.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +12 -11
  3. package/dist/agent-connectivity-resolver.d.ts +47 -25
  4. package/dist/agent-connectivity-resolver.js +92 -15
  5. package/dist/agent-manager.d.ts +17 -1
  6. package/dist/agent-manager.js +40 -7
  7. package/dist/agent.d.ts +33 -10
  8. package/dist/agent.js +38 -50
  9. package/dist/api-key-connectivity-resolver.d.ts +110 -0
  10. package/dist/api-key-connectivity-resolver.js +114 -0
  11. package/dist/errors.d.ts +2 -0
  12. package/dist/errors.js +2 -0
  13. package/dist/harness/agent-harness.d.ts +27 -5
  14. package/dist/harness/harness-bus-owner.d.ts +17 -0
  15. package/dist/harness/harness-bus-owner.js +27 -0
  16. package/dist/harness/harness-config.d.ts +3 -2
  17. package/dist/harness/harness-factory.d.ts +13 -0
  18. package/dist/harness/stream-input.d.ts +9 -5
  19. package/dist/harness/stream-input.js +12 -14
  20. package/dist/index.d.ts +8 -2
  21. package/dist/index.js +4 -1
  22. package/dist/internal/wire-communication-router.d.ts +43 -0
  23. package/dist/internal/wire-communication-router.js +119 -0
  24. package/dist/mcp-auth.d.ts +1 -1
  25. package/dist/models/claude-opus-4-5.d.ts +11 -0
  26. package/dist/models/claude-opus-4-5.js +21 -0
  27. package/dist/models/claude-opus-4-6.d.ts +11 -0
  28. package/dist/models/claude-opus-4-6.js +22 -0
  29. package/dist/models/claude-opus-4-7.d.ts +11 -0
  30. package/dist/models/claude-opus-4-7.js +22 -0
  31. package/dist/models/claude-sonnet-4-5.d.ts +11 -0
  32. package/dist/models/claude-sonnet-4-5.js +23 -0
  33. package/dist/models/claude-sonnet-4-6.d.ts +11 -0
  34. package/dist/models/claude-sonnet-4-6.js +22 -0
  35. package/dist/models/create-claude-model.d.ts +54 -0
  36. package/dist/models/create-claude-model.js +62 -0
  37. package/dist/models/gpt-5-4.d.ts +11 -0
  38. package/dist/models/gpt-5-4.js +21 -0
  39. package/dist/models/gpt-5-5.d.ts +15 -0
  40. package/dist/models/gpt-5-5.js +24 -0
  41. package/dist/models/gpt-5.d.ts +11 -0
  42. package/dist/models/gpt-5.js +23 -0
  43. package/dist/models/index.d.ts +19 -0
  44. package/dist/models/index.js +49 -0
  45. package/dist/models/model.d.ts +69 -0
  46. package/dist/models/model.js +63 -0
  47. package/dist/models/multimodal.d.ts +35 -0
  48. package/dist/models/multimodal.js +78 -0
  49. package/dist/models/types.d.ts +49 -0
  50. package/dist/models/types.js +18 -0
  51. package/dist/types/index.d.ts +1 -0
  52. package/dist/types/model-connectivity-info.d.ts +87 -0
  53. package/dist/types/model-connectivity-info.js +6 -0
  54. package/dist/types/usage.d.ts +3 -3
  55. package/dist/types/wire-communication-event.d.ts +124 -0
  56. package/dist/types/wire-communication-event.js +6 -0
  57. package/dist/wire-communication-file-writer.d.ts +39 -0
  58. package/dist/wire-communication-file-writer.js +142 -0
  59. package/package.json +7 -8
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
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.22.0] - 2026-06-19
7
+
8
+ ### Features
9
+ - **BREAKING** Major refactor to Unify Connectivity via ModelConnectivityInfo @W-22782317 ([#607](https://github.com/forcedotcom/agentic-dx/pull/607))
10
+
11
+ ### Chores
12
+ - **deps-dev**: bump @vitest/eslint-plugin from 1.6.19 to 1.6.20 in the vitest group across 1 directory ([#601](https://github.com/forcedotcom/agentic-dx/pull/601))
13
+ - **deps-dev**: bump eslint from 10.4.1 to 10.5.0 in the eslint group across 1 directory ([#600](https://github.com/forcedotcom/agentic-dx/pull/600))
14
+ - **deps-dev**: bump @types/node from 22.19.20 to 22.19.21 in the dev-dependencies group ([#599](https://github.com/forcedotcom/agentic-dx/pull/599))
15
+
16
+ ## [0.21.0] - 2026-06-15
17
+
18
+ ### Fixes
19
+ - **harness-claude,harness-mastra,agent-sdk**: idempotent settle + quiet dispose (#589) ([#598](https://github.com/forcedotcom/agentic-dx/pull/598))
20
+
6
21
  ## [0.20.0] - 2026-06-12
7
22
 
8
23
  ### Features
package/README.md CHANGED
@@ -540,17 +540,18 @@ totals, subscribe to `chat-stream-completed` telemetry instead.
540
540
  The SDK throws `AgentSDKError` for predictable not-found and compatibility conditions. Each error has a `type` property
541
541
  from `AgentSDKErrorType`:
542
542
 
543
- | Type | Thrown By |
544
- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
545
- | `AGENT_NOT_FOUND` | `AgentManager.getAgent()`, `AgentManager.destroyAgent()` |
546
- | `CHAT_SESSION_NOT_FOUND` | `Agent.getChatSession()`, `Agent.destroyChatSession()`, `Agent.cloneChatSession()`, `Agent.compactChatSession()` |
547
- | `COMPACTION_FAILED` | `Agent.compactChatSession()` when the harness's underlying summarization call rejects. The original error is attached as `cause`; the source session is left intact. |
548
- | `DISPOSED` | `Agent` and `ChatSession` methods called after the owner has been destroyed |
549
- | `INCOMPATIBLE_HARNESS` | `createAgentManager()` when the factory advertises an unsupported `protocolVersion`, or the constructed harness reports a `protocolVersion` that differs from the factory's |
550
- | `INVALID_MESSAGE_CONTENT` | `ChatSession.chat()` / harness `stream()` when a message part is not valid as input (a `tool-call`/`tool-result` part, or non-base64-string file data) |
551
- | `MCP_SERVER_DISABLED` | `Agent.reconnectMcpServer()` when the named server is configured with `enabled: false` |
552
- | `MCP_SERVER_NOT_FOUND` | `Agent.reconnectMcpServer()` when the server name is not in the agent's `mcpServers` config |
553
- | `MULTIMODAL_NOT_SUPPORTED` | `ChatSession.chat()` / harness `stream()` when a file fails pre-stream capability validation (unsupported format, too large, or too many files) |
543
+ | Type | Thrown By |
544
+ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
545
+ | `AGENT_NOT_FOUND` | `AgentManager.getAgent()`, `AgentManager.destroyAgent()` |
546
+ | `CHAT_SESSION_NOT_FOUND` | `Agent.getChatSession()`, `Agent.destroyChatSession()`, `Agent.cloneChatSession()`, `Agent.compactChatSession()` |
547
+ | `COMPACTION_FAILED` | `Agent.compactChatSession()` when the harness's underlying summarization call rejects. The original error is attached as `cause`; the source session is left intact. |
548
+ | `DISPOSED` | `Agent` and `ChatSession` methods called after the owner has been destroyed |
549
+ | `INCOMPATIBLE_HARNESS` | `createAgentManager()` when the factory advertises an unsupported `protocolVersion`, or the constructed harness reports a `protocolVersion` that differs from the factory's |
550
+ | `INVALID_MESSAGE_CONTENT` | `ChatSession.chat()` / harness `stream()` when a message part is not valid as input (a `tool-call`/`tool-result` part, or non-base64-string file data) |
551
+ | `MCP_SERVER_DISABLED` | `Agent.reconnectMcpServer()` when the named server is configured with `enabled: false` |
552
+ | `MCP_SERVER_NOT_FOUND` | `Agent.reconnectMcpServer()` when the server name is not in the agent's `mcpServers` config |
553
+ | `MODEL_NOT_SUPPORTED_BY_HARNESS` | `AgentManager.createAgent()` / `Agent.updateAgentConfig()` (G8 pre-flight) when the resolved `ModelConnectivityInfo.providerHint` isn't in the harness's `supportedProviderHints`. Surfaces before any harness work runs (no MCP discovery, no subprocess spawn, no language-model construction) so the consumer can branch cleanly on `err.type` and recover without resource cleanup. |
554
+ | `MULTIMODAL_NOT_SUPPORTED` | `ChatSession.chat()` / harness `stream()` when a file fails pre-stream capability validation (unsupported format, too large, or too many files) |
554
555
 
555
556
  ```typescript
556
557
  import { AgentSDKError, AgentSDKErrorType } from '@salesforce/sfdx-agent-sdk';
@@ -1,54 +1,76 @@
1
- import { Model, type JSONWebToken, type LLMGatewayClient, type LLMGatewayClientFactory } from '@salesforce/llm-gateway-sdk';
2
- import { type OrgConnection, type OrgConnectionFactory } from '@salesforce/agentic-common';
1
+ import { Model } from './models/index.js';
2
+ import { type JSONWebToken, type OrgConnection, type OrgConnectionFactory } from '@salesforce/agentic-common';
3
3
  import type { AgentConfig } from './harness/harness-config.js';
4
+ import type { ModelConnectivityInfo } from './types/model-connectivity-info.js';
4
5
  /**
5
- * The result of resolving an agent's org connectivity: a ready-to-use authenticated
6
- * LLM gateway client and the underlying connection.
6
+ * The result of resolving an agent's connectivity.
7
+ *
8
+ * `modelConnectivityInfo` is the harness-facing bag. `orgConnection` and
9
+ * `orgJwt` are Salesforce-org-specific and only present when the resolver targets a
10
+ * Salesforce-org host; non-Salesforce hosts (MuleSoft, Commerce Vibes — see
11
+ * [#605](https://github.com/forcedotcom/agentic-dx/issues/605)) omit them. Downstream
12
+ * consumers (MCP auth, identity-derived headers) treat them as optional.
7
13
  */
8
14
  export type ResolvedConnectivity = {
9
- /** Pre-configured and authenticated LLM gateway client for the resolved org. */
10
- llmGatewayClient: LLMGatewayClient;
11
- /** Authenticated org connection carrying identity and env inference. */
12
- orgConnection: OrgConnection;
13
- /** Self-refreshing JWT for the resolved org, used for MCP auth injection. */
14
- orgJwt: JSONWebToken;
15
+ /**
16
+ * Connectivity facts the harness needs to make an LLM request: model, base URL,
17
+ * native model id, provider hint, and a per-call `getHeaders()`. See
18
+ * {@link ModelConnectivityInfo} for the contract.
19
+ */
20
+ modelConnectivityInfo: ModelConnectivityInfo;
21
+ /**
22
+ * Authenticated org connection carrying identity and env inference. Optional —
23
+ * non-Salesforce hosts (MuleSoft, Commerce Vibes) omit it.
24
+ */
25
+ orgConnection?: OrgConnection;
26
+ /**
27
+ * Self-refreshing JWT for the resolved org. Used for MCP auth injection and any
28
+ * other Salesforce-platform-auth need (identity, future Apex). Optional —
29
+ * non-Salesforce hosts (or hosts that authenticate the LLM via api-key without
30
+ * needing org auth for MCP) omit it.
31
+ */
32
+ orgJwt?: JSONWebToken;
15
33
  };
16
34
  /**
17
- * Resolves the org connectivity needed to create an agent for a given project and
35
+ * Resolves the connectivity needed to create an agent for a given project and
18
36
  * configuration.
19
37
  *
20
- * Implementations are responsible for resolving the org's authentication and constructing
21
- * an authenticated {@link LLMGatewayClient} and {@link OrgConnection} from an
22
- * {@link AgentConfig}.
38
+ * Implementations are responsible for producing a {@link ModelConnectivityInfo} bag
39
+ * the harness can use to make LLM requests. Salesforce-org hosts additionally produce
40
+ * an {@link OrgConnection} + {@link JSONWebToken} for MCP auth and identity; non-SF
41
+ * hosts omit those fields.
42
+ *
43
+ * The same `AgentConnectivityResolver` instance is invoked at `createAgent` time and
44
+ * at `updateAgentConfig` time when `orgAlias` or `modelId` change — implementations
45
+ * MUST be safe to call repeatedly with different `AgentConfig` values.
23
46
  */
24
47
  export interface AgentConnectivityResolver {
25
48
  /**
26
- * Resolve the org connectivity for an agent.
49
+ * Resolve the connectivity for an agent.
27
50
  *
28
51
  * @param projectRoot - The root directory of the project the agent operates within.
29
- * Used to find a project-local `target-org` when no `orgAlias` is configured.
52
+ * Used to find a project-local `target-org` when no `orgAlias` is configured
53
+ * (Salesforce-org hosts only).
30
54
  * @param config - The agent's configuration, including optional org alias and model selection.
31
- * @returns The resolved LLM gateway client and org connection metadata.
55
+ * @returns The resolved connectivity bag plus optional org handle / JWT.
32
56
  */
33
57
  resolve(projectRoot: string, config: AgentConfig): Promise<ResolvedConnectivity>;
34
58
  }
35
59
  /**
36
60
  * Default implementation of {@link AgentConnectivityResolver}.
37
61
  *
38
- * Delegates org resolution to {@link OrgConnectionFactory} and LLM client creation to
39
- * {@link LLMGatewayClientFactory}. Both dependencies are injectable for testing.
62
+ * Targets the Salesforce LLM Gateway with a Salesforce-org JWT. Produces a
63
+ * `ModelConnectivityInfo` bag (the harness-facing seam). The connection
64
+ * factory is injectable for testing.
40
65
  */
41
66
  export declare class DefaultAgentConnectivityResolver implements AgentConnectivityResolver {
42
67
  private readonly connectionFactory;
43
- private readonly gatewayClientFactory;
44
68
  /**
45
69
  * @param connectionFactory - Creates authenticated Salesforce org connections.
46
- * @param gatewayClientFactory - Creates authenticated LLM gateway clients.
47
70
  */
48
- constructor(connectionFactory?: OrgConnectionFactory, gatewayClientFactory?: LLMGatewayClientFactory);
71
+ constructor(connectionFactory?: OrgConnectionFactory);
49
72
  /**
50
- * Resolves the org, constructs an authenticated LLM gateway client, and
51
- * returns the underlying connection.
73
+ * Resolves the org and produces the harness-facing `ModelConnectivityInfo` bag.
52
74
  *
53
75
  * If `config.orgAlias` is set, resolves that alias explicitly; otherwise falls back
54
76
  * to the project-local or machine-default org via
@@ -56,7 +78,7 @@ export declare class DefaultAgentConnectivityResolver implements AgentConnectivi
56
78
  *
57
79
  * @param projectRoot - Project root for project-local org resolution.
58
80
  * @param config - Agent configuration with optional org alias and model ID.
59
- * @returns The resolved LLM gateway client and connection.
81
+ * @returns The resolved connectivity bag, org connection, and JWT.
60
82
  */
61
83
  resolve(projectRoot: string, config: AgentConfig): Promise<ResolvedConnectivity>;
62
84
  }
@@ -2,8 +2,8 @@
2
2
  * Copyright 2026, Salesforce, Inc. All rights reserved.
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
- import { DefaultLLMGatewayClientFactory, Model, ModelName, Models, createClaudeModel, createJWTFromConnection, } from '@salesforce/llm-gateway-sdk';
6
- import { SfApiEnv, RealOrgConnectionFactory, } from '@salesforce/agentic-common';
5
+ import { Model, ModelName, Models, createClaudeModel } from './models/index.js';
6
+ import { createJWTFromConnection, RealOrgConnectionFactory, SfApiEnv, } from '@salesforce/agentic-common';
7
7
  // TODO(@W-22782317): Temporary workaround — only on prod orgs the LLM Gateway must
8
8
  // route requests through AgentforceVibes rather than the default VibesService. Remove once a
9
9
  // long-term feature ID configuration strategy is in place.
@@ -11,23 +11,20 @@ const PROD_ORG_FEATURE_ID = 'AgentforceVibes';
11
11
  /**
12
12
  * Default implementation of {@link AgentConnectivityResolver}.
13
13
  *
14
- * Delegates org resolution to {@link OrgConnectionFactory} and LLM client creation to
15
- * {@link LLMGatewayClientFactory}. Both dependencies are injectable for testing.
14
+ * Targets the Salesforce LLM Gateway with a Salesforce-org JWT. Produces a
15
+ * `ModelConnectivityInfo` bag (the harness-facing seam). The connection
16
+ * factory is injectable for testing.
16
17
  */
17
18
  export class DefaultAgentConnectivityResolver {
18
19
  connectionFactory;
19
- gatewayClientFactory;
20
20
  /**
21
21
  * @param connectionFactory - Creates authenticated Salesforce org connections.
22
- * @param gatewayClientFactory - Creates authenticated LLM gateway clients.
23
22
  */
24
- constructor(connectionFactory = new RealOrgConnectionFactory(), gatewayClientFactory = new DefaultLLMGatewayClientFactory()) {
23
+ constructor(connectionFactory = new RealOrgConnectionFactory()) {
25
24
  this.connectionFactory = connectionFactory;
26
- this.gatewayClientFactory = gatewayClientFactory;
27
25
  }
28
26
  /**
29
- * Resolves the org, constructs an authenticated LLM gateway client, and
30
- * returns the underlying connection.
27
+ * Resolves the org and produces the harness-facing `ModelConnectivityInfo` bag.
31
28
  *
32
29
  * If `config.orgAlias` is set, resolves that alias explicitly; otherwise falls back
33
30
  * to the project-local or machine-default org via
@@ -35,7 +32,7 @@ export class DefaultAgentConnectivityResolver {
35
32
  *
36
33
  * @param projectRoot - Project root for project-local org resolution.
37
34
  * @param config - Agent configuration with optional org alias and model ID.
38
- * @returns The resolved LLM gateway client and connection.
35
+ * @returns The resolved connectivity bag, org connection, and JWT.
39
36
  */
40
37
  async resolve(projectRoot, config) {
41
38
  const orgConnection = config.orgAlias !== undefined
@@ -45,11 +42,91 @@ export class DefaultAgentConnectivityResolver {
45
42
  const env = orgConnection.getInferredSfApiEnv();
46
43
  const featureId = env === SfApiEnv.Prod ? PROD_ORG_FEATURE_ID : undefined;
47
44
  const orgJwt = await createJWTFromConnection(orgConnection, { featureId });
48
- const llmGatewayClient = this.gatewayClientFactory.create(orgJwt, { env });
49
- llmGatewayClient.setModel(resolveAgentConfigModel(config.modelId));
50
- return { llmGatewayClient, orgConnection, orgJwt };
45
+ const model = resolveAgentConfigModel(config.modelId);
46
+ const modelConnectivityInfo = {
47
+ model,
48
+ baseUrl: salesforceGatewayBaseUrl(env),
49
+ // The Salesforce gateway accepts the canonical `llmgateway__*` model id verbatim,
50
+ // so `nativeModelId === model.name` for every gateway-routed call.
51
+ nativeModelId: model.name,
52
+ providerHint: pickProviderHintForGatewayModel(model),
53
+ getHeaders: async () => buildSalesforceGatewayHeaders(orgJwt, model),
54
+ };
55
+ return { modelConnectivityInfo, orgConnection, orgJwt };
51
56
  }
52
57
  }
58
+ /**
59
+ * Maps a Salesforce gateway-routed {@link Model} to the wire shape the harness will
60
+ * speak. Anthropic models go to the `/invoke-with-response-stream` (Bedrock-Anthropic)
61
+ * pass-through endpoint; OpenAI models go to the `/responses` (OpenAI Responses)
62
+ * pass-through endpoint. Throws for any model whose family cannot be inferred from
63
+ * its `name` — a programming error worth surfacing eagerly rather than silently
64
+ * defaulting.
65
+ *
66
+ * **Scope:** This helper is for the Salesforce gateway path only — both
67
+ * `llmgateway__BedrockAnthropic*` and `llmgateway__Anthropic*` prefixes go to the
68
+ * gateway's Bedrock pass-through, so both collapse to `'bedrock-anthropic'`. A
69
+ * consumer wanting direct-Anthropic auth (`providerHint: 'anthropic'`) drives that
70
+ * path through {@link ApiKeyConnectivityResolver} with an explicit `providerHint`
71
+ * argument, not through this resolver.
72
+ */
73
+ function pickProviderHintForGatewayModel(model) {
74
+ const name = model.name;
75
+ if (name.startsWith('llmgateway__BedrockAnthropic') || name.startsWith('llmgateway__Anthropic')) {
76
+ return 'bedrock-anthropic';
77
+ }
78
+ if (name.startsWith('llmgateway__OpenAI')) {
79
+ return 'openai-responses';
80
+ }
81
+ throw new Error(`Cannot infer providerHint for model "${name}". Salesforce gateway models must start with "llmgateway__BedrockAnthropic*" or "llmgateway__OpenAI*".`);
82
+ }
83
+ /**
84
+ * Returns the Salesforce LLM Gateway base URL for the given environment.
85
+ *
86
+ * Includes the `/ai/gpt/v1` API root: the pass-through endpoints
87
+ * (`/responses` for OpenAI Responses, `/model/<id>/invoke-with-response-stream`
88
+ * for Bedrock-Anthropic) live under this prefix, and provider SDKs
89
+ * (`@anthropic-ai/bedrock-sdk`, `openai`) construct their per-model paths by
90
+ * concatenation onto the base URL the consumer supplies.
91
+ */
92
+ function salesforceGatewayBaseUrl(env) {
93
+ switch (env) {
94
+ case SfApiEnv.Dev:
95
+ return 'https://dev.api.salesforce.com/ai/gpt/v1';
96
+ case SfApiEnv.Perf:
97
+ return 'https://perf.api.salesforce.com/ai/gpt/v1';
98
+ case SfApiEnv.Stage:
99
+ return 'https://stage.api.salesforce.com/ai/gpt/v1';
100
+ case SfApiEnv.Test:
101
+ return 'https://test.api.salesforce.com/ai/gpt/v1';
102
+ case SfApiEnv.Prod:
103
+ default:
104
+ return 'https://api.salesforce.com/ai/gpt/v1';
105
+ }
106
+ }
107
+ /**
108
+ * Builds the full header set for a Salesforce LLM Gateway request, including the
109
+ * Authorization Bearer JWT, tenant-key, feature-id, and any model-specific custom
110
+ * headers. Re-evaluated on every harness call so JWT rotations land on the next
111
+ * request without rebuilding the bag.
112
+ */
113
+ async function buildSalesforceGatewayHeaders(orgJwt, model) {
114
+ const [jwtValue, tenantKey] = await Promise.all([orgJwt.getValue(), orgJwt.getTenantKey()]);
115
+ // Order matters: spread `customHeaders` FIRST so the resolver-set auth /
116
+ // tenant / feature-id headers win on conflict. `customHeaders` is for
117
+ // vendor-specific labels (`anthropic-version`, model-tag headers, etc.) —
118
+ // not for auth or tenancy. A `Model` instance built with
119
+ // `customHeaders: { Authorization: '...' }` must NOT shadow the
120
+ // resolver's freshly-fetched JWT.
121
+ return {
122
+ ...(model.customHeaders ?? {}),
123
+ Authorization: `Bearer ${jwtValue}`,
124
+ 'Content-Type': 'application/json;charset=utf-8',
125
+ 'x-client-feature-id': orgJwt.getFeatureId(),
126
+ 'x-sfdc-app-context': 'EinsteinGPT',
127
+ 'x-sfdc-core-tenant-id': tenantKey,
128
+ };
129
+ }
53
130
  /**
54
131
  * Resolves an `AgentConfig.modelId` value (which may be a {@link ModelName} enum value, a
55
132
  * pre-built {@link Model} instance, or `undefined`) to a concrete {@link Model}.
@@ -68,7 +145,7 @@ export function resolveAgentConfigModel(modelId) {
68
145
  if (modelId === undefined)
69
146
  return Models.getDefault();
70
147
  // Known limitation: `instanceof Model` is realm-scoped — a consumer that ends up with two copies
71
- // of `@salesforce/llm-gateway-sdk` resolved in their dependency tree will have their `Model`
148
+ // of `@salesforce/sfdx-agent-sdk` resolved in their dependency tree will have their `Model`
72
149
  // instance fail this check and fall through to `rehydratePersistedModel`. That branch handles
73
150
  // it correctly for Claude variants but throws for anything else. The duplicate-package case is
74
151
  // a packaging bug at the consumer; we don't paper over it here.
@@ -5,6 +5,8 @@ import { type AgentConfig } from './harness/harness-config.js';
5
5
  import { type Agent } from './agent.js';
6
6
  import type { HooksForAgent } from './types/redaction.js';
7
7
  import type { TelemetryEventCallback } from './types/telemetry-events.js';
8
+ import type { WireCommunicationEventCallback } from './types/wire-communication-event.js';
9
+ import type { ProviderHint } from './types/model-connectivity-info.js';
8
10
  import { type AgentConnectivityResolver } from './agent-connectivity-resolver.js';
9
11
  /**
10
12
  * Per-agent failure recorded during the boot-time restore pass.
@@ -104,6 +106,16 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
104
106
  onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
105
107
  /** Subscribe to structured log records across every managed agent. */
106
108
  onLog(callback: (record: LogRecord) => void): Unsubscribe;
109
+ /**
110
+ * Subscribe to wire-communication events emitted by the harness across
111
+ * every managed agent — raw HTTP request/response captures of the LLM
112
+ * provider's traffic. The events may contain user prompts and PII; the
113
+ * channel is opt-in and not flowed to log files by default. Use
114
+ * {@link WireCommunicationFileWriter} to dump events to disk for
115
+ * debugging. Fidelity differs by harness — see
116
+ * `AgentHarness.onWireCommunication` for the cross-harness contract.
117
+ */
118
+ onWireCommunication(callback: WireCommunicationEventCallback): Unsubscribe;
107
119
  /**
108
120
  * Returns a snapshot of the boot-time restore failures the SDK has not
109
121
  * yet been told to forget. Each entry is cleared on a successful
@@ -123,6 +135,7 @@ export interface AgentManager<H extends AgentHarness = AgentHarness> {
123
135
  */
124
136
  export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness> implements AgentManager<H> {
125
137
  private readonly harness;
138
+ private readonly harnessSupportedProviderHints;
126
139
  private readonly agentIdGenerator;
127
140
  private readonly agentConnectivityResolver;
128
141
  private readonly hooksForAgent;
@@ -132,7 +145,9 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
132
145
  private restoreFailures;
133
146
  private readonly telemetryBus;
134
147
  private readonly logBus;
148
+ private readonly wireBus;
135
149
  private readonly router;
150
+ private readonly wireRouter;
136
151
  private readonly unroutedUnsubs;
137
152
  private disposed;
138
153
  private constructor();
@@ -146,7 +161,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
146
161
  * is private, so this is the only way to obtain an instance, but
147
162
  * consumers should always go through {@link createAgentManager}.
148
163
  */
149
- static __build<H extends AgentHarness>(harness: H, agentConnectivityResolver: AgentConnectivityResolver, hooksForAgent: HooksForAgent | undefined, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
164
+ static __build<H extends AgentHarness>(harness: H, harnessSupportedProviderHints: readonly ProviderHint[], agentConnectivityResolver: AgentConnectivityResolver, hooksForAgent: HooksForAgent | undefined, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
150
165
  private init;
151
166
  shutdown(): Promise<void>;
152
167
  createAgent(projectRoot: string, config?: ConfigOf<H> & {
@@ -172,6 +187,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
172
187
  destroyAgent(agentId: string): Promise<void>;
173
188
  onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
174
189
  onLog(callback: (record: LogRecord) => void): Unsubscribe;
190
+ onWireCommunication(callback: WireCommunicationEventCallback): Unsubscribe;
175
191
  getRestoreFailures(): RestoreFailure[];
176
192
  /**
177
193
  * Re-exposes `harness.extensions` typed off `H`. Read-only; the SDK never
@@ -10,6 +10,7 @@ import { toHarnessConfig } from './harness/harness-config.js';
10
10
  import { DefaultAgent } from './agent.js';
11
11
  import { AgentSDKError, AgentSDKErrorType } from './errors.js';
12
12
  import { TelemetryRouter } from './internal/telemetry-router.js';
13
+ import { WireCommunicationRouter } from './internal/wire-communication-router.js';
13
14
  import { AgentIdentityStore } from './internal/agent-identity-store.js';
14
15
  import { DefaultAgentConnectivityResolver } from './agent-connectivity-resolver.js';
15
16
  /**
@@ -23,6 +24,7 @@ import { DefaultAgentConnectivityResolver } from './agent-connectivity-resolver.
23
24
  */
24
25
  export class DefaultAgentManager {
25
26
  harness;
27
+ harnessSupportedProviderHints;
26
28
  agentIdGenerator;
27
29
  agentConnectivityResolver;
28
30
  hooksForAgent;
@@ -32,11 +34,14 @@ export class DefaultAgentManager {
32
34
  restoreFailures = [];
33
35
  telemetryBus = new EventBus();
34
36
  logBus;
37
+ wireBus = new EventBus();
35
38
  router;
39
+ wireRouter;
36
40
  unroutedUnsubs;
37
41
  disposed = false;
38
- constructor(harness, agentConnectivityResolver, hooksForAgent, identityStore, agentIdGenerator, clock, logBus) {
42
+ constructor(harness, harnessSupportedProviderHints, agentConnectivityResolver, hooksForAgent, identityStore, agentIdGenerator, clock, logBus) {
39
43
  this.harness = harness;
44
+ this.harnessSupportedProviderHints = harnessSupportedProviderHints;
40
45
  this.agentConnectivityResolver = agentConnectivityResolver;
41
46
  this.hooksForAgent = hooksForAgent;
42
47
  this.identityStore = identityStore;
@@ -44,9 +49,15 @@ export class DefaultAgentManager {
44
49
  this.clock = clock;
45
50
  this.logBus = logBus;
46
51
  this.router = new TelemetryRouter(harness);
52
+ this.wireRouter = new WireCommunicationRouter(harness);
47
53
  this.unroutedUnsubs = [
48
54
  this.router.unrouted.telemetry.forwardTo(this.telemetryBus),
49
55
  this.router.unrouted.log.forwardTo(this.logBus),
56
+ // Wire-communication uses `forwardWhileSubscribed` — when no consumer subscribes to
57
+ // `manager.onWireCommunication`, the link stays cold all the way back to the harness bus.
58
+ // The Claude harness reads `wireBus.listenerCount > 0` before each subprocess spawn and
59
+ // skips the (expensive) `ANTHROPIC_LOG=debug` env when nobody is listening.
60
+ this.wireRouter.unrouted.wire.forwardWhileSubscribed(this.wireBus),
50
61
  ];
51
62
  }
52
63
  /**
@@ -59,9 +70,9 @@ export class DefaultAgentManager {
59
70
  * is private, so this is the only way to obtain an instance, but
60
71
  * consumers should always go through {@link createAgentManager}.
61
72
  */
62
- static async __build(harness, agentConnectivityResolver, hooksForAgent, storageRootFolder, agentIdGenerator, clock, logBus) {
73
+ static async __build(harness, harnessSupportedProviderHints, agentConnectivityResolver, hooksForAgent, storageRootFolder, agentIdGenerator, clock, logBus) {
63
74
  const identityStore = new AgentIdentityStore(storageRootFolder, harness.harnessId, logBus);
64
- const manager = new DefaultAgentManager(harness, agentConnectivityResolver, hooksForAgent, identityStore, agentIdGenerator, clock, logBus);
75
+ const manager = new DefaultAgentManager(harness, harnessSupportedProviderHints, agentConnectivityResolver, hooksForAgent, identityStore, agentIdGenerator, clock, logBus);
65
76
  await manager.init();
66
77
  return manager;
67
78
  }
@@ -103,8 +114,10 @@ export class DefaultAgentManager {
103
114
  for (const unsub of this.unroutedUnsubs)
104
115
  unsub();
105
116
  this.router.dispose();
117
+ this.wireRouter.dispose();
106
118
  this.telemetryBus.dispose();
107
119
  this.logBus.dispose();
120
+ this.wireBus.dispose();
108
121
  this.disposed = true;
109
122
  }
110
123
  async createAgent(projectRoot, config = {}, options) {
@@ -157,17 +170,31 @@ export class DefaultAgentManager {
157
170
  throw new Error(`projectRoot is not a directory: "${projectRoot}"`);
158
171
  }
159
172
  const runtime = await this.agentConnectivityResolver.resolve(projectRoot, config);
173
+ // G8 validation — fail before any harness work runs. The harness's static
174
+ // `supportedProviderHints` is the source of truth; the resolver picks one of
175
+ // them. If they disagree, the consumer wired the wrong harness for the model
176
+ // (e.g. a Claude harness pointed at GPT-5).
177
+ const providerHint = runtime.modelConnectivityInfo.providerHint;
178
+ if (!this.harnessSupportedProviderHints.includes(providerHint)) {
179
+ throw new AgentSDKError(`Harness "${this.harness.harnessId}" does not support providerHint "${providerHint}". Supported: ${this.harnessSupportedProviderHints.join(', ')}.`, AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS);
180
+ }
160
181
  const hooks = this.hooksForAgent?.(agentId, config) ?? {};
161
- await this.harness.createAgent(agentId, projectRoot, runtime.llmGatewayClient, toHarnessConfig(config, runtime.orgJwt), { ...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}), hooks });
182
+ await this.harness.createAgent(agentId, projectRoot, runtime.modelConnectivityInfo, toHarnessConfig(config, runtime.orgJwt), { ...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}), hooks });
162
183
  const agentSlice = this.router.registerAgent(agentId);
163
- const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.llmGatewayClient, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.hooksForAgent, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
184
+ // Forward-compat: register the agent against the wire-communication router
185
+ // too. Today every WireCommunicationEvent lands on the unrouted slice (no
186
+ // routing fields on the event shape), but the per-agent slice is allocated
187
+ // symmetrically with the telemetry router so the wiring is in place if/when
188
+ // the event shape grows an agentId field.
189
+ this.wireRouter.registerAgent(agentId);
190
+ const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.hooksForAgent, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
164
191
  this.agents.set(agentId, agent);
165
192
  this.telemetryBus.emit({
166
193
  type: 'agent-created',
167
194
  timestamp: this.clock.now(),
168
195
  agentId,
169
196
  projectRoot,
170
- modelName: runtime.llmGatewayClient.getModel().name,
197
+ modelName: runtime.modelConnectivityInfo.model.name,
171
198
  });
172
199
  if (options.rehydrateThreads) {
173
200
  // If thread enumeration or session attachment fails, the restore is a full failure
@@ -195,6 +222,7 @@ export class DefaultAgentManager {
195
222
  // Swallow secondary failure; the original error (passed to caller) is what matters.
196
223
  }
197
224
  this.router.unregisterAgent(agentId);
225
+ this.wireRouter.unregisterAgent(agentId);
198
226
  this.agents.delete(agentId);
199
227
  }
200
228
  getAgentIds() {
@@ -219,6 +247,7 @@ export class DefaultAgentManager {
219
247
  if (agent) {
220
248
  await agent.destroy();
221
249
  this.router.unregisterAgent(agentId);
250
+ this.wireRouter.unregisterAgent(agentId);
222
251
  this.agents.delete(agentId);
223
252
  }
224
253
  if (failureIndex !== -1) {
@@ -234,6 +263,10 @@ export class DefaultAgentManager {
234
263
  this.assertNotDisposed();
235
264
  return this.logBus.on(callback);
236
265
  }
266
+ onWireCommunication(callback) {
267
+ this.assertNotDisposed();
268
+ return this.wireBus.on(callback);
269
+ }
237
270
  getRestoreFailures() {
238
271
  this.assertNotDisposed();
239
272
  return [...this.restoreFailures];
@@ -318,7 +351,7 @@ export async function createAgentManager(storageRootFolder, harnessFactory, opti
318
351
  `Update the SDK or harness package.`, AgentSDKErrorType.INCOMPATIBLE_HARNESS);
319
352
  }
320
353
  const agentConnectivityResolver = options?.connectivityResolver ?? new DefaultAgentConnectivityResolver();
321
- return DefaultAgentManager.__build(harness, agentConnectivityResolver, options?.hooksForAgent, storageRootFolder, new UUIDGenerator(), new RealClock(), new LogBus());
354
+ return DefaultAgentManager.__build(harness, harnessFactory.supportedProviderHints, agentConnectivityResolver, options?.hooksForAgent, storageRootFolder, new UUIDGenerator(), new RealClock(), new LogBus());
322
355
  }
323
356
  function isSupportedProtocolVersion(version) {
324
357
  return (typeof version === 'number' &&
package/dist/agent.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { type Clock, LogBus, type LogRecord, type OrgConnection, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
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
3
  import { type AgentConfig } from './harness/harness-config.js';
4
4
  import { type ChatSession } from './chat-session.js';
5
5
  import type { McpServerInfo } from './mcp-config.js';
6
- import { type JSONWebToken, type LLMGatewayClient } from '@salesforce/llm-gateway-sdk';
7
- import { type AgentConnectivityResolver } from './agent-connectivity-resolver.js';
6
+ import type { AgentConnectivityResolver } from './agent-connectivity-resolver.js';
7
+ import type { ModelConnectivityInfo, ProviderHint } from './types/model-connectivity-info.js';
8
8
  import type { AgentIdentityStore } from './internal/agent-identity-store.js';
9
9
  import type { TelemetryRouter, TelemetrySlice } from './internal/telemetry-router.js';
10
10
  import type { HooksForAgent } from './types/redaction.js';
@@ -32,8 +32,12 @@ export interface Agent {
32
32
  getId(): string;
33
33
  /** Returns the project root folder this agent operates within. */
34
34
  getProjectRoot(): string;
35
- /** Returns the authenticated org connection for the org this agent runs under. */
36
- getOrgConnection(): OrgConnection;
35
+ /**
36
+ * Returns the authenticated org connection for the org this agent runs under, or
37
+ * `undefined` if the connectivity resolver did not produce one (non-Salesforce
38
+ * hosts; api-key resolvers without an `orgAlias`).
39
+ */
40
+ getOrgConnection(): OrgConnection | undefined;
37
41
  /** Returns the current agent configuration. */
38
42
  getAgentConfig(): AgentConfig;
39
43
  /**
@@ -73,10 +77,25 @@ export interface Agent {
73
77
  * to the live agent (e.g., new instructions, tools, model).
74
78
  *
75
79
  * @param config - Partial configuration to merge with the current config.
76
- * @param options - Optional execution options, including abort signals.
80
+ * @param options - Optional execution options.
81
+ * - `abortSignal` — caller-side cancellation; threaded through to the
82
+ * harness's `updateAgent` call.
83
+ * - `forceResolve` — when `true`, re-run the connectivity resolver even
84
+ * if neither `orgAlias` nor `modelId` is in the partial. Useful when
85
+ * a consumer-side state change the resolver reads (e.g. a BYOK
86
+ * toggle, a feature-id flip, a rate-limit gate state) needs to land
87
+ * on the next outbound call without an `orgAlias` / `modelId` flip
88
+ * on the SDK surface. Without this flag, partials that don't touch
89
+ * either field skip the resolver to avoid unnecessary work. Note
90
+ * that **only** `orgAlias` and `modelId` automatically trigger
91
+ * re-resolution when present as own properties of the partial; all
92
+ * other `AgentConfig` fields (`instructions`, `tools`, `mcpServers`,
93
+ * `rules`, `skills`, `name`, `description`, etc.) flow to the
94
+ * harness via `updateAgent` without re-running the resolver.
77
95
  */
78
96
  updateAgentConfig(config?: AgentConfig, options?: {
79
97
  abortSignal?: AbortSignal;
98
+ forceResolve?: boolean;
80
99
  }): Promise<void>;
81
100
  /**
82
101
  * Create a new chat session (conversation thread) for this agent.
@@ -134,10 +153,11 @@ export declare class DefaultAgent implements Agent {
134
153
  private readonly agentId;
135
154
  private readonly projectRoot;
136
155
  private config;
137
- private llmGatewayClient;
156
+ private modelConnectivityInfo;
138
157
  private orgConnection;
139
158
  private orgJwt;
140
159
  private readonly agentConnectivityResolver;
160
+ private readonly harnessSupportedProviderHints;
141
161
  private readonly hooksForAgent;
142
162
  private readonly identityStore;
143
163
  private readonly sessions;
@@ -155,7 +175,9 @@ export declare class DefaultAgent implements Agent {
155
175
  * @param agentId - Unique identifier for this agent.
156
176
  * @param projectRoot - Project folder this agent is allowed to operate within.
157
177
  * @param config - Initial agent configuration (instructions, model, tools, etc.).
158
- * @param llmGatewayClient - Authenticated LLM gateway client for the resolved org.
178
+ * @param modelConnectivityInfo - Connectivity bag (model, baseUrl, nativeModelId,
179
+ * providerHint, getHeaders) the harness uses to talk to the LLM. Replaced
180
+ * on every `updateAgentConfig` re-resolve.
159
181
  * @param orgConnection - Authenticated org connection carrying identity and env inference.
160
182
  * @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
161
183
  * @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
@@ -169,7 +191,7 @@ export declare class DefaultAgent implements Agent {
169
191
  * @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
170
192
  * @param parent - Manager's bus pair; this agent forwards its events upward into them.
171
193
  */
172
- constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, llmGatewayClient: LLMGatewayClient, orgConnection: OrgConnection, orgJwt: JSONWebToken, agentConnectivityResolver: AgentConnectivityResolver, hooksForAgent: HooksForAgent | undefined, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
194
+ constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[], hooksForAgent: HooksForAgent | undefined, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
173
195
  /**
174
196
  * @requirements
175
197
  * - MUST return the agent's ID.
@@ -177,7 +199,7 @@ export declare class DefaultAgent implements Agent {
177
199
  getId(): string;
178
200
  /** Returns the project root folder this agent operates within. */
179
201
  getProjectRoot(): string;
180
- getOrgConnection(): OrgConnection;
202
+ getOrgConnection(): OrgConnection | undefined;
181
203
  /**
182
204
  * @requirements
183
205
  * - MUST return a shallow copy of the internal `config` object to prevent external mutation of the agent's state.
@@ -203,6 +225,7 @@ export declare class DefaultAgent implements Agent {
203
225
  */
204
226
  updateAgentConfig(config?: AgentConfig, options?: {
205
227
  abortSignal?: AbortSignal;
228
+ forceResolve?: boolean;
206
229
  }): Promise<void>;
207
230
  /**
208
231
  * @requirements