@salesforce/sfdx-agent-harness-mastra 0.20.0 → 0.21.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 +11 -0
- package/README.md +18 -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 +7 -1
- package/dist/mastra-harness.d.ts +32 -10
- package/dist/mastra-harness.js +126 -8
- package/dist/mcp-config-mapper.d.ts +1 -1
- package/dist/pass-through-anthropic.d.ts +66 -0
- package/dist/pass-through-anthropic.js +454 -0
- package/dist/pass-through-openai-chat-completions.d.ts +50 -0
- package/dist/pass-through-openai-chat-completions.js +388 -0
- package/dist/pass-through-openai.d.ts +39 -0
- package/dist/pass-through-openai.js +372 -0
- package/dist/v3-stream-mapping.d.ts +241 -0
- package/dist/v3-stream-mapping.js +359 -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,17 @@
|
|
|
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.21.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**: bump the mastra group across 1 directory with 4 updates ([#602](https://github.com/forcedotcom/agentic-dx/pull/602))
|
|
15
|
+
- **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))
|
|
16
|
+
|
|
6
17
|
## [0.20.0] - 2026-06-15
|
|
7
18
|
|
|
8
19
|
### Fixes
|
package/README.md
CHANGED
|
@@ -44,15 +44,15 @@ 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
56
|
|
|
57
57
|
### Extension surface
|
|
58
58
|
|
|
@@ -236,19 +236,20 @@ contract.
|
|
|
236
236
|
|
|
237
237
|
### Using LLM Gateway Express
|
|
238
238
|
|
|
239
|
+
LLMG Express, Anthropic-direct, OpenAI-direct, and any other api-key endpoint flow through the SDK's
|
|
240
|
+
`ApiKeyConnectivityResolver` (no harness-specific factory needed):
|
|
241
|
+
|
|
239
242
|
```ts
|
|
240
|
-
import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
241
|
-
import { MastraHarnessFactory
|
|
243
|
+
import { ApiKeyConnectivityResolver, createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
244
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
242
245
|
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
const manager = await createAgentManager('/path/to/storage', new MastraHarnessFactory(), {
|
|
247
|
+
connectivityResolver: new ApiKeyConnectivityResolver({
|
|
248
|
+
getApiKey: () => process.env.LLMG_EXPRESS_API_KEY!,
|
|
246
249
|
baseUrl: process.env.LLMG_EXPRESS_BASE_URL!,
|
|
247
|
-
|
|
250
|
+
providerHint: 'openai-compatible',
|
|
248
251
|
}),
|
|
249
252
|
});
|
|
250
|
-
|
|
251
|
-
const manager = await createAgentManager('/path/to/storage', harnessFactory);
|
|
252
253
|
```
|
|
253
254
|
|
|
254
255
|
## 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
|
}
|
|
@@ -9,12 +9,18 @@ export class MastraHarnessFactory {
|
|
|
9
9
|
config;
|
|
10
10
|
harnessId = 'mastra';
|
|
11
11
|
protocolVersion = 1;
|
|
12
|
+
supportedProviderHints = [
|
|
13
|
+
'anthropic',
|
|
14
|
+
'bedrock-anthropic',
|
|
15
|
+
'openai',
|
|
16
|
+
'openai-responses',
|
|
17
|
+
'openai-compatible',
|
|
18
|
+
];
|
|
12
19
|
constructor(config = {}) {
|
|
13
20
|
this.config = config;
|
|
14
21
|
}
|
|
15
22
|
async create(storageRootFolder) {
|
|
16
23
|
return new MastraHarness(storageRootFolder, {
|
|
17
|
-
modelConfigFactory: this.config.modelConfigFactory,
|
|
18
24
|
toolApprovalTimeoutMs: this.config.toolApprovalTimeoutMs ?? DEFAULT_TOOL_APPROVAL_TIMEOUT_MS,
|
|
19
25
|
});
|
|
20
26
|
}
|
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,7 +194,6 @@ 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;
|
|
@@ -216,7 +217,15 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
216
217
|
* SDK never reads this; consumers reach through `manager.extensions.mastra`.
|
|
217
218
|
*/
|
|
218
219
|
readonly extensions: MastraAgentHarness['extensions'];
|
|
219
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Live `ModelConnectivityInfo` per agent. The pass-through builders close
|
|
222
|
+
* over a getter that reads from this map, so within-shape `modelId` swaps,
|
|
223
|
+
* JWT rotations, and BYOK provider swaps land on the next outbound call
|
|
224
|
+
* without rebuilding the Mastra `Agent`. The map is replaced on every
|
|
225
|
+
* `updateAgent`; the language model's fetch wrapper picks up the new bag
|
|
226
|
+
* via the closure.
|
|
227
|
+
*/
|
|
228
|
+
private readonly modelConnectivityByAgent;
|
|
220
229
|
private readonly clock;
|
|
221
230
|
private readonly idGenerator;
|
|
222
231
|
private readonly toolApprovalTimeoutMs;
|
|
@@ -233,6 +242,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
233
242
|
shutdown(): Promise<void>;
|
|
234
243
|
onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
|
|
235
244
|
onLog(callback: (record: LogRecord) => void): Unsubscribe;
|
|
245
|
+
onWireCommunication(callback: WireCommunicationEventCallback): Unsubscribe;
|
|
236
246
|
/**
|
|
237
247
|
* @requirements
|
|
238
248
|
* - MUST throw an error if the harness has not been initialized.
|
|
@@ -251,7 +261,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
251
261
|
* `mapToolDefinitionsToMastraTools()` and merge with namespaced MCP-discovered tools.
|
|
252
262
|
* - MUST store the original `MCPConfiguration` so getMcpServerInfo can report disabled servers.
|
|
253
263
|
*/
|
|
254
|
-
createAgent(agentId: string, projectRoot: string,
|
|
264
|
+
createAgent(agentId: string, projectRoot: string, modelConnectivityInfo: ModelConnectivityInfo, config?: HarnessAgentConfig, options?: {
|
|
255
265
|
abortSignal?: AbortSignal;
|
|
256
266
|
hooks?: AgentHooks;
|
|
257
267
|
}): Promise<void>;
|
|
@@ -298,7 +308,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
298
308
|
* `previousConfig`) sees the harness's current truth and reverts only the
|
|
299
309
|
* actual deltas.
|
|
300
310
|
*/
|
|
301
|
-
updateAgent(agentId: string,
|
|
311
|
+
updateAgent(agentId: string, modelConnectivityInfo: ModelConnectivityInfo, config?: HarnessAgentConfig, options?: {
|
|
302
312
|
abortSignal?: AbortSignal;
|
|
303
313
|
hooks?: AgentHooks;
|
|
304
314
|
}): Promise<void>;
|
|
@@ -359,6 +369,18 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
359
369
|
* still observable. Used by `compactThread` on both the empty-source and
|
|
360
370
|
* non-empty paths.
|
|
361
371
|
*/
|
|
372
|
+
/**
|
|
373
|
+
* Dispatches on the bag's `providerHint` and returns a `LanguageModelV3` that
|
|
374
|
+
* speaks the provider-native pass-through endpoint. The builder closes over
|
|
375
|
+
* `getInfo` so within-shape `modelId` swaps, JWT rotations, and
|
|
376
|
+
* premium-rate-limit gate flips land on the next outbound call without
|
|
377
|
+
* rebuilding this language model.
|
|
378
|
+
*
|
|
379
|
+
* The manager validates `providerHint` against `MastraHarnessFactory.supportedProviderHints`
|
|
380
|
+
* via G8 before any harness work runs (`AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS`),
|
|
381
|
+
* so a hint not handled here is a programming error worth surfacing eagerly.
|
|
382
|
+
*/
|
|
383
|
+
private buildLanguageModel;
|
|
362
384
|
private destroyThreadBestEffort;
|
|
363
385
|
/**
|
|
364
386
|
* @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,13 +364,20 @@ 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;
|
|
369
379
|
constructor(storageRootFolder, opts = {}) {
|
|
370
380
|
this.storageRootFolder = storageRootFolder;
|
|
371
|
-
this.modelConfigFactory = opts.modelConfigFactory ?? new DefaultMastraModelConfigFactory();
|
|
372
381
|
this.clock = opts.clock ?? new RealClock();
|
|
373
382
|
this.idGenerator = opts.idGenerator ?? new UUIDGenerator();
|
|
374
383
|
this.toolApprovalTimeoutMs = opts.toolApprovalTimeoutMs ?? 600_000;
|
|
@@ -414,6 +423,7 @@ export class MastraHarness {
|
|
|
414
423
|
state.skillSearchProcessor?.dispose();
|
|
415
424
|
}));
|
|
416
425
|
this.activeAgents.clear();
|
|
426
|
+
this.modelConnectivityByAgent.clear();
|
|
417
427
|
await this.mastra?.shutdown();
|
|
418
428
|
this.buses.dispose();
|
|
419
429
|
}
|
|
@@ -423,6 +433,9 @@ export class MastraHarness {
|
|
|
423
433
|
onLog(callback) {
|
|
424
434
|
return this.buses.onLog(callback);
|
|
425
435
|
}
|
|
436
|
+
onWireCommunication(callback) {
|
|
437
|
+
return this.buses.onWireCommunication(callback);
|
|
438
|
+
}
|
|
426
439
|
// ── Agent Lifecycle ──────────────────────────────────────────────
|
|
427
440
|
/**
|
|
428
441
|
* @requirements
|
|
@@ -442,10 +455,12 @@ export class MastraHarness {
|
|
|
442
455
|
* `mapToolDefinitionsToMastraTools()` and merge with namespaced MCP-discovered tools.
|
|
443
456
|
* - MUST store the original `MCPConfiguration` so getMcpServerInfo can report disabled servers.
|
|
444
457
|
*/
|
|
445
|
-
async createAgent(agentId, projectRoot,
|
|
458
|
+
async createAgent(agentId, projectRoot, modelConnectivityInfo, config = {}, options) {
|
|
446
459
|
if (this.activeAgents.has(agentId)) {
|
|
447
460
|
throw new Error(`Agent with id "${agentId}" is already registered`);
|
|
448
461
|
}
|
|
462
|
+
// Store the bag first so the language-model getter has it on construct.
|
|
463
|
+
this.modelConnectivityByAgent.set(agentId, modelConnectivityInfo);
|
|
449
464
|
// Run rule load before MCP setup so a bad rule path can't leak constructed clients.
|
|
450
465
|
const composedInstructions = await composeInstructionsWithRules(config.instructions, config.rules);
|
|
451
466
|
// ── MCP setup (non-blocking, per-server) ──────────────────────────
|
|
@@ -487,7 +502,22 @@ export class MastraHarness {
|
|
|
487
502
|
const consumerToolNames = new Set((config.tools ?? []).map((t) => t.name));
|
|
488
503
|
const parkerLookup = (threadId) => this.activeAgents.get(agentId)?.coordinators.get(threadId)?.parkConsumerToolCall;
|
|
489
504
|
const customTools = config.tools?.length ? mapToolDefinitionsToMastraTools(config.tools, parkerLookup) : {};
|
|
490
|
-
|
|
505
|
+
// Build the language model from the live ModelConnectivityInfo bag. The
|
|
506
|
+
// builder closes over `() => this.modelConnectivityByAgent.get(agentId)!`
|
|
507
|
+
// so within-shape `modelId` swaps and JWT rotations land on the next
|
|
508
|
+
// outbound call without rebuilding the Mastra `Agent` (cross-shape
|
|
509
|
+
// swaps rebuild via `updateAgent` below). `validateFiles` reads `model`
|
|
510
|
+
// from the bag so multimodal pre-flight stays in lockstep with the
|
|
511
|
+
// model the harness is actually about to talk to.
|
|
512
|
+
const getInfoForAgent = () => {
|
|
513
|
+
const info = this.modelConnectivityByAgent.get(agentId);
|
|
514
|
+
if (!info) {
|
|
515
|
+
throw new AgentSDKError(`No connectivity info registered for agentId "${agentId}".`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
516
|
+
}
|
|
517
|
+
return info;
|
|
518
|
+
};
|
|
519
|
+
const resolvedModel = this.buildLanguageModel(getInfoForAgent);
|
|
520
|
+
const validateFiles = (files) => validateMultimodalFiles(files, getInfoForAgent().model);
|
|
491
521
|
let mastraWorkspace;
|
|
492
522
|
let agent;
|
|
493
523
|
let mastraAgentAdded = false;
|
|
@@ -630,6 +660,11 @@ export class MastraHarness {
|
|
|
630
660
|
skillSearchProcessor?.dispose();
|
|
631
661
|
await mastraWorkspace?.destroy().catch(() => undefined);
|
|
632
662
|
await Promise.allSettled(Array.from(mcpServerStates.values()).map((s) => s.client.disconnect().catch(() => undefined)));
|
|
663
|
+
// The bag was registered eagerly at the top of createAgent so the
|
|
664
|
+
// language-model getter could read it. On failure we never made
|
|
665
|
+
// it to activeAgents.set, so the registration is orphaned —
|
|
666
|
+
// clear it for symmetry with destroyAgent.
|
|
667
|
+
this.modelConnectivityByAgent.delete(agentId);
|
|
633
668
|
throw error;
|
|
634
669
|
}
|
|
635
670
|
}
|
|
@@ -652,6 +687,7 @@ export class MastraHarness {
|
|
|
652
687
|
state.skillSearchProcessor?.dispose();
|
|
653
688
|
await state.mastraWorkspace?.destroy();
|
|
654
689
|
this.mastra.removeAgent(agentId);
|
|
690
|
+
this.modelConnectivityByAgent.delete(agentId);
|
|
655
691
|
return this.activeAgents.delete(agentId);
|
|
656
692
|
}
|
|
657
693
|
/**
|
|
@@ -820,9 +856,21 @@ export class MastraHarness {
|
|
|
820
856
|
* `previousConfig`) sees the harness's current truth and reverts only the
|
|
821
857
|
* actual deltas.
|
|
822
858
|
*/
|
|
823
|
-
async updateAgent(agentId,
|
|
859
|
+
async updateAgent(agentId, modelConnectivityInfo, config = {}, options) {
|
|
824
860
|
const state = this.getAgentStateOrThrow(agentId);
|
|
825
861
|
options?.abortSignal?.throwIfAborted();
|
|
862
|
+
// Swap the connectivity bag in the per-agent map BEFORE any rebuild
|
|
863
|
+
// logic runs. Within-shape model swaps (Claude 4.5 → 4.6) flow through
|
|
864
|
+
// the existing language model's getter on the next outbound call;
|
|
865
|
+
// cross-shape swaps trigger an `Agent` rebuild below. JWT rotation
|
|
866
|
+
// and rate-limit gate flips also land here — no separate swap path.
|
|
867
|
+
// Cross-shape swaps could in principle skip the Agent rebuild below
|
|
868
|
+
// — only a `providerHint` change requires a new `mastra.removeAgent`
|
|
869
|
+
// round trip — but Mastra always rebuilds the `Agent` on
|
|
870
|
+
// `updateAgent` regardless, so that optimization isn't load-bearing
|
|
871
|
+
// today; the previous-hint capture is left out so this site doesn't
|
|
872
|
+
// accumulate dead state.
|
|
873
|
+
this.modelConnectivityByAgent.set(agentId, modelConnectivityInfo);
|
|
826
874
|
// ── Phase 0: validate non-MCP inputs before any MCP client is touched.
|
|
827
875
|
// A bad rule path or skill path rejects updateAgent here so
|
|
828
876
|
// half-applied state from non-MCP failures is impossible.
|
|
@@ -905,7 +953,24 @@ export class MastraHarness {
|
|
|
905
953
|
const consumerToolNames = new Set((config.tools ?? []).map((t) => t.name));
|
|
906
954
|
const parkerLookup = (threadId) => this.activeAgents.get(agentId)?.coordinators.get(threadId)?.parkConsumerToolCall;
|
|
907
955
|
const customTools = config.tools?.length ? mapToolDefinitionsToMastraTools(config.tools, parkerLookup) : {};
|
|
908
|
-
|
|
956
|
+
// Same shape as createAgent's getter — reads the live bag on every call so
|
|
957
|
+
// the next-built `Agent`'s language model picks up future bag swaps.
|
|
958
|
+
const getInfoForAgent = () => {
|
|
959
|
+
const info = this.modelConnectivityByAgent.get(agentId);
|
|
960
|
+
if (!info) {
|
|
961
|
+
throw new AgentSDKError(`No connectivity info registered for agentId "${agentId}".`, AgentSDKErrorType.AGENT_NOT_FOUND);
|
|
962
|
+
}
|
|
963
|
+
return info;
|
|
964
|
+
};
|
|
965
|
+
// Cross-shape swap (e.g. Claude → GPT) requires a different language
|
|
966
|
+
// model class because the provider SDK underneath differs. Within-shape
|
|
967
|
+
// swaps could reuse the existing language model — its getter reads the
|
|
968
|
+
// refreshed bag on the next outbound call — but Mastra reconstructs the
|
|
969
|
+
// `Agent` unconditionally on every `updateAgent` (instructions, tools,
|
|
970
|
+
// processors all flow through), so we always rebuild the language model
|
|
971
|
+
// here too. Allocation cost is negligible compared to the Agent rebuild.
|
|
972
|
+
const resolvedModel = this.buildLanguageModel(getInfoForAgent);
|
|
973
|
+
const validateFiles = (files) => validateMultimodalFiles(files, getInfoForAgent().model);
|
|
909
974
|
// Capture the live mcpServers map by reference — the new Agent's
|
|
910
975
|
// tools() closure will read it on every step, so preserved clients'
|
|
911
976
|
// discovered tools stay visible without a re-listing.
|
|
@@ -1153,6 +1218,59 @@ export class MastraHarness {
|
|
|
1153
1218
|
* still observable. Used by `compactThread` on both the empty-source and
|
|
1154
1219
|
* non-empty paths.
|
|
1155
1220
|
*/
|
|
1221
|
+
/**
|
|
1222
|
+
* Dispatches on the bag's `providerHint` and returns a `LanguageModelV3` that
|
|
1223
|
+
* speaks the provider-native pass-through endpoint. The builder closes over
|
|
1224
|
+
* `getInfo` so within-shape `modelId` swaps, JWT rotations, and
|
|
1225
|
+
* premium-rate-limit gate flips land on the next outbound call without
|
|
1226
|
+
* rebuilding this language model.
|
|
1227
|
+
*
|
|
1228
|
+
* The manager validates `providerHint` against `MastraHarnessFactory.supportedProviderHints`
|
|
1229
|
+
* via G8 before any harness work runs (`AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS`),
|
|
1230
|
+
* so a hint not handled here is a programming error worth surfacing eagerly.
|
|
1231
|
+
*/
|
|
1232
|
+
buildLanguageModel(getInfo) {
|
|
1233
|
+
const initial = getInfo();
|
|
1234
|
+
const logBus = this.buses.getLogBus();
|
|
1235
|
+
// The fetch wrapper emits one WireCommunicationEvent pair per HTTP call. Forward
|
|
1236
|
+
// each event to the harness bus; if the bus has been disposed (a late
|
|
1237
|
+
// emit during shutdown), swallow the typed DISPOSED error so trace
|
|
1238
|
+
// hooks never fail the underlying fetch.
|
|
1239
|
+
const emitWireCommunication = (event) => {
|
|
1240
|
+
try {
|
|
1241
|
+
this.buses.emitWireCommunication(event);
|
|
1242
|
+
}
|
|
1243
|
+
catch (err) {
|
|
1244
|
+
if (!isDisposedError(err))
|
|
1245
|
+
throw err;
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
switch (initial.providerHint) {
|
|
1249
|
+
case 'bedrock-anthropic':
|
|
1250
|
+
// Salesforce LLM Gateway's `/invoke-with-response-stream` (Bedrock pass-through).
|
|
1251
|
+
return buildAnthropicLanguageModel({ getInfo, logBus, emitWireCommunication });
|
|
1252
|
+
case 'anthropic':
|
|
1253
|
+
// Direct Anthropic Messages API (`api.anthropic.com/v1/messages`) —
|
|
1254
|
+
// different wire shape (`x-api-key` auth, body without `anthropic_version`).
|
|
1255
|
+
return buildAnthropicDirectLanguageModel({ getInfo, logBus, emitWireCommunication });
|
|
1256
|
+
case 'openai-responses':
|
|
1257
|
+
case 'openai':
|
|
1258
|
+
// OpenAI Responses API. Salesforce LLM Gateway's `/responses` pass-through
|
|
1259
|
+
// for `'openai-responses'`; `api.openai.com/v1/responses` for `'openai'` direct.
|
|
1260
|
+
// Same wire shape; the only difference is the `baseUrl` the resolver supplies.
|
|
1261
|
+
return buildOpenAILanguageModel({ getInfo, logBus, emitWireCommunication });
|
|
1262
|
+
case 'openai-compatible':
|
|
1263
|
+
// OpenAI-compatible Chat Completions API (LLMG Express, LiteLLM, etc.).
|
|
1264
|
+
// Different wire shape from Responses — `/v1/chat/completions` with
|
|
1265
|
+
// `choices[0].delta` streaming events.
|
|
1266
|
+
return buildOpenAIChatCompletionsLanguageModel({ getInfo, logBus, emitWireCommunication });
|
|
1267
|
+
default:
|
|
1268
|
+
// Defensive — manager-side G8 should have caught this before
|
|
1269
|
+
// we got here. Make the error typed so consumers can branch
|
|
1270
|
+
// on `err.type` instead of regexing the message.
|
|
1271
|
+
throw new AgentSDKError(`Mastra harness has no language-model builder for providerHint "${initial.providerHint}".`, AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1156
1274
|
async destroyThreadBestEffort(agentId, threadId) {
|
|
1157
1275
|
try {
|
|
1158
1276
|
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.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3Prompt } from '@ai-sdk/provider';
|
|
3
|
+
import type { LogBus } from '@salesforce/agentic-common';
|
|
4
|
+
import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
|
|
5
|
+
/**
|
|
6
|
+
* Builds the in-process `LanguageModelV3` impl for the Salesforce LLM Gateway's
|
|
7
|
+
* `/invoke-with-response-stream` (Bedrock-Anthropic pass-through) endpoint.
|
|
8
|
+
* Drives `@anthropic-ai/bedrock-sdk` with `skipAuth: true` so the connectivity
|
|
9
|
+
* resolver's headers (Salesforce JWT, tenant key, etc.) are the single source
|
|
10
|
+
* of auth.
|
|
11
|
+
*
|
|
12
|
+
* Each `doStream` / `doGenerate` call reads the live `ModelConnectivityInfo`
|
|
13
|
+
* via the supplied `getInfo` getter — allowing the harness to swap the bag
|
|
14
|
+
* (within-shape `modelId` change, JWT rotation, premium-rate-limit gate flip)
|
|
15
|
+
* between turns without rebuilding this language model. The fetch wrapper
|
|
16
|
+
* calls `info.getHeaders()` per HTTP call so credential rotation lands on
|
|
17
|
+
* the next request.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildAnthropicLanguageModel(opts: {
|
|
20
|
+
getInfo: () => ModelConnectivityInfo;
|
|
21
|
+
logBus?: LogBus;
|
|
22
|
+
emitWireCommunication?: (event: WireCommunicationEvent) => void;
|
|
23
|
+
}): LanguageModelV3;
|
|
24
|
+
/**
|
|
25
|
+
* Builds the in-process `LanguageModelV3` impl for direct-Anthropic API access
|
|
26
|
+
* (`api.anthropic.com/v1/messages` or any Anthropic-shaped endpoint). Drives
|
|
27
|
+
* `@anthropic-ai/sdk` with the api-key peeled from the resolver's headers.
|
|
28
|
+
*
|
|
29
|
+
* Authentication: the connectivity resolver emits `Authorization: Bearer <key>`
|
|
30
|
+
* as its generic credential carrier. The Anthropic Messages API requires
|
|
31
|
+
* `x-api-key` instead, so this builder peels the bearer token at construction
|
|
32
|
+
* time and passes it as the SDK's `apiKey` arg. The SDK then writes
|
|
33
|
+
* `x-api-key` and `anthropic-version` itself; our fetch wrapper supplies the
|
|
34
|
+
* resolver's remaining headers (typically `Content-Type` and any
|
|
35
|
+
* `model.customHeaders`) on top, with the bearer header explicitly stripped
|
|
36
|
+
* to avoid sending both auth shapes redundantly.
|
|
37
|
+
*
|
|
38
|
+
* Same per-call freshness contract as the Bedrock builder: every HTTP call
|
|
39
|
+
* re-evaluates `getHeaders()` via the fetch wrapper so a rotating api-key
|
|
40
|
+
* source (BYOK toggle, secret-store fetch) lands without rebuilding the
|
|
41
|
+
* language model.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildAnthropicDirectLanguageModel(opts: {
|
|
44
|
+
getInfo: () => ModelConnectivityInfo;
|
|
45
|
+
logBus?: LogBus;
|
|
46
|
+
emitWireCommunication?: (event: WireCommunicationEvent) => void;
|
|
47
|
+
}): LanguageModelV3;
|
|
48
|
+
/**
|
|
49
|
+
* Translates a V3 `LanguageModelV3Prompt` into Anthropic's Messages API shape
|
|
50
|
+
* — top-level `system` string + `MessageParam[]` of role-tagged content blocks.
|
|
51
|
+
*
|
|
52
|
+
* V3 keeps system / user / assistant / tool messages flat. Anthropic combines
|
|
53
|
+
* tool results onto a synthetic user-role turn carrying `tool_result` blocks,
|
|
54
|
+
* so we collapse consecutive tool-only V3 messages into the surrounding user
|
|
55
|
+
* turn shape.
|
|
56
|
+
*/
|
|
57
|
+
export declare function translatePromptToAnthropic(prompt: LanguageModelV3Prompt): {
|
|
58
|
+
system?: string;
|
|
59
|
+
messages: Anthropic.Messages.MessageParam[];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Translates V3 function-tool definitions into Anthropic's `Tool[]` shape.
|
|
63
|
+
* Skips non-function variants (provider-defined / dynamic) — the pass-through
|
|
64
|
+
* path supports only the function-tool subset that Mastra's tool mapper produces.
|
|
65
|
+
*/
|
|
66
|
+
export declare function translateToolsToAnthropic(options: LanguageModelV3CallOptions): Anthropic.Messages.Tool[];
|