@salesforce/sfdx-agent-harness-mastra 0.19.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 +16 -0
- package/README.md +24 -20
- package/dist/index.d.ts +0 -3
- package/dist/index.js +7 -2
- package/dist/mastra-approval-coordinator.d.ts +46 -8
- package/dist/mastra-approval-coordinator.js +85 -27
- 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,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.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
|
+
|
|
17
|
+
## [0.20.0] - 2026-06-15
|
|
18
|
+
|
|
19
|
+
### Fixes
|
|
20
|
+
- **harness-claude,harness-mastra,agent-sdk**: idempotent settle + quiet dispose (#589) ([#598](https://github.com/forcedotcom/agentic-dx/pull/598))
|
|
21
|
+
|
|
6
22
|
## [0.19.0] - 2026-06-12
|
|
7
23
|
|
|
8
24
|
### Features
|
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
|
|
|
@@ -203,9 +203,12 @@ active `eventStream`:
|
|
|
203
203
|
{ type: 'finish', finishReason: 'error' }
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
Subsequent `approveToolCall` / `declineToolCall` / `submitToolResult` calls for the timed-out tool
|
|
207
|
-
coordinator
|
|
208
|
-
|
|
206
|
+
Subsequent `approveToolCall` / `declineToolCall` / `submitToolResult` calls for the timed-out tool are silent no-ops
|
|
207
|
+
(issue #589 idempotent settle) — the coordinator already auto-resolved them, so the consumer's intended outcome is
|
|
208
|
+
already true and there's nothing to fail. Only a `toolCallId` the coordinator never saw at all (typo, wrong session,
|
|
209
|
+
called before `tool-approval-request` ever fired) throws, and that throw is an `AgentSDKError` whose
|
|
210
|
+
`type === AgentSDKErrorType.TOOL_CALL_NOT_FOUND`. The granularity is per `toolCallId` — each emitted approval arms its
|
|
211
|
+
own timer; the first one to fire tears down the whole turn (since Mastra can't continue the suspended run after timeout
|
|
209
212
|
anyway). To observe the timeout, keep iterating the `eventStream` past the `tool-approval-request` event so the
|
|
210
213
|
synthetic terminal pair lands in your loop:
|
|
211
214
|
|
|
@@ -233,19 +236,20 @@ contract.
|
|
|
233
236
|
|
|
234
237
|
### Using LLM Gateway Express
|
|
235
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
|
+
|
|
236
242
|
```ts
|
|
237
|
-
import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
238
|
-
import { MastraHarnessFactory
|
|
243
|
+
import { ApiKeyConnectivityResolver, createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
244
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
239
245
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
const manager = await createAgentManager('/path/to/storage', new MastraHarnessFactory(), {
|
|
247
|
+
connectivityResolver: new ApiKeyConnectivityResolver({
|
|
248
|
+
getApiKey: () => process.env.LLMG_EXPRESS_API_KEY!,
|
|
243
249
|
baseUrl: process.env.LLMG_EXPRESS_BASE_URL!,
|
|
244
|
-
|
|
250
|
+
providerHint: 'openai-compatible',
|
|
245
251
|
}),
|
|
246
252
|
});
|
|
247
|
-
|
|
248
|
-
const manager = await createAgentManager('/path/to/storage', harnessFactory);
|
|
249
253
|
```
|
|
250
254
|
|
|
251
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
|
|
@@ -128,8 +128,11 @@ export declare class MastraApprovalCoordinator {
|
|
|
128
128
|
private readonly consumerSettled;
|
|
129
129
|
/**
|
|
130
130
|
* ToolCallIds the coordinator auto-resolved (timeout / abort / dispose
|
|
131
|
-
* / consumer-tool auto-approve). A subsequent settle for
|
|
132
|
-
*
|
|
131
|
+
* / pump-error / consumer-tool auto-approve). A subsequent settle for
|
|
132
|
+
* these ids is a silent no-op via the idempotent-settle contract
|
|
133
|
+
* (issue #589) — the lost race is unactionable from the consumer's
|
|
134
|
+
* side, and the methods are void-typed so there's no return value to
|
|
135
|
+
* communicate the no-op.
|
|
133
136
|
*/
|
|
134
137
|
private readonly settledByCoordinator;
|
|
135
138
|
/** Per-toolCallId approval-timeout timers (armed when an approval is emitted). */
|
|
@@ -181,6 +184,15 @@ export declare class MastraApprovalCoordinator {
|
|
|
181
184
|
* parker invocation for the same id.
|
|
182
185
|
*/
|
|
183
186
|
private readonly preSubmittedByToolCallId;
|
|
187
|
+
/**
|
|
188
|
+
* ToolCallIds the consumer has settled via {@link submitToolResult},
|
|
189
|
+
* or that the coordinator drained at teardown time. Used as the
|
|
190
|
+
* idempotent-settle ledger for the consumer-tool path (parallel to
|
|
191
|
+
* `consumerSettled` + `settledByCoordinator` on the approve/decline
|
|
192
|
+
* path). A subsequent `submitToolResult` for these ids is a silent
|
|
193
|
+
* no-op rather than throwing `TOOL_CALL_NOT_FOUND` (issue #589).
|
|
194
|
+
*/
|
|
195
|
+
private readonly consumerToolSettled;
|
|
184
196
|
/**
|
|
185
197
|
* Callbacks invoked exactly once during teardown (`abort` or `dispose`,
|
|
186
198
|
* whichever fires first, or natural pump exit).
|
|
@@ -238,13 +250,39 @@ export declare class MastraApprovalCoordinator {
|
|
|
238
250
|
/**
|
|
239
251
|
* Single teardown path for abort / dispose / approval-timeout / pump-
|
|
240
252
|
* error. Drains timers, rejects pending consumer-tool parkers, marks
|
|
241
|
-
* every emitted approval
|
|
242
|
-
* suspended run, pushes the
|
|
243
|
-
*
|
|
253
|
+
* every emitted approval and consumer-tool toolCallId as
|
|
254
|
+
* coordinator-settled, releases Mastra's suspended run, pushes the
|
|
255
|
+
* typed `error` + `finish('error')` pair on the sink, and fires
|
|
256
|
+
* teardown callbacks.
|
|
257
|
+
*
|
|
258
|
+
* Every teardown reason — including `'dispose'` — pushes both events
|
|
259
|
+
* (issue #589). Two reasons:
|
|
260
|
+
*
|
|
261
|
+
* - **Consumers route on `error.code`, not on the *presence* of an
|
|
262
|
+
* error event.** AFV's `chat-stream-service.ts` maps
|
|
263
|
+
* `error.code === AgentSDKErrorType.DISPOSED` → its internal
|
|
264
|
+
* `AGENT_CANCELLED` and reaps the turn as cancelled (rather than
|
|
265
|
+
* completed or failed). Suppressing the dispose error event would
|
|
266
|
+
* delete that signal and a turn that was actually torn down would
|
|
267
|
+
* record as successful in their telemetry pipeline. The original
|
|
268
|
+
* bug (issue #589 failure 2) was *untyped* error noise —
|
|
269
|
+
* `error.code` is already typed as `AgentSDKErrorType.DISPOSED`,
|
|
270
|
+
* which gives consumers a clean discriminator without
|
|
271
|
+
* string-matching.
|
|
272
|
+
*
|
|
273
|
+
* - **The SDK invariant requires every chat turn to end with one
|
|
274
|
+
* terminal `FinishEvent`.** `DefaultChatSession.wrapEventStream`
|
|
275
|
+
* only synthesizes a synthetic `finish` when an error landed on
|
|
276
|
+
* the stream; if dispose ended the sink without an error, the
|
|
277
|
+
* turn would end with neither a natural finish nor a synthetic
|
|
278
|
+
* one — breaking the SDK contract pinned in
|
|
279
|
+
* `packages/sfdx-agent-sdk/ARCHITECTURE.md` →
|
|
280
|
+
* "Critical Invariants" → "Every chat turn ends with exactly one
|
|
281
|
+
* terminal FinishEvent".
|
|
244
282
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
283
|
+
* Mirrors the Claude coordinator's `teardown` so the abort / dispose
|
|
284
|
+
* / timeout / pump-error paths can't drift out of step on either
|
|
285
|
+
* harness — issue #589 lands the same shape on both.
|
|
248
286
|
*/
|
|
249
287
|
private teardown;
|
|
250
288
|
approve(toolCallId: string): void;
|
|
@@ -64,8 +64,11 @@ export class MastraApprovalCoordinator {
|
|
|
64
64
|
consumerSettled = new Set();
|
|
65
65
|
/**
|
|
66
66
|
* ToolCallIds the coordinator auto-resolved (timeout / abort / dispose
|
|
67
|
-
* / consumer-tool auto-approve). A subsequent settle for
|
|
68
|
-
*
|
|
67
|
+
* / pump-error / consumer-tool auto-approve). A subsequent settle for
|
|
68
|
+
* these ids is a silent no-op via the idempotent-settle contract
|
|
69
|
+
* (issue #589) — the lost race is unactionable from the consumer's
|
|
70
|
+
* side, and the methods are void-typed so there's no return value to
|
|
71
|
+
* communicate the no-op.
|
|
69
72
|
*/
|
|
70
73
|
settledByCoordinator = new Set();
|
|
71
74
|
/** Per-toolCallId approval-timeout timers (armed when an approval is emitted). */
|
|
@@ -117,6 +120,15 @@ export class MastraApprovalCoordinator {
|
|
|
117
120
|
* parker invocation for the same id.
|
|
118
121
|
*/
|
|
119
122
|
preSubmittedByToolCallId = new Map();
|
|
123
|
+
/**
|
|
124
|
+
* ToolCallIds the consumer has settled via {@link submitToolResult},
|
|
125
|
+
* or that the coordinator drained at teardown time. Used as the
|
|
126
|
+
* idempotent-settle ledger for the consumer-tool path (parallel to
|
|
127
|
+
* `consumerSettled` + `settledByCoordinator` on the approve/decline
|
|
128
|
+
* path). A subsequent `submitToolResult` for these ids is a silent
|
|
129
|
+
* no-op rather than throwing `TOOL_CALL_NOT_FOUND` (issue #589).
|
|
130
|
+
*/
|
|
131
|
+
consumerToolSettled = new Set();
|
|
120
132
|
/**
|
|
121
133
|
* Callbacks invoked exactly once during teardown (`abort` or `dispose`,
|
|
122
134
|
* whichever fires first, or natural pump exit).
|
|
@@ -229,18 +241,52 @@ export class MastraApprovalCoordinator {
|
|
|
229
241
|
/**
|
|
230
242
|
* Single teardown path for abort / dispose / approval-timeout / pump-
|
|
231
243
|
* error. Drains timers, rejects pending consumer-tool parkers, marks
|
|
232
|
-
* every emitted approval
|
|
233
|
-
* suspended run, pushes the
|
|
234
|
-
*
|
|
244
|
+
* every emitted approval and consumer-tool toolCallId as
|
|
245
|
+
* coordinator-settled, releases Mastra's suspended run, pushes the
|
|
246
|
+
* typed `error` + `finish('error')` pair on the sink, and fires
|
|
247
|
+
* teardown callbacks.
|
|
248
|
+
*
|
|
249
|
+
* Every teardown reason — including `'dispose'` — pushes both events
|
|
250
|
+
* (issue #589). Two reasons:
|
|
251
|
+
*
|
|
252
|
+
* - **Consumers route on `error.code`, not on the *presence* of an
|
|
253
|
+
* error event.** AFV's `chat-stream-service.ts` maps
|
|
254
|
+
* `error.code === AgentSDKErrorType.DISPOSED` → its internal
|
|
255
|
+
* `AGENT_CANCELLED` and reaps the turn as cancelled (rather than
|
|
256
|
+
* completed or failed). Suppressing the dispose error event would
|
|
257
|
+
* delete that signal and a turn that was actually torn down would
|
|
258
|
+
* record as successful in their telemetry pipeline. The original
|
|
259
|
+
* bug (issue #589 failure 2) was *untyped* error noise —
|
|
260
|
+
* `error.code` is already typed as `AgentSDKErrorType.DISPOSED`,
|
|
261
|
+
* which gives consumers a clean discriminator without
|
|
262
|
+
* string-matching.
|
|
235
263
|
*
|
|
236
|
-
* The
|
|
237
|
-
* `
|
|
238
|
-
*
|
|
264
|
+
* - **The SDK invariant requires every chat turn to end with one
|
|
265
|
+
* terminal `FinishEvent`.** `DefaultChatSession.wrapEventStream`
|
|
266
|
+
* only synthesizes a synthetic `finish` when an error landed on
|
|
267
|
+
* the stream; if dispose ended the sink without an error, the
|
|
268
|
+
* turn would end with neither a natural finish nor a synthetic
|
|
269
|
+
* one — breaking the SDK contract pinned in
|
|
270
|
+
* `packages/sfdx-agent-sdk/ARCHITECTURE.md` →
|
|
271
|
+
* "Critical Invariants" → "Every chat turn ends with exactly one
|
|
272
|
+
* terminal FinishEvent".
|
|
273
|
+
*
|
|
274
|
+
* Mirrors the Claude coordinator's `teardown` so the abort / dispose
|
|
275
|
+
* / timeout / pump-error paths can't drift out of step on either
|
|
276
|
+
* harness — issue #589 lands the same shape on both.
|
|
239
277
|
*/
|
|
240
278
|
teardown(reason, error, code) {
|
|
241
279
|
this.cancelAllTimers();
|
|
242
280
|
for (const id of this.emittedApprovals)
|
|
243
281
|
this.settledByCoordinator.add(id);
|
|
282
|
+
// Issue #589: drain in-flight consumer-tool ids into the
|
|
283
|
+
// idempotent-settle ledger so a late `submitToolResult` after
|
|
284
|
+
// teardown is a silent no-op rather than throwing
|
|
285
|
+
// `TOOL_CALL_NOT_FOUND`. Mirrors the approve/decline drain above.
|
|
286
|
+
for (const id of this.pendingByToolCallId.keys())
|
|
287
|
+
this.consumerToolSettled.add(id);
|
|
288
|
+
for (const id of this.toolCallArgs.keys())
|
|
289
|
+
this.consumerToolSettled.add(id);
|
|
244
290
|
// Reject every parked consumer-tool call so the bridge handler
|
|
245
291
|
// unblocks with an error rather than waiting forever.
|
|
246
292
|
for (const entry of this.pendingByToolCallId.values())
|
|
@@ -264,18 +310,9 @@ export class MastraApprovalCoordinator {
|
|
|
264
310
|
const resolve = this.continuationResolver;
|
|
265
311
|
this.continuationResolver = undefined;
|
|
266
312
|
resolve?.('done');
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// end, so a `finish` event would be misleading.
|
|
271
|
-
this.initial.push({ type: 'error', error, code });
|
|
272
|
-
this.initial.end();
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
this.initial.push({ type: 'error', error, code });
|
|
276
|
-
this.initial.push({ type: 'finish', finishReason: 'error' });
|
|
277
|
-
this.initial.end();
|
|
278
|
-
}
|
|
313
|
+
this.initial.push({ type: 'error', error, code });
|
|
314
|
+
this.initial.push({ type: 'finish', finishReason: 'error' });
|
|
315
|
+
this.initial.end();
|
|
279
316
|
this.pendingSuspendIds.clear();
|
|
280
317
|
this.runTeardownCallbacks();
|
|
281
318
|
}
|
|
@@ -287,14 +324,21 @@ export class MastraApprovalCoordinator {
|
|
|
287
324
|
this.recordDecision('decline', toolCallId, { kind: 'decline' });
|
|
288
325
|
}
|
|
289
326
|
recordDecision(method, toolCallId, decision) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
327
|
+
// Issue #589: idempotent settle. A tool call the coordinator
|
|
328
|
+
// already auto-resolved (timeout / abort / dispose / pump-error)
|
|
329
|
+
// or the consumer already settled is a lost-race no-op rather
|
|
330
|
+
// than a throw — settle methods are void-typed, so the consumer
|
|
331
|
+
// never branches on a return value, and the outcome they wanted
|
|
332
|
+
// (id is settled) is already true.
|
|
333
|
+
if (this.settledByCoordinator.has(toolCallId) || this.consumerSettled.has(toolCallId)) {
|
|
334
|
+
return;
|
|
295
335
|
}
|
|
296
336
|
if (!this.emittedApprovals.has(toolCallId)) {
|
|
297
|
-
|
|
337
|
+
// Genuine misuse — the coordinator never saw this id (typo,
|
|
338
|
+
// wrong session, or settle called before approval-request /
|
|
339
|
+
// tool-call ever fired). Typed so consumers can branch on
|
|
340
|
+
// err.type instead of string-matching the message.
|
|
341
|
+
throw new AgentSDKError(`MastraApprovalCoordinator.${method}(): no pending approval for tool call "${toolCallId}"`, AgentSDKErrorType.TOOL_CALL_NOT_FOUND);
|
|
298
342
|
}
|
|
299
343
|
this.consumerSettled.add(toolCallId);
|
|
300
344
|
this.cancelTimer(toolCallId);
|
|
@@ -344,8 +388,18 @@ export class MastraApprovalCoordinator {
|
|
|
344
388
|
* resolves (probe-validated, see #529 step 2c).
|
|
345
389
|
*/
|
|
346
390
|
submitToolResult(toolCallId, result, isError) {
|
|
391
|
+
// Issue #589: idempotent settle. A submitToolResult for an id the
|
|
392
|
+
// consumer already settled, or that the coordinator drained at
|
|
393
|
+
// teardown time, is a silent no-op — the lost-race shape is
|
|
394
|
+
// unactionable from the consumer's side, and the method is
|
|
395
|
+
// void-typed. Mirrors the approve/decline path's
|
|
396
|
+
// `consumerSettled` + `settledByCoordinator` ledger.
|
|
397
|
+
if (this.consumerToolSettled.has(toolCallId)) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
347
400
|
const entry = this.pendingByToolCallId.get(toolCallId);
|
|
348
401
|
if (entry) {
|
|
402
|
+
this.consumerToolSettled.add(toolCallId);
|
|
349
403
|
this.pendingByToolCallId.delete(toolCallId);
|
|
350
404
|
entry.resolve({ result, isError });
|
|
351
405
|
return;
|
|
@@ -355,10 +409,14 @@ export class MastraApprovalCoordinator {
|
|
|
355
409
|
// We allow this whenever the coordinator has seen a `tool-call`
|
|
356
410
|
// event under that id (reachable in `toolCallArgs`); else throw.
|
|
357
411
|
if (this.toolCallArgs.has(toolCallId)) {
|
|
412
|
+
this.consumerToolSettled.add(toolCallId);
|
|
358
413
|
this.preSubmittedByToolCallId.set(toolCallId, { result, isError });
|
|
359
414
|
return;
|
|
360
415
|
}
|
|
361
|
-
|
|
416
|
+
// Issue #589: typed error — the coordinator never saw this id, so
|
|
417
|
+
// this is genuine misuse rather than the lost-race shape the
|
|
418
|
+
// idempotent-settle path collapses to a no-op.
|
|
419
|
+
throw new AgentSDKError(`MastraApprovalCoordinator.submitToolResult(): no pending tool call for id "${toolCallId}"`, AgentSDKErrorType.TOOL_CALL_NOT_FOUND);
|
|
362
420
|
}
|
|
363
421
|
// ───────────────────────── pump loop ────────────────────────────────────
|
|
364
422
|
/**
|
|
@@ -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
|