@salesforce/sfdx-agent-harness-mastra 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.
- package/CHANGELOG.md +16 -0
- package/README.md +29 -17
- package/dist/index.d.ts +0 -3
- package/dist/index.js +7 -2
- package/dist/mastra-harness-factory.d.ts +2 -8
- package/dist/mastra-harness-factory.js +10 -1
- package/dist/mastra-harness.d.ts +42 -10
- package/dist/mastra-harness.js +131 -10
- package/dist/mcp-config-mapper.d.ts +9 -2
- package/dist/mcp-config-mapper.js +11 -4
- package/dist/pass-through-anthropic.d.ts +75 -0
- package/dist/pass-through-anthropic.js +454 -0
- package/dist/pass-through-openai-chat-completions.d.ts +52 -0
- package/dist/pass-through-openai-chat-completions.js +389 -0
- package/dist/pass-through-openai.d.ts +41 -0
- package/dist/pass-through-openai.js +373 -0
- package/dist/v3-stream-mapping.d.ts +247 -0
- package/dist/v3-stream-mapping.js +365 -0
- package/package.json +14 -12
- package/dist/default-model-config-factory.d.ts +0 -16
- package/dist/default-model-config-factory.js +0 -28
- package/dist/llmg-express-model-config-factory.d.ts +0 -26
- package/dist/llmg-express-model-config-factory.js +0 -37
- package/dist/model-config-factory.d.ts +0 -33
- package/dist/model-config-factory.js +0 -6
- package/dist/salesforce-llm-gateway.d.ts +0 -49
- package/dist/salesforce-llm-gateway.js +0 -365
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to `@salesforce/sfdx-agent-harness-mastra` 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-22
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **agentic-common,harnesses**: route HTTPS_PROXY via injectable undici Dispatcher @W-23121596 ([#610](https://github.com/forcedotcom/agentic-dx/pull/610))
|
|
10
|
+
|
|
11
|
+
## [0.21.0] - 2026-06-19
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- **BREAKING** Major refactor to Unify Connectivity via ModelConnectivityInfo @W-22782317 ([#607](https://github.com/forcedotcom/agentic-dx/pull/607))
|
|
15
|
+
|
|
16
|
+
### Chores
|
|
17
|
+
- **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))
|
|
18
|
+
- **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))
|
|
19
|
+
- **deps**: bump the mastra group across 1 directory with 4 updates ([#602](https://github.com/forcedotcom/agentic-dx/pull/602))
|
|
20
|
+
- **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))
|
|
21
|
+
|
|
6
22
|
## [0.20.0] - 2026-06-15
|
|
7
23
|
|
|
8
24
|
### Fixes
|
package/README.md
CHANGED
|
@@ -44,15 +44,26 @@ Linux and macOS release the handle eagerly; the retry options are no-ops on thos
|
|
|
44
44
|
|
|
45
45
|
### Core
|
|
46
46
|
|
|
47
|
-
| Export
|
|
48
|
-
|
|
|
49
|
-
| `MastraHarnessFactory`
|
|
50
|
-
| `MastraHarnessFactoryConfig`
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
| Export | Kind | Description |
|
|
48
|
+
| ---------------------------- | --------- | -------------------------------------------------------------------------------------- |
|
|
49
|
+
| `MastraHarnessFactory` | class | `HarnessFactory` that produces a Mastra-backed `AgentHarness` bound to a storage root. |
|
|
50
|
+
| `MastraHarnessFactoryConfig` | interface | Configuration for `MastraHarnessFactory` (`toolApprovalTimeoutMs?`). |
|
|
51
|
+
|
|
52
|
+
Connectivity (model + provider auth) is supplied by the SDK's `AgentConnectivityResolver`. Use the bundled
|
|
53
|
+
`DefaultAgentConnectivityResolver` for the standard Salesforce LLM Gateway path, or `ApiKeyConnectivityResolver` for
|
|
54
|
+
direct-provider / LLMG Express api-key endpoints — both ship from `@salesforce/sfdx-agent-sdk`. The harness dispatches
|
|
55
|
+
on `ModelConnectivityInfo.providerHint` internally to pick the right pass-through builder for each provider.
|
|
56
|
+
|
|
57
|
+
#### HTTPS proxy routing
|
|
58
|
+
|
|
59
|
+
The harness honors `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` (either casing) via
|
|
60
|
+
`@salesforce/agentic-common`'s `resolveProxyDispatcher()`. When either proxy var is set at factory `create()` time, the
|
|
61
|
+
harness wraps every in-process `fetch` call site (LLM gateway language-model builders + MCP remote transports) with an
|
|
62
|
+
undici-backed routing function — no `globalThis` mutation. When no proxy env is set, `fetch` runs unwrapped (zero
|
|
63
|
+
overhead).
|
|
64
|
+
|
|
65
|
+
**Snapshot timing.** `HTTPS_PROXY` / `HTTP_PROXY` are captured at `create()` time and pinned for the harness's lifetime;
|
|
66
|
+
`NO_PROXY` is re-evaluated per request. Set the proxy env vars before constructing the factory's first harness.
|
|
56
67
|
|
|
57
68
|
### Extension surface
|
|
58
69
|
|
|
@@ -236,19 +247,20 @@ contract.
|
|
|
236
247
|
|
|
237
248
|
### Using LLM Gateway Express
|
|
238
249
|
|
|
250
|
+
LLMG Express, Anthropic-direct, OpenAI-direct, and any other api-key endpoint flow through the SDK's
|
|
251
|
+
`ApiKeyConnectivityResolver` (no harness-specific factory needed):
|
|
252
|
+
|
|
239
253
|
```ts
|
|
240
|
-
import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
241
|
-
import { MastraHarnessFactory
|
|
254
|
+
import { ApiKeyConnectivityResolver, createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
255
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
242
256
|
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
const manager = await createAgentManager('/path/to/storage', new MastraHarnessFactory(), {
|
|
258
|
+
connectivityResolver: new ApiKeyConnectivityResolver({
|
|
259
|
+
getApiKey: () => process.env.LLMG_EXPRESS_API_KEY!,
|
|
246
260
|
baseUrl: process.env.LLMG_EXPRESS_BASE_URL!,
|
|
247
|
-
|
|
261
|
+
providerHint: 'openai-compatible',
|
|
248
262
|
}),
|
|
249
263
|
});
|
|
250
|
-
|
|
251
|
-
const manager = await createAgentManager('/path/to/storage', harnessFactory);
|
|
252
264
|
```
|
|
253
265
|
|
|
254
266
|
## Development
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
export { MastraHarnessFactory, type MastraHarnessFactoryConfig } from './mastra-harness-factory.js';
|
|
2
2
|
export type { MastraAgentHarness } from './mastra-harness.js';
|
|
3
3
|
export { type MastraAgentConfig, type MastraSkillSearchConfig, type MastraToolSearchConfig, type SkillSearchHandle, type ToolSearchHandle, } from './tool-search.js';
|
|
4
|
-
export type { MastraModelConfigFactory, ResolvedModelConfig } from './model-config-factory.js';
|
|
5
|
-
export { DefaultMastraModelConfigFactory } from './default-model-config-factory.js';
|
|
6
|
-
export { LLMGExpressMastraModelConfigFactory, type LLMGExpressConfig } from './llmg-express-model-config-factory.js';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { MastraHarnessFactory } from './mastra-harness-factory.js';
|
|
6
6
|
export {} from './tool-search.js';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Mastra drives requests through provider-native pass-through endpoints —
|
|
8
|
+
// Bedrock-Anthropic, direct Anthropic Messages API, OpenAI Responses, and
|
|
9
|
+
// OpenAI Chat Completions (LLMG Express / openai-compatible) — dispatched on
|
|
10
|
+
// the connectivity resolver's `ModelConnectivityInfo.providerHint`. There's
|
|
11
|
+
// no consumer-facing knob for switching the wire path; the resolver drives
|
|
12
|
+
// it. For BYOK / direct-Anthropic / LLMG-Express deployments use
|
|
13
|
+
// `ApiKeyConnectivityResolver` from `@salesforce/sfdx-agent-sdk`.
|
|
9
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import type { HarnessFactory } from '@salesforce/sfdx-agent-sdk';
|
|
1
|
+
import type { HarnessFactory, ProviderHint } from '@salesforce/sfdx-agent-sdk';
|
|
2
2
|
import { type MastraAgentHarness } from './mastra-harness.js';
|
|
3
|
-
import type { MastraModelConfigFactory } from './model-config-factory.js';
|
|
4
3
|
export interface MastraHarnessFactoryConfig {
|
|
5
|
-
/**
|
|
6
|
-
* Strategy for resolving the Mastra model configuration. Defaults to
|
|
7
|
-
* `DefaultMastraModelConfigFactory`, which targets the standard LLM
|
|
8
|
-
* Gateway path.
|
|
9
|
-
*/
|
|
10
|
-
modelConfigFactory?: MastraModelConfigFactory;
|
|
11
4
|
/**
|
|
12
5
|
* How long to wait for a `requireToolApproval` consumer to call
|
|
13
6
|
* `approveToolCall` / `declineToolCall` before auto-denying via
|
|
@@ -23,6 +16,7 @@ export declare class MastraHarnessFactory implements HarnessFactory<MastraAgentH
|
|
|
23
16
|
private readonly config;
|
|
24
17
|
readonly harnessId = "mastra";
|
|
25
18
|
readonly protocolVersion = 1;
|
|
19
|
+
readonly supportedProviderHints: readonly ProviderHint[];
|
|
26
20
|
constructor(config?: MastraHarnessFactoryConfig);
|
|
27
21
|
create(storageRootFolder: string): Promise<MastraAgentHarness>;
|
|
28
22
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
3
|
* See LICENSE.txt for license terms.
|
|
4
4
|
*/
|
|
5
|
+
import { createProxyAwareFetch, resolveProxyDispatcher } from '@salesforce/agentic-common';
|
|
5
6
|
import { MastraHarness } from './mastra-harness.js';
|
|
6
7
|
const DEFAULT_TOOL_APPROVAL_TIMEOUT_MS = 600_000;
|
|
7
8
|
/** Mastra-backed factory for creating AgentHarness instances. */
|
|
@@ -9,13 +10,21 @@ export class MastraHarnessFactory {
|
|
|
9
10
|
config;
|
|
10
11
|
harnessId = 'mastra';
|
|
11
12
|
protocolVersion = 1;
|
|
13
|
+
supportedProviderHints = [
|
|
14
|
+
'anthropic',
|
|
15
|
+
'bedrock-anthropic',
|
|
16
|
+
'openai',
|
|
17
|
+
'openai-responses',
|
|
18
|
+
'openai-compatible',
|
|
19
|
+
];
|
|
12
20
|
constructor(config = {}) {
|
|
13
21
|
this.config = config;
|
|
14
22
|
}
|
|
15
23
|
async create(storageRootFolder) {
|
|
24
|
+
const innerFetch = createProxyAwareFetch(resolveProxyDispatcher());
|
|
16
25
|
return new MastraHarness(storageRootFolder, {
|
|
17
|
-
modelConfigFactory: this.config.modelConfigFactory,
|
|
18
26
|
toolApprovalTimeoutMs: this.config.toolApprovalTimeoutMs ?? DEFAULT_TOOL_APPROVAL_TIMEOUT_MS,
|
|
27
|
+
innerFetch,
|
|
19
28
|
});
|
|
20
29
|
}
|
|
21
30
|
}
|
package/dist/mastra-harness.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Agent } from '@mastra/core/agent';
|
|
2
2
|
import { SkillSearchProcessor, ToolSearchProcessor } from '@mastra/core/processors';
|
|
3
3
|
import { MCPClient } from '@mastra/mcp';
|
|
4
|
-
import { McpServerStatus, type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type MessagePart, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo } from '@salesforce/sfdx-agent-sdk';
|
|
4
|
+
import { McpServerStatus, type AgentHarness, type WithAgentConfig, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type MessagePart, type ModelConnectivityInfo, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo, type WireCommunicationEventCallback } from '@salesforce/sfdx-agent-sdk';
|
|
5
5
|
import { type AgentHooks } from '@salesforce/sfdx-agent-sdk/harness';
|
|
6
6
|
import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
|
|
7
|
-
import type { MastraModelConfigFactory } from './model-config-factory.js';
|
|
8
7
|
import { type MastraAgentConfig, type SkillSearchHandle, type ToolSearchHandle } from './tool-search.js';
|
|
9
|
-
import { type
|
|
8
|
+
import { type MultimodalFile } from '@salesforce/sfdx-agent-sdk';
|
|
10
9
|
import { Clock, type LogRecord, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
|
|
11
10
|
import { type ToolsInput } from './utils.js';
|
|
12
11
|
import { Workspace } from '@mastra/core/workspace';
|
|
@@ -77,9 +76,12 @@ interface MastraAgentState {
|
|
|
77
76
|
composedInstructions: string;
|
|
78
77
|
/**
|
|
79
78
|
* Pre-flight multimodal validation for this agent's model configuration.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
79
|
+
* Wraps `validateMultimodalFiles(files, model)` against the agent's
|
|
80
|
+
* currently-bound `ModelConnectivityInfo.model` at call time, so within-
|
|
81
|
+
* shape model swaps (Claude 4.5 → 4.6) pick up the updated caps without
|
|
82
|
+
* rebuilding the closure. Called by `toMastraStreamInput` before every
|
|
83
|
+
* stream that carries `image` or `file` parts. Throws
|
|
84
|
+
* `AgentSDKError(MULTIMODAL_NOT_SUPPORTED)` on violation.
|
|
83
85
|
*/
|
|
84
86
|
validateFiles: (files: readonly MultimodalFile[]) => void;
|
|
85
87
|
/** Per-server MCP state, keyed by server name. Empty map when no MCP servers are configured. */
|
|
@@ -192,10 +194,18 @@ export type MastraAgentHarness = WithAgentConfig<AgentHarness & {
|
|
|
192
194
|
* directly.
|
|
193
195
|
*/
|
|
194
196
|
export interface MastraHarnessOptions {
|
|
195
|
-
modelConfigFactory?: MastraModelConfigFactory;
|
|
196
197
|
clock?: Clock;
|
|
197
198
|
idGenerator?: UniqueIDGenerator;
|
|
198
199
|
toolApprovalTimeoutMs?: number;
|
|
200
|
+
/**
|
|
201
|
+
* Inner fetch the harness threads into every in-process HTTP call site
|
|
202
|
+
* (LLM gateway language-model builders + MCP remote transports). Defaults
|
|
203
|
+
* to `globalThis.fetch` when omitted. The harness factory builds a
|
|
204
|
+
* proxy-aware fetch via `createProxyAwareFetch(dispatcher)` once per
|
|
205
|
+
* `create()` and passes it here, so `HTTPS_PROXY` routing happens without
|
|
206
|
+
* any `globalThis` mutation.
|
|
207
|
+
*/
|
|
208
|
+
innerFetch?: typeof fetch;
|
|
199
209
|
}
|
|
200
210
|
export declare class MastraHarness implements MastraAgentHarness {
|
|
201
211
|
private readonly storageRootFolder;
|
|
@@ -216,10 +226,19 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
216
226
|
* SDK never reads this; consumers reach through `manager.extensions.mastra`.
|
|
217
227
|
*/
|
|
218
228
|
readonly extensions: MastraAgentHarness['extensions'];
|
|
219
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Live `ModelConnectivityInfo` per agent. The pass-through builders close
|
|
231
|
+
* over a getter that reads from this map, so within-shape `modelId` swaps,
|
|
232
|
+
* JWT rotations, and BYOK provider swaps land on the next outbound call
|
|
233
|
+
* without rebuilding the Mastra `Agent`. The map is replaced on every
|
|
234
|
+
* `updateAgent`; the language model's fetch wrapper picks up the new bag
|
|
235
|
+
* via the closure.
|
|
236
|
+
*/
|
|
237
|
+
private readonly modelConnectivityByAgent;
|
|
220
238
|
private readonly clock;
|
|
221
239
|
private readonly idGenerator;
|
|
222
240
|
private readonly toolApprovalTimeoutMs;
|
|
241
|
+
private readonly innerFetch;
|
|
223
242
|
constructor(storageRootFolder: string, opts?: MastraHarnessOptions);
|
|
224
243
|
/**
|
|
225
244
|
* @requirements
|
|
@@ -233,6 +252,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
233
252
|
shutdown(): Promise<void>;
|
|
234
253
|
onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
|
|
235
254
|
onLog(callback: (record: LogRecord) => void): Unsubscribe;
|
|
255
|
+
onWireCommunication(callback: WireCommunicationEventCallback): Unsubscribe;
|
|
236
256
|
/**
|
|
237
257
|
* @requirements
|
|
238
258
|
* - MUST throw an error if the harness has not been initialized.
|
|
@@ -251,7 +271,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
251
271
|
* `mapToolDefinitionsToMastraTools()` and merge with namespaced MCP-discovered tools.
|
|
252
272
|
* - MUST store the original `MCPConfiguration` so getMcpServerInfo can report disabled servers.
|
|
253
273
|
*/
|
|
254
|
-
createAgent(agentId: string, projectRoot: string,
|
|
274
|
+
createAgent(agentId: string, projectRoot: string, modelConnectivityInfo: ModelConnectivityInfo, config?: HarnessAgentConfig, options?: {
|
|
255
275
|
abortSignal?: AbortSignal;
|
|
256
276
|
hooks?: AgentHooks;
|
|
257
277
|
}): Promise<void>;
|
|
@@ -298,7 +318,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
298
318
|
* `previousConfig`) sees the harness's current truth and reverts only the
|
|
299
319
|
* actual deltas.
|
|
300
320
|
*/
|
|
301
|
-
updateAgent(agentId: string,
|
|
321
|
+
updateAgent(agentId: string, modelConnectivityInfo: ModelConnectivityInfo, config?: HarnessAgentConfig, options?: {
|
|
302
322
|
abortSignal?: AbortSignal;
|
|
303
323
|
hooks?: AgentHooks;
|
|
304
324
|
}): Promise<void>;
|
|
@@ -359,6 +379,18 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
359
379
|
* still observable. Used by `compactThread` on both the empty-source and
|
|
360
380
|
* non-empty paths.
|
|
361
381
|
*/
|
|
382
|
+
/**
|
|
383
|
+
* Dispatches on the bag's `providerHint` and returns a `LanguageModelV3` that
|
|
384
|
+
* speaks the provider-native pass-through endpoint. The builder closes over
|
|
385
|
+
* `getInfo` so within-shape `modelId` swaps, JWT rotations, and
|
|
386
|
+
* premium-rate-limit gate flips land on the next outbound call without
|
|
387
|
+
* rebuilding this language model.
|
|
388
|
+
*
|
|
389
|
+
* The manager validates `providerHint` against `MastraHarnessFactory.supportedProviderHints`
|
|
390
|
+
* via G8 before any harness work runs (`AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS`),
|
|
391
|
+
* so a hint not handled here is a programming error worth surfacing eagerly.
|
|
392
|
+
*/
|
|
393
|
+
private buildLanguageModel;
|
|
362
394
|
private destroyThreadBestEffort;
|
|
363
395
|
/**
|
|
364
396
|
* @requirements
|
package/dist/mastra-harness.js
CHANGED
|
@@ -19,9 +19,11 @@ import { isStandardSchemaWithJSON, standardSchemaToJSONSchema } from '@mastra/sc
|
|
|
19
19
|
import { mapToolDefinitionsToMastraTools } from './mastra-tool-mapper.js';
|
|
20
20
|
import { hasEnabledServers, mapToMastraMCPServers } from './mcp-config-mapper.js';
|
|
21
21
|
import { buildMcpErrorDetail, sanitizeMcpErrorMessage } from './mcp-error-classifier.js';
|
|
22
|
-
import { DefaultMastraModelConfigFactory } from './default-model-config-factory.js';
|
|
23
22
|
import { partitionPool, resolveAlwaysActive, } from './tool-search.js';
|
|
24
|
-
import {} from '@salesforce/
|
|
23
|
+
import { validateMultimodalFiles } from '@salesforce/sfdx-agent-sdk';
|
|
24
|
+
import { buildAnthropicDirectLanguageModel, buildAnthropicLanguageModel } from './pass-through-anthropic.js';
|
|
25
|
+
import { buildOpenAIChatCompletionsLanguageModel } from './pass-through-openai-chat-completions.js';
|
|
26
|
+
import { buildOpenAILanguageModel } from './pass-through-openai.js';
|
|
25
27
|
import { ToolResultRedactionProcessor } from './mastra-tool-result-redaction-processor.js';
|
|
26
28
|
import { backfillCreatedAt, buildSummaryPrompt, Clock, getErrorMessage, RealClock, splitFrontmatterAndBody, UUIDGenerator, } from '@salesforce/agentic-common';
|
|
27
29
|
import { makeAbortable } from './utils.js';
|
|
@@ -362,16 +364,25 @@ export class MastraHarness {
|
|
|
362
364
|
formatMcpToolName: (serverName, toolName) => `${serverName}_${toolName}`,
|
|
363
365
|
},
|
|
364
366
|
};
|
|
365
|
-
|
|
367
|
+
/**
|
|
368
|
+
* Live `ModelConnectivityInfo` per agent. The pass-through builders close
|
|
369
|
+
* over a getter that reads from this map, so within-shape `modelId` swaps,
|
|
370
|
+
* JWT rotations, and BYOK provider swaps land on the next outbound call
|
|
371
|
+
* without rebuilding the Mastra `Agent`. The map is replaced on every
|
|
372
|
+
* `updateAgent`; the language model's fetch wrapper picks up the new bag
|
|
373
|
+
* via the closure.
|
|
374
|
+
*/
|
|
375
|
+
modelConnectivityByAgent = new Map();
|
|
366
376
|
clock;
|
|
367
377
|
idGenerator;
|
|
368
378
|
toolApprovalTimeoutMs;
|
|
379
|
+
innerFetch;
|
|
369
380
|
constructor(storageRootFolder, opts = {}) {
|
|
370
381
|
this.storageRootFolder = storageRootFolder;
|
|
371
|
-
this.modelConfigFactory = opts.modelConfigFactory ?? new DefaultMastraModelConfigFactory();
|
|
372
382
|
this.clock = opts.clock ?? new RealClock();
|
|
373
383
|
this.idGenerator = opts.idGenerator ?? new UUIDGenerator();
|
|
374
384
|
this.toolApprovalTimeoutMs = opts.toolApprovalTimeoutMs ?? 600_000;
|
|
385
|
+
this.innerFetch = opts.innerFetch ?? globalThis.fetch;
|
|
375
386
|
this.storage = new LibSQLStore({
|
|
376
387
|
id: 'mastra-storage',
|
|
377
388
|
url: `file:${this.storageRootFolder}/mastra.db`,
|
|
@@ -414,6 +425,7 @@ export class MastraHarness {
|
|
|
414
425
|
state.skillSearchProcessor?.dispose();
|
|
415
426
|
}));
|
|
416
427
|
this.activeAgents.clear();
|
|
428
|
+
this.modelConnectivityByAgent.clear();
|
|
417
429
|
await this.mastra?.shutdown();
|
|
418
430
|
this.buses.dispose();
|
|
419
431
|
}
|
|
@@ -423,6 +435,9 @@ export class MastraHarness {
|
|
|
423
435
|
onLog(callback) {
|
|
424
436
|
return this.buses.onLog(callback);
|
|
425
437
|
}
|
|
438
|
+
onWireCommunication(callback) {
|
|
439
|
+
return this.buses.onWireCommunication(callback);
|
|
440
|
+
}
|
|
426
441
|
// ── Agent Lifecycle ──────────────────────────────────────────────
|
|
427
442
|
/**
|
|
428
443
|
* @requirements
|
|
@@ -442,17 +457,19 @@ export class MastraHarness {
|
|
|
442
457
|
* `mapToolDefinitionsToMastraTools()` and merge with namespaced MCP-discovered tools.
|
|
443
458
|
* - MUST store the original `MCPConfiguration` so getMcpServerInfo can report disabled servers.
|
|
444
459
|
*/
|
|
445
|
-
async createAgent(agentId, projectRoot,
|
|
460
|
+
async createAgent(agentId, projectRoot, modelConnectivityInfo, config = {}, options) {
|
|
446
461
|
if (this.activeAgents.has(agentId)) {
|
|
447
462
|
throw new Error(`Agent with id "${agentId}" is already registered`);
|
|
448
463
|
}
|
|
464
|
+
// Store the bag first so the language-model getter has it on construct.
|
|
465
|
+
this.modelConnectivityByAgent.set(agentId, modelConnectivityInfo);
|
|
449
466
|
// Run rule load before MCP setup so a bad rule path can't leak constructed clients.
|
|
450
467
|
const composedInstructions = await composeInstructionsWithRules(config.instructions, config.rules);
|
|
451
468
|
// ── MCP setup (non-blocking, per-server) ──────────────────────────
|
|
452
469
|
const mcpServerStates = new Map();
|
|
453
470
|
if (hasEnabledServers(config.mcpServers)) {
|
|
454
471
|
options?.abortSignal?.throwIfAborted();
|
|
455
|
-
const enabledServers = mapToMastraMCPServers(config.mcpServers, config.orgJwt);
|
|
472
|
+
const enabledServers = mapToMastraMCPServers(config.mcpServers, config.orgJwt, this.innerFetch);
|
|
456
473
|
for (const [serverName, serverDef] of Object.entries(enabledServers)) {
|
|
457
474
|
const client = new MCPClient({
|
|
458
475
|
id: `${agentId}:${serverName}`,
|
|
@@ -487,7 +504,22 @@ export class MastraHarness {
|
|
|
487
504
|
const consumerToolNames = new Set((config.tools ?? []).map((t) => t.name));
|
|
488
505
|
const parkerLookup = (threadId) => this.activeAgents.get(agentId)?.coordinators.get(threadId)?.parkConsumerToolCall;
|
|
489
506
|
const customTools = config.tools?.length ? mapToolDefinitionsToMastraTools(config.tools, parkerLookup) : {};
|
|
490
|
-
|
|
507
|
+
// Build the language model from the live ModelConnectivityInfo bag. The
|
|
508
|
+
// builder closes over `() => this.modelConnectivityByAgent.get(agentId)!`
|
|
509
|
+
// so within-shape `modelId` swaps and JWT rotations land on the next
|
|
510
|
+
// outbound call without rebuilding the Mastra `Agent` (cross-shape
|
|
511
|
+
// swaps rebuild via `updateAgent` below). `validateFiles` reads `model`
|
|
512
|
+
// from the bag so multimodal pre-flight stays in lockstep with the
|
|
513
|
+
// model the harness is actually about to talk to.
|
|
514
|
+
const getInfoForAgent = () => {
|
|
515
|
+
const info = this.modelConnectivityByAgent.get(agentId);
|
|
516
|
+
if (!info) {
|
|
517
|
+
throw new AgentSDKError(`No connectivity info registered for agentId "${agentId}".`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
518
|
+
}
|
|
519
|
+
return info;
|
|
520
|
+
};
|
|
521
|
+
const resolvedModel = this.buildLanguageModel(getInfoForAgent);
|
|
522
|
+
const validateFiles = (files) => validateMultimodalFiles(files, getInfoForAgent().model);
|
|
491
523
|
let mastraWorkspace;
|
|
492
524
|
let agent;
|
|
493
525
|
let mastraAgentAdded = false;
|
|
@@ -630,6 +662,11 @@ export class MastraHarness {
|
|
|
630
662
|
skillSearchProcessor?.dispose();
|
|
631
663
|
await mastraWorkspace?.destroy().catch(() => undefined);
|
|
632
664
|
await Promise.allSettled(Array.from(mcpServerStates.values()).map((s) => s.client.disconnect().catch(() => undefined)));
|
|
665
|
+
// The bag was registered eagerly at the top of createAgent so the
|
|
666
|
+
// language-model getter could read it. On failure we never made
|
|
667
|
+
// it to activeAgents.set, so the registration is orphaned —
|
|
668
|
+
// clear it for symmetry with destroyAgent.
|
|
669
|
+
this.modelConnectivityByAgent.delete(agentId);
|
|
633
670
|
throw error;
|
|
634
671
|
}
|
|
635
672
|
}
|
|
@@ -652,6 +689,7 @@ export class MastraHarness {
|
|
|
652
689
|
state.skillSearchProcessor?.dispose();
|
|
653
690
|
await state.mastraWorkspace?.destroy();
|
|
654
691
|
this.mastra.removeAgent(agentId);
|
|
692
|
+
this.modelConnectivityByAgent.delete(agentId);
|
|
655
693
|
return this.activeAgents.delete(agentId);
|
|
656
694
|
}
|
|
657
695
|
/**
|
|
@@ -820,9 +858,21 @@ export class MastraHarness {
|
|
|
820
858
|
* `previousConfig`) sees the harness's current truth and reverts only the
|
|
821
859
|
* actual deltas.
|
|
822
860
|
*/
|
|
823
|
-
async updateAgent(agentId,
|
|
861
|
+
async updateAgent(agentId, modelConnectivityInfo, config = {}, options) {
|
|
824
862
|
const state = this.getAgentStateOrThrow(agentId);
|
|
825
863
|
options?.abortSignal?.throwIfAborted();
|
|
864
|
+
// Swap the connectivity bag in the per-agent map BEFORE any rebuild
|
|
865
|
+
// logic runs. Within-shape model swaps (Claude 4.5 → 4.6) flow through
|
|
866
|
+
// the existing language model's getter on the next outbound call;
|
|
867
|
+
// cross-shape swaps trigger an `Agent` rebuild below. JWT rotation
|
|
868
|
+
// and rate-limit gate flips also land here — no separate swap path.
|
|
869
|
+
// Cross-shape swaps could in principle skip the Agent rebuild below
|
|
870
|
+
// — only a `providerHint` change requires a new `mastra.removeAgent`
|
|
871
|
+
// round trip — but Mastra always rebuilds the `Agent` on
|
|
872
|
+
// `updateAgent` regardless, so that optimization isn't load-bearing
|
|
873
|
+
// today; the previous-hint capture is left out so this site doesn't
|
|
874
|
+
// accumulate dead state.
|
|
875
|
+
this.modelConnectivityByAgent.set(agentId, modelConnectivityInfo);
|
|
826
876
|
// ── Phase 0: validate non-MCP inputs before any MCP client is touched.
|
|
827
877
|
// A bad rule path or skill path rejects updateAgent here so
|
|
828
878
|
// half-applied state from non-MCP failures is impossible.
|
|
@@ -861,7 +911,7 @@ export class MastraHarness {
|
|
|
861
911
|
const nextConfig = config.mcpServers ?? {};
|
|
862
912
|
const allNames = new Set([...Object.keys(prevConfig), ...Object.keys(nextConfig)]);
|
|
863
913
|
const nextEnabledServers = hasEnabledServers(nextConfig)
|
|
864
|
-
? mapToMastraMCPServers(nextConfig, config.orgJwt)
|
|
914
|
+
? mapToMastraMCPServers(nextConfig, config.orgJwt, this.innerFetch)
|
|
865
915
|
: {};
|
|
866
916
|
for (const name of allNames) {
|
|
867
917
|
const prev = prevConfig[name];
|
|
@@ -905,7 +955,24 @@ export class MastraHarness {
|
|
|
905
955
|
const consumerToolNames = new Set((config.tools ?? []).map((t) => t.name));
|
|
906
956
|
const parkerLookup = (threadId) => this.activeAgents.get(agentId)?.coordinators.get(threadId)?.parkConsumerToolCall;
|
|
907
957
|
const customTools = config.tools?.length ? mapToolDefinitionsToMastraTools(config.tools, parkerLookup) : {};
|
|
908
|
-
|
|
958
|
+
// Same shape as createAgent's getter — reads the live bag on every call so
|
|
959
|
+
// the next-built `Agent`'s language model picks up future bag swaps.
|
|
960
|
+
const getInfoForAgent = () => {
|
|
961
|
+
const info = this.modelConnectivityByAgent.get(agentId);
|
|
962
|
+
if (!info) {
|
|
963
|
+
throw new AgentSDKError(`No connectivity info registered for agentId "${agentId}".`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
964
|
+
}
|
|
965
|
+
return info;
|
|
966
|
+
};
|
|
967
|
+
// Cross-shape swap (e.g. Claude → GPT) requires a different language
|
|
968
|
+
// model class because the provider SDK underneath differs. Within-shape
|
|
969
|
+
// swaps could reuse the existing language model — its getter reads the
|
|
970
|
+
// refreshed bag on the next outbound call — but Mastra reconstructs the
|
|
971
|
+
// `Agent` unconditionally on every `updateAgent` (instructions, tools,
|
|
972
|
+
// processors all flow through), so we always rebuild the language model
|
|
973
|
+
// here too. Allocation cost is negligible compared to the Agent rebuild.
|
|
974
|
+
const resolvedModel = this.buildLanguageModel(getInfoForAgent);
|
|
975
|
+
const validateFiles = (files) => validateMultimodalFiles(files, getInfoForAgent().model);
|
|
909
976
|
// Capture the live mcpServers map by reference — the new Agent's
|
|
910
977
|
// tools() closure will read it on every step, so preserved clients'
|
|
911
978
|
// discovered tools stay visible without a re-listing.
|
|
@@ -1153,6 +1220,60 @@ export class MastraHarness {
|
|
|
1153
1220
|
* still observable. Used by `compactThread` on both the empty-source and
|
|
1154
1221
|
* non-empty paths.
|
|
1155
1222
|
*/
|
|
1223
|
+
/**
|
|
1224
|
+
* Dispatches on the bag's `providerHint` and returns a `LanguageModelV3` that
|
|
1225
|
+
* speaks the provider-native pass-through endpoint. The builder closes over
|
|
1226
|
+
* `getInfo` so within-shape `modelId` swaps, JWT rotations, and
|
|
1227
|
+
* premium-rate-limit gate flips land on the next outbound call without
|
|
1228
|
+
* rebuilding this language model.
|
|
1229
|
+
*
|
|
1230
|
+
* The manager validates `providerHint` against `MastraHarnessFactory.supportedProviderHints`
|
|
1231
|
+
* via G8 before any harness work runs (`AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS`),
|
|
1232
|
+
* so a hint not handled here is a programming error worth surfacing eagerly.
|
|
1233
|
+
*/
|
|
1234
|
+
buildLanguageModel(getInfo) {
|
|
1235
|
+
const initial = getInfo();
|
|
1236
|
+
const logBus = this.buses.getLogBus();
|
|
1237
|
+
// The fetch wrapper emits one WireCommunicationEvent pair per HTTP call. Forward
|
|
1238
|
+
// each event to the harness bus; if the bus has been disposed (a late
|
|
1239
|
+
// emit during shutdown), swallow the typed DISPOSED error so trace
|
|
1240
|
+
// hooks never fail the underlying fetch.
|
|
1241
|
+
const emitWireCommunication = (event) => {
|
|
1242
|
+
try {
|
|
1243
|
+
this.buses.emitWireCommunication(event);
|
|
1244
|
+
}
|
|
1245
|
+
catch (err) {
|
|
1246
|
+
if (!isDisposedError(err))
|
|
1247
|
+
throw err;
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
const innerFetch = this.innerFetch;
|
|
1251
|
+
switch (initial.providerHint) {
|
|
1252
|
+
case 'bedrock-anthropic':
|
|
1253
|
+
// Salesforce LLM Gateway's `/invoke-with-response-stream` (Bedrock pass-through).
|
|
1254
|
+
return buildAnthropicLanguageModel({ getInfo, logBus, emitWireCommunication, innerFetch });
|
|
1255
|
+
case 'anthropic':
|
|
1256
|
+
// Direct Anthropic Messages API (`api.anthropic.com/v1/messages`) —
|
|
1257
|
+
// different wire shape (`x-api-key` auth, body without `anthropic_version`).
|
|
1258
|
+
return buildAnthropicDirectLanguageModel({ getInfo, logBus, emitWireCommunication, innerFetch });
|
|
1259
|
+
case 'openai-responses':
|
|
1260
|
+
case 'openai':
|
|
1261
|
+
// OpenAI Responses API. Salesforce LLM Gateway's `/responses` pass-through
|
|
1262
|
+
// for `'openai-responses'`; `api.openai.com/v1/responses` for `'openai'` direct.
|
|
1263
|
+
// Same wire shape; the only difference is the `baseUrl` the resolver supplies.
|
|
1264
|
+
return buildOpenAILanguageModel({ getInfo, logBus, emitWireCommunication, innerFetch });
|
|
1265
|
+
case 'openai-compatible':
|
|
1266
|
+
// OpenAI-compatible Chat Completions API (LLMG Express, LiteLLM, etc.).
|
|
1267
|
+
// Different wire shape from Responses — `/v1/chat/completions` with
|
|
1268
|
+
// `choices[0].delta` streaming events.
|
|
1269
|
+
return buildOpenAIChatCompletionsLanguageModel({ getInfo, logBus, emitWireCommunication, innerFetch });
|
|
1270
|
+
default:
|
|
1271
|
+
// Defensive — manager-side G8 should have caught this before
|
|
1272
|
+
// we got here. Make the error typed so consumers can branch
|
|
1273
|
+
// on `err.type` instead of regexing the message.
|
|
1274
|
+
throw new AgentSDKError(`Mastra harness has no language-model builder for providerHint "${initial.providerHint}".`, AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1156
1277
|
async destroyThreadBestEffort(agentId, threadId) {
|
|
1157
1278
|
try {
|
|
1158
1279
|
await this.destroyThread(agentId, threadId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MastraMCPServerDefinition } from '@mastra/mcp';
|
|
2
|
-
import type { JSONWebToken } from '@salesforce/
|
|
2
|
+
import type { JSONWebToken } from '@salesforce/agentic-common';
|
|
3
3
|
import { type MCPConfiguration } from '@salesforce/sfdx-agent-sdk';
|
|
4
4
|
/**
|
|
5
5
|
* Returns `true` if `config` contains at least one server that is not explicitly disabled.
|
|
@@ -24,4 +24,11 @@ export declare function hasEnabledServers(config: MCPConfiguration | undefined):
|
|
|
24
24
|
* - When `orgJwt` is provided, remote servers get a `fetch` that delegates to
|
|
25
25
|
* {@link resolveMcpServerHeaders} per-request to inject auth for Salesforce Platform URLs.
|
|
26
26
|
*/
|
|
27
|
-
export declare function mapToMastraMCPServers(config: MCPConfiguration, orgJwt?: JSONWebToken
|
|
27
|
+
export declare function mapToMastraMCPServers(config: MCPConfiguration, orgJwt?: JSONWebToken,
|
|
28
|
+
/**
|
|
29
|
+
* Inner fetch routed through the harness's `Dispatcher`. Defaults to
|
|
30
|
+
* `globalThis.fetch`; the harness factory threads a proxy-aware fetch
|
|
31
|
+
* here so MCP remote transports honor `HTTPS_PROXY` without `globalThis`
|
|
32
|
+
* mutation. Stdio transports ignore it (they're child processes).
|
|
33
|
+
*/
|
|
34
|
+
innerFetch?: typeof fetch): Record<string, MastraMCPServerDefinition>;
|
|
@@ -44,7 +44,14 @@ const DEFAULT_RECONNECTION_OPTIONS = {
|
|
|
44
44
|
* - When `orgJwt` is provided, remote servers get a `fetch` that delegates to
|
|
45
45
|
* {@link resolveMcpServerHeaders} per-request to inject auth for Salesforce Platform URLs.
|
|
46
46
|
*/
|
|
47
|
-
export function mapToMastraMCPServers(config, orgJwt
|
|
47
|
+
export function mapToMastraMCPServers(config, orgJwt,
|
|
48
|
+
/**
|
|
49
|
+
* Inner fetch routed through the harness's `Dispatcher`. Defaults to
|
|
50
|
+
* `globalThis.fetch`; the harness factory threads a proxy-aware fetch
|
|
51
|
+
* here so MCP remote transports honor `HTTPS_PROXY` without `globalThis`
|
|
52
|
+
* mutation. Stdio transports ignore it (they're child processes).
|
|
53
|
+
*/
|
|
54
|
+
innerFetch = globalThis.fetch) {
|
|
48
55
|
const result = {};
|
|
49
56
|
for (const [name, serverConfig] of Object.entries(config)) {
|
|
50
57
|
if (serverConfig.enabled === false) {
|
|
@@ -73,7 +80,7 @@ export function mapToMastraMCPServers(config, orgJwt) {
|
|
|
73
80
|
...(serverConfig.headers !== undefined && {
|
|
74
81
|
requestInit: { headers: serverConfig.headers },
|
|
75
82
|
}),
|
|
76
|
-
...(orgJwt && { fetch: createAuthFetch(serverConfig.url, orgJwt, serverConfig.headers) }),
|
|
83
|
+
...(orgJwt && { fetch: createAuthFetch(serverConfig.url, orgJwt, innerFetch, serverConfig.headers) }),
|
|
77
84
|
...(serverConfig.timeout !== undefined && {
|
|
78
85
|
timeout: serverConfig.timeout,
|
|
79
86
|
connectTimeout: serverConfig.timeout,
|
|
@@ -99,14 +106,14 @@ export function mapToMastraMCPServers(config, orgJwt) {
|
|
|
99
106
|
}
|
|
100
107
|
return result;
|
|
101
108
|
}
|
|
102
|
-
function createAuthFetch(serverUrl, orgJwt, existingHeaders) {
|
|
109
|
+
function createAuthFetch(serverUrl, orgJwt, innerFetch, existingHeaders) {
|
|
103
110
|
return async (url, init) => {
|
|
104
111
|
const authHeaders = await resolveMcpServerHeaders(serverUrl, orgJwt, existingHeaders);
|
|
105
112
|
const merged = new Headers(init?.headers);
|
|
106
113
|
for (const [key, value] of Object.entries(authHeaders)) {
|
|
107
114
|
merged.set(key, value);
|
|
108
115
|
}
|
|
109
|
-
return
|
|
116
|
+
return innerFetch(url, { ...init, headers: merged });
|
|
110
117
|
};
|
|
111
118
|
}
|
|
112
119
|
//# sourceMappingURL=mcp-config-mapper.js.map
|