@salesforce/sfdx-agent-harness-mastra 0.35.0 → 0.37.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 CHANGED
@@ -3,6 +3,19 @@
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.37.0] - 2026-07-28
7
+
8
+ ### Fixes
9
+ - **harness-openai**: treat usage: null like missing usage in finalizeUsage @W-23577076@ ([#697](https://github.com/forcedotcom/agentic-dx/pull/697))
10
+
11
+ ### Chores
12
+ - **deps**: bump the mastra group with 5 updates ([#706](https://github.com/forcedotcom/agentic-dx/pull/706))
13
+
14
+ ## [0.36.0] - 2026-07-27
15
+
16
+ ### Features
17
+ - **chat-generations**: GovCloud chat/generations fallback @W-23560954@ ([#695](https://github.com/forcedotcom/agentic-dx/pull/695))
18
+
6
19
  ## [0.35.0] - 2026-07-27
7
20
 
8
21
  _No changes — released alongside dependent packages._
package/README.md CHANGED
@@ -44,12 +44,14 @@ Linux and macOS release the handle eagerly; the retry options are no-ops on thos
44
44
 
45
45
  ### Core
46
46
 
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
- | `MASTRA_BUILT_IN_TOOL_POLICIES` | const | Frozen `ReadonlyArray<ToolPolicyRule>` the harness feeds the SDK's `resolveToolApprovalPolicy` as the `tiers.harness` slice — `allow` rules for Mastra's built-in `updateWorkingMemory` / `skill` / `skill_search` / `skill_read` tools. |
52
- | `MASTRA_BUILT_IN_TOOL_NAMES` | const | The four built-in tool leaf names `MASTRA_BUILT_IN_TOOL_POLICIES` covers. |
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?`, `languageModelBuilders?`). |
51
+ | `MastraLanguageModelBuilderContext` | type | Context bag the harness threads into each `LanguageModelV3` builder (`getInfo`, `logBus`, `emitWireCommunication`, `innerFetch`). |
52
+ | `MastraLanguageModelBuilder` | type | Factory function shape for a consumer-injected builder: `(ctx: MastraLanguageModelBuilderContext) => LanguageModelV3`. |
53
+ | `MASTRA_BUILT_IN_TOOL_POLICIES` | const | Frozen `ReadonlyArray<ToolPolicyRule>` the harness feeds the SDK's `resolveToolApprovalPolicy` as the `tiers.harness` slice — `allow` rules for Mastra's built-in `updateWorkingMemory` / `skill` / `skill_search` / `skill_read` tools. |
54
+ | `MASTRA_BUILT_IN_TOOL_NAMES` | const | The four built-in tool leaf names `MASTRA_BUILT_IN_TOOL_POLICIES` covers. |
53
55
 
54
56
  Connectivity (model + provider auth) is supplied by the SDK's `AgentConnectivityResolver`. Use the bundled
55
57
  `DefaultAgentConnectivityResolver` for the standard Salesforce LLM Gateway path, or `ApiKeyConnectivityResolver` for
@@ -66,6 +68,32 @@ no `globalThis` mutation. When no proxy env is set, `fetch` runs unwrapped (zero
66
68
  **Snapshot timing.** `HTTPS_PROXY` / `HTTP_PROXY` are captured at `create()` time and pinned for the harness's lifetime;
67
69
  `NO_PROXY` is re-evaluated per request. Set the proxy env vars before constructing the factory's first harness.
68
70
 
71
+ #### Injecting custom wire shapes
72
+
73
+ A consumer can register a `LanguageModelV3` builder for a custom `providerHint` via
74
+ `MastraHarnessFactoryConfig.languageModelBuilders` without editing the harness's internal switch. Each injected key
75
+ becomes a supported provider hint and a dispatch arm. Used to inject wire shapes the harness does not ship natively
76
+ (e.g., a temporary GovCloud `chat/generations` fallback).
77
+
78
+ ```ts
79
+ import {
80
+ CHAT_GENERATIONS_PROVIDER_HINT,
81
+ buildChatGenerationsLanguageModel,
82
+ } from '@salesforce/sfdx-agent-chat-generations';
83
+
84
+ const factory = new MastraHarnessFactory({
85
+ languageModelBuilders: {
86
+ [CHAT_GENERATIONS_PROVIDER_HINT]: buildChatGenerationsLanguageModel,
87
+ },
88
+ });
89
+ ```
90
+
91
+ The builder receives the same `MastraLanguageModelBuilderContext` bag (`getInfo`, `logBus`, `emitWireCommunication`,
92
+ `innerFetch`) the harness's built-in builders get, so an injected builder can emit wire-monitoring events and honor
93
+ `HTTPS_PROXY` routing without duplicating the harness's plumbing. Injected keys MUST NOT collide with built-in hints
94
+ (`anthropic`, `bedrock-anthropic`, `openai`, `openai-responses`, `openai-compatible`) — the factory throws at
95
+ construction time if a collision is detected.
96
+
69
97
  ### Extension surface
70
98
 
71
99
  These types let consumers reach Mastra-specific features through the SDK's typed `extensions` slot. The SDK infers
package/dist/index.d.ts CHANGED
@@ -1,4 +1,50 @@
1
+ import type { LogBus } from '@salesforce/agentic-common';
2
+ import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
3
+ import type { LanguageModelV3 } from '@ai-sdk/provider';
1
4
  export { MastraHarnessFactory, type MastraHarnessFactoryConfig } from './mastra-harness-factory.js';
2
5
  export type { MastraAgentHarness } from './mastra-harness.js';
3
6
  export { MASTRA_BUILT_IN_TOOL_NAMES, MASTRA_BUILT_IN_TOOL_POLICIES } from './mastra-built-in-policies.js';
4
7
  export { type MastraAgentConfig, type MastraSkillSearchConfig, type MastraToolSearchConfig, type SkillSearchHandle, type ToolSearchHandle, } from './tool-search.js';
8
+ /**
9
+ * Context the harness threads into each `LanguageModelV3` builder — both the
10
+ * built-in builders and any consumer-injected builders registered via
11
+ * `MastraHarnessFactoryConfig.languageModelBuilders`. The shape mirrors what
12
+ * `MastraHarness.buildLanguageModel` currently hands its internal builders, so an
13
+ * injected builder receives the same context bag and can compose wire monitoring +
14
+ * proxy routing identically to the native pass-through builders.
15
+ *
16
+ * @remarks
17
+ * All fields come from the harness:
18
+ * - `getInfo()` returns the live `ModelConnectivityInfo` per outbound call (JWT
19
+ * rotation + within-shape model swaps land on the next request without rebuilding).
20
+ * - `logBus` is the harness's `LogBus` (optional — may be undefined during early
21
+ * construction phases).
22
+ * - `emitWireCommunication` emits `LlmRequestEvent` / `LlmResponseEvent` pairs onto
23
+ * the harness's wire bus (for monitoring / debugging).
24
+ * - `innerFetch` is the harness's proxy-aware fetch (HTTPS_PROXY routing when
25
+ * configured; `globalThis.fetch` otherwise).
26
+ */
27
+ export type MastraLanguageModelBuilderContext = {
28
+ getInfo: () => ModelConnectivityInfo;
29
+ logBus?: LogBus;
30
+ emitWireCommunication: (event: WireCommunicationEvent) => void;
31
+ innerFetch: typeof fetch;
32
+ };
33
+ /**
34
+ * Factory function shape for a consumer-injected `LanguageModelV3` builder.
35
+ * Registered via `MastraHarnessFactoryConfig.languageModelBuilders` keyed by a custom
36
+ * `providerHint`, used to inject wire shapes the harness does not ship natively
37
+ * (e.g. a temporary GovCloud `chat/generations` fallback). The builder receives the
38
+ * same context bag the harness's built-in pass-through builders get, so an injected
39
+ * builder can emit `WireCommunicationEvent`s and honor `HTTPS_PROXY` routing without
40
+ * duplicating the harness's own fetch wrapper / wire-monitoring plumbing.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // A consumer package registers a builder for its own custom providerHint.
45
+ * const factory = new MastraHarnessFactory({
46
+ * languageModelBuilders: { 'my-custom-wire-shape': (ctx) => buildMyLanguageModel(ctx) },
47
+ * });
48
+ * ```
49
+ */
50
+ export type MastraLanguageModelBuilder = (ctx: MastraLanguageModelBuilderContext) => LanguageModelV3;
package/dist/index.js CHANGED
@@ -8,11 +8,4 @@ export { MastraHarnessFactory } from './mastra-harness-factory.js';
8
8
  // inspect which Mastra built-ins the harness auto-approves.
9
9
  export { MASTRA_BUILT_IN_TOOL_NAMES, MASTRA_BUILT_IN_TOOL_POLICIES } from './mastra-built-in-policies.js';
10
10
  export {} from './tool-search.js';
11
- // Mastra drives requests through provider-native pass-through endpoints —
12
- // Bedrock-Anthropic, direct Anthropic Messages API, OpenAI Responses, and
13
- // OpenAI Chat Completions (LLMG Express / openai-compatible) — dispatched on
14
- // the connectivity resolver's `ModelConnectivityInfo.providerHint`. There's
15
- // no consumer-facing knob for switching the wire path; the resolver drives
16
- // it. For BYOK / direct-Anthropic / LLMG-Express deployments use
17
- // `ApiKeyConnectivityResolver` from `@salesforce/sfdx-agent-sdk`.
18
11
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,6 @@
1
1
  import type { HarnessFactory, ProviderHint } from '@salesforce/sfdx-agent-sdk';
2
2
  import { type MastraAgentHarness } from './mastra-harness.js';
3
+ import type { MastraLanguageModelBuilder } from './index.js';
3
4
  export interface MastraHarnessFactoryConfig {
4
5
  /**
5
6
  * How long to wait for a consumer to call
@@ -10,6 +11,34 @@ export interface MastraHarnessFactoryConfig {
10
11
  * `ClaudeHarnessFactoryConfig.toolApprovalTimeoutMs`.
11
12
  */
12
13
  toolApprovalTimeoutMs?: number;
14
+ /**
15
+ * Consumer-registered `LanguageModelV3` builders keyed by `providerHint`. Each key
16
+ * becomes a supported provider hint and a dispatch arm in `buildLanguageModel`. Used
17
+ * to inject wire shapes the harness does not ship natively (e.g. a temporary
18
+ * GovCloud `chat/generations` fallback path). Keys MUST NOT collide with the
19
+ * harness's built-in hints (`anthropic`, `bedrock-anthropic`, `openai`,
20
+ * `openai-responses`, `openai-compatible`) — constructing with a colliding key
21
+ * throws at factory construction time rather than silently shadowing the built-in.
22
+ *
23
+ * @remarks
24
+ * The builder's context parameter provides the full `ModelConnectivityInfo` bag
25
+ * (via `getInfo()`), the harness's `LogBus`, a `WireCommunicationEvent` emitter,
26
+ * and the proxy-aware `innerFetch` — identical to what the harness's built-in
27
+ * builders receive. Each injected `providerHint` is advertised on
28
+ * `MastraHarnessFactory.supportedProviderHints` so the manager's pre-flight
29
+ * validation (G8) passes.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // A consumer package supplies a builder for its own custom providerHint.
34
+ * const factory = new MastraHarnessFactory({
35
+ * languageModelBuilders: {
36
+ * 'my-custom-wire-shape': (ctx) => buildMyLanguageModel(ctx),
37
+ * },
38
+ * });
39
+ * ```
40
+ */
41
+ languageModelBuilders?: Readonly<Record<string, MastraLanguageModelBuilder>>;
13
42
  }
14
43
  /** Mastra-backed factory for creating AgentHarness instances. */
15
44
  export declare class MastraHarnessFactory implements HarnessFactory<MastraAgentHarness> {
@@ -17,6 +46,7 @@ export declare class MastraHarnessFactory implements HarnessFactory<MastraAgentH
17
46
  readonly harnessId = "mastra";
18
47
  readonly protocolVersion = 1;
19
48
  readonly supportedProviderHints: readonly ProviderHint[];
49
+ private static readonly BUILT_IN_HINTS;
20
50
  constructor(config?: MastraHarnessFactoryConfig);
21
51
  create(storageRootFolder: string): Promise<MastraAgentHarness>;
22
52
  }
@@ -19,7 +19,8 @@ export class MastraHarnessFactory {
19
19
  config;
20
20
  harnessId = 'mastra';
21
21
  protocolVersion = 1;
22
- supportedProviderHints = [
22
+ supportedProviderHints;
23
+ static BUILT_IN_HINTS = [
23
24
  'anthropic',
24
25
  'bedrock-anthropic',
25
26
  'openai',
@@ -28,12 +29,23 @@ export class MastraHarnessFactory {
28
29
  ];
29
30
  constructor(config = {}) {
30
31
  this.config = config;
32
+ const injected = Object.keys(config.languageModelBuilders ?? {});
33
+ // Collision guard: an injected key shadowing a built-in hint is a footgun
34
+ // (silent override of a native wire shape). Fail loud at construction time
35
+ // so the mistake is caught before any harness work runs.
36
+ for (const hint of injected) {
37
+ if (MastraHarnessFactory.BUILT_IN_HINTS.includes(hint)) {
38
+ throw new Error(`MastraHarnessFactory: injected languageModelBuilder key "${hint}" collides with a built-in provider hint. Choose a distinct key.`);
39
+ }
40
+ }
41
+ this.supportedProviderHints = [...MastraHarnessFactory.BUILT_IN_HINTS, ...injected];
31
42
  }
32
43
  async create(storageRootFolder) {
33
44
  const innerFetch = createProxyAwareFetch(resolveProxyDispatcher());
34
45
  return new MastraHarness(storageRootFolder, {
35
46
  toolApprovalTimeoutMs: this.config.toolApprovalTimeoutMs ?? DEFAULT_TOOL_APPROVAL_TIMEOUT_MS,
36
47
  innerFetch,
48
+ languageModelBuilders: this.config.languageModelBuilders,
37
49
  });
38
50
  }
39
51
  }
@@ -3,10 +3,12 @@ import { SkillSearchProcessor, ToolSearchProcessor } from '@mastra/core/processo
3
3
  import { MCPClient } from '@mastra/mcp';
4
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
+ import type { MastraLanguageModelBuilderContext } from './index.js';
6
7
  import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
7
8
  import { type HttpErrorCapture } from './mcp-config-mapper.js';
8
9
  import { type MastraAgentConfig, type SkillSearchHandle, type ToolSearchHandle } from './tool-search.js';
9
10
  import { type MultimodalFile } from '@salesforce/sfdx-agent-sdk';
11
+ import type { LanguageModelV3 } from '@ai-sdk/provider';
10
12
  import { Clock, type LogRecord, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
11
13
  import { type ToolsInput } from './utils.js';
12
14
  import { Workspace } from '@mastra/core/workspace';
@@ -223,6 +225,14 @@ export interface MastraHarnessOptions {
223
225
  * any `globalThis` mutation.
224
226
  */
225
227
  innerFetch?: typeof fetch;
228
+ /**
229
+ * Consumer-registered `LanguageModelV3` builders keyed by `providerHint`.
230
+ * Forwarded from `MastraHarnessFactoryConfig.languageModelBuilders`. The
231
+ * harness consults this map in `buildLanguageModel`'s `default` arm before
232
+ * throwing `MODEL_NOT_SUPPORTED_BY_HARNESS`, so an injected builder can
233
+ * handle a custom hint without editing the harness's internal switch.
234
+ */
235
+ languageModelBuilders?: Readonly<Record<string, (ctx: MastraLanguageModelBuilderContext) => LanguageModelV3>>;
226
236
  }
227
237
  export declare class MastraHarness implements MastraAgentHarness {
228
238
  private readonly storageRootFolder;
@@ -256,6 +266,7 @@ export declare class MastraHarness implements MastraAgentHarness {
256
266
  private readonly idGenerator;
257
267
  private readonly toolApprovalTimeoutMs;
258
268
  private readonly innerFetch;
269
+ private readonly languageModelBuilders?;
259
270
  constructor(storageRootFolder: string, opts?: MastraHarnessOptions);
260
271
  /**
261
272
  * @requirements
@@ -398,12 +398,14 @@ export class MastraHarness {
398
398
  idGenerator;
399
399
  toolApprovalTimeoutMs;
400
400
  innerFetch;
401
+ languageModelBuilders;
401
402
  constructor(storageRootFolder, opts = {}) {
402
403
  this.storageRootFolder = storageRootFolder;
403
404
  this.clock = opts.clock ?? new RealClock();
404
405
  this.idGenerator = opts.idGenerator ?? new UUIDGenerator();
405
406
  this.toolApprovalTimeoutMs = opts.toolApprovalTimeoutMs ?? 600_000;
406
407
  this.innerFetch = opts.innerFetch ?? globalThis.fetch;
408
+ this.languageModelBuilders = opts.languageModelBuilders;
407
409
  this.storage = new LibSQLStore({
408
410
  id: 'mastra-storage',
409
411
  url: `file:${this.storageRootFolder}/mastra.db`,
@@ -1307,11 +1309,20 @@ export class MastraHarness {
1307
1309
  // Different wire shape from Responses — `/v1/chat/completions` with
1308
1310
  // `choices[0].delta` streaming events.
1309
1311
  return buildOpenAIChatCompletionsLanguageModel({ getInfo, logBus, emitWireCommunication, innerFetch });
1310
- default:
1312
+ default: {
1313
+ // Consult the injected builders map before throwing. This is the
1314
+ // extension point for consumer-registered wire shapes (e.g. a temporary
1315
+ // GovCloud chat/generations fallback, W-23560954). The builder receives
1316
+ // the same context bag the built-in builders get.
1317
+ const custom = this.languageModelBuilders?.[initial.providerHint];
1318
+ if (custom) {
1319
+ return custom({ getInfo, logBus, emitWireCommunication, innerFetch });
1320
+ }
1311
1321
  // Defensive — manager-side G8 should have caught this before
1312
1322
  // we got here. Make the error typed so consumers can branch
1313
1323
  // on `err.type` instead of regexing the message.
1314
1324
  throw new AgentSDKError(`Mastra harness has no language-model builder for providerHint "${initial.providerHint}".`, AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS);
1325
+ }
1315
1326
  }
1316
1327
  }
1317
1328
  async destroyThreadBestEffort(agentId, threadId) {
@@ -357,7 +357,9 @@ function toOpenAIInputFileOrImage(part) {
357
357
  * back to `undefined` `UsageMetadata` for consumers.
358
358
  */
359
359
  function finalizeUsage(usage, ctx, finishRaw) {
360
- const mapped = mapOpenAIUsage(usage === undefined
360
+ const mapped = mapOpenAIUsage(
361
+ // Gateway returns usage: null (not undefined) on billable-token-free completions; both collapse to the sentinel.
362
+ usage == null
361
363
  ? undefined
362
364
  : {
363
365
  prompt_tokens: usage.input_tokens,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-harness-mastra",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "Mastra-backed AgentHarness implementation for @salesforce/sfdx-agent-sdk",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,21 +32,21 @@
32
32
  "@ai-sdk/provider": "^3.0.10",
33
33
  "@anthropic-ai/bedrock-sdk": "^0.32.0",
34
34
  "@anthropic-ai/sdk": "^0.111.0",
35
- "@mastra/core": "^1.50.1",
36
- "@mastra/libsql": "^1.15.1",
37
- "@mastra/mcp": "^1.13.1",
38
- "@mastra/memory": "^1.22.2",
35
+ "@mastra/core": "^1.51.0",
36
+ "@mastra/libsql": "^1.16.0",
37
+ "@mastra/mcp": "^1.14.0",
38
+ "@mastra/memory": "^1.23.0",
39
39
  "@mastra/schema-compat": "^1.2.11",
40
40
  "@salesforce/agentic-common": "0.13.0",
41
41
  "openai": "^6.0.0"
42
42
  },
43
43
  "peerDependencies": {
44
- "@salesforce/sfdx-agent-sdk": "0.36.0"
44
+ "@salesforce/sfdx-agent-sdk": "0.38.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@eslint/js": "^10.0.1",
48
48
  "@salesforce/harness-conformance": "0.0.1",
49
- "@salesforce/sfdx-agent-sdk": "0.36.0",
49
+ "@salesforce/sfdx-agent-sdk": "0.38.0",
50
50
  "@types/node": "^22.20.0",
51
51
  "@vitest/coverage-istanbul": "^4.1.10",
52
52
  "@vitest/eslint-plugin": "^1.6.22",