@salesforce/sfdx-agent-harness-mastra 0.77.0 → 0.79.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,16 @@
|
|
|
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.79.0] - 2026-09-14
|
|
7
|
+
|
|
8
|
+
### Fixes
|
|
9
|
+
- **harness-mastra**: stamp Anthropic cache_control in the Mastra pass-through @W-24130072@ ([#811](https://github.com/forcedotcom/agentic-dx/pull/811))
|
|
10
|
+
|
|
11
|
+
## [0.78.0] - 2026-09-14
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- add ChatSession.cancelTurn() with abort-persistence barrier @W-23559264@ ([#810](https://github.com/forcedotcom/agentic-dx/pull/810))
|
|
15
|
+
|
|
6
16
|
## [0.77.0] - 2026-09-09
|
|
7
17
|
|
|
8
18
|
_No changes — released alongside dependent packages._
|
|
@@ -275,7 +275,9 @@ export class MastraApprovalCoordinator {
|
|
|
275
275
|
this.aborted = true;
|
|
276
276
|
const abortError = new Error('The stream was aborted.');
|
|
277
277
|
abortError.name = 'AbortError';
|
|
278
|
-
|
|
278
|
+
// Canonical cancellation code across harnesses (was harness-specific
|
|
279
|
+
// `'aborted'`): the abort teardown ends the turn with `finish('cancelled')`.
|
|
280
|
+
this.teardown('abort', abortError, 'cancelled');
|
|
279
281
|
}
|
|
280
282
|
dispose() {
|
|
281
283
|
if (this.disposed)
|
|
@@ -357,8 +359,12 @@ export class MastraApprovalCoordinator {
|
|
|
357
359
|
const resolve = this.continuationResolver;
|
|
358
360
|
this.continuationResolver = undefined;
|
|
359
361
|
resolve?.('done');
|
|
362
|
+
// A cancellation (abort) ends the turn with `finish('cancelled')`, never
|
|
363
|
+
// `error` — the terminal reason a cancelled turn surfaces per the SDK
|
|
364
|
+
// contract. Every other teardown reason stays `error`.
|
|
365
|
+
const finishReason = reason === 'abort' ? 'cancelled' : 'error';
|
|
360
366
|
this.initial.push({ type: 'error', error, code });
|
|
361
|
-
this.initial.push({ type: 'finish', finishReason
|
|
367
|
+
this.initial.push({ type: 'finish', finishReason });
|
|
362
368
|
this.initial.end();
|
|
363
369
|
this.pendingSuspendIds.clear();
|
|
364
370
|
this.runTeardownCallbacks();
|
package/dist/mastra-harness.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
4
|
+
import { McpServerStatus, type AgentHarness, type WithAgentConfig, type HarnessStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerErrorDetail, type McpServerInfo, type Message, type MessagePart, type McpAuthProviders, type ModelConnectivityInfo, type SessionContext, type StreamOptions, type TelemetryEventCallback, type ToolDeclineReason, type ToolResultInfo, type WireCommunicationEventCallback } from '@salesforce/sfdx-agent-sdk';
|
|
5
5
|
import { type AgentHooks } from '@salesforce/sfdx-agent-sdk/harness';
|
|
6
6
|
import type { MastraLanguageModelBuilderContext } from './index.js';
|
|
7
7
|
import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
|
|
@@ -520,7 +520,8 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
520
520
|
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
521
521
|
* - MUST invoke `agent.stream()` with memory context for the thread.
|
|
522
522
|
* - MUST capture the `runId` for tool approval flows.
|
|
523
|
-
* - MUST return a `
|
|
523
|
+
* - MUST return a `HarnessStreamResult` with `textStream`, `eventStream`, and the `settled`
|
|
524
|
+
* barrier (coordinator sink ended + Mastra transcript save flushed) for `cancelTurn()`.
|
|
524
525
|
* - On pre-stream rejection from `agent.stream()`, MUST re-throw without emitting telemetry —
|
|
525
526
|
* the SDK's chat-session boundary catches the rejection and emits `chat-stream-error`.
|
|
526
527
|
* - MUST install a per-run abort listener on `options.abortSignal` so that aborting
|
|
@@ -571,7 +572,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
571
572
|
* harness; this method only supplies the Mastra-specific lowering of each part.
|
|
572
573
|
*/
|
|
573
574
|
private toMastraStreamInput;
|
|
574
|
-
stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<
|
|
575
|
+
stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<HarnessStreamResult>;
|
|
575
576
|
/**
|
|
576
577
|
* @requirements
|
|
577
578
|
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
package/dist/mastra-harness.js
CHANGED
|
@@ -1648,7 +1648,8 @@ export class MastraHarness {
|
|
|
1648
1648
|
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
1649
1649
|
* - MUST invoke `agent.stream()` with memory context for the thread.
|
|
1650
1650
|
* - MUST capture the `runId` for tool approval flows.
|
|
1651
|
-
* - MUST return a `
|
|
1651
|
+
* - MUST return a `HarnessStreamResult` with `textStream`, `eventStream`, and the `settled`
|
|
1652
|
+
* barrier (coordinator sink ended + Mastra transcript save flushed) for `cancelTurn()`.
|
|
1652
1653
|
* - On pre-stream rejection from `agent.stream()`, MUST re-throw without emitting telemetry —
|
|
1653
1654
|
* the SDK's chat-session boundary catches the rejection and emits `chat-stream-error`.
|
|
1654
1655
|
* - MUST install a per-run abort listener on `options.abortSignal` so that aborting
|
|
@@ -1845,9 +1846,21 @@ export class MastraHarness {
|
|
|
1845
1846
|
}
|
|
1846
1847
|
}
|
|
1847
1848
|
coordinator.start({ runId: output.runId, fullStream: output.fullStream });
|
|
1849
|
+
// Settle barrier for `ChatSession.cancelTurn()`: resolves only once the
|
|
1850
|
+
// coordinator's per-turn sink has ended (natural finish or abort teardown)
|
|
1851
|
+
// AND Mastra's own stream completion has flushed — its `onFinish` / LibSQL
|
|
1852
|
+
// transcript save runs on natural finish and, since `@mastra/core >= 1.62`,
|
|
1853
|
+
// on abort too (W-24001006). Awaiting `output.finishReason` waits out that
|
|
1854
|
+
// save; it never rejects the barrier (an aborted turn is over either way).
|
|
1855
|
+
const coordinatorSettled = new Promise((resolve) => coordinator.onTeardown(resolve));
|
|
1856
|
+
// `Promise.resolve(...)` tolerates a test-stub output with no `finishReason`
|
|
1857
|
+
// (production `MastraModelOutput` always exposes the promise).
|
|
1858
|
+
const persistenceFlushed = Promise.resolve(output.finishReason).then(() => undefined, () => undefined);
|
|
1859
|
+
const settled = Promise.all([coordinatorSettled, persistenceFlushed]).then(() => undefined);
|
|
1848
1860
|
return {
|
|
1849
1861
|
eventStream: coordinator.initialSink().generator(),
|
|
1850
1862
|
textStream: this.readableStreamToAsyncGenerator(output.textStream),
|
|
1863
|
+
settled,
|
|
1851
1864
|
};
|
|
1852
1865
|
}
|
|
1853
1866
|
// ── Tool Execution ───────────────────────────────────────────────
|
|
@@ -105,6 +105,19 @@ export declare function buildAnthropicDirectLanguageModel(opts: {
|
|
|
105
105
|
/** Per-read idle timeout (ms) for the gateway response body; see `createGatewayFetch`. */
|
|
106
106
|
idleReadTimeoutMs?: number;
|
|
107
107
|
}): LanguageModelV3;
|
|
108
|
+
/**
|
|
109
|
+
* Stamps `cache_control: {type:'ephemeral'}` on up to three of Anthropic's four
|
|
110
|
+
* allowed breakpoints: the last tool (caches the constant tools block), the
|
|
111
|
+
* system prompt (sent as a `text` block carrying the breakpoint), and a
|
|
112
|
+
* rolling breakpoint on the last cache-eligible block of the last message —
|
|
113
|
+
* the growing history is the majority of per-step input on a multi-step turn,
|
|
114
|
+
* so this breakpoint is load-bearing, not incidental: dropping it would leave
|
|
115
|
+
* only the turn-constant tools/system cached and forfeit most of the win.
|
|
116
|
+
* Mutates only `commonParams`'s own arrays — which
|
|
117
|
+
* `translate*ToAnthropic` builds fresh per request — never the caller's
|
|
118
|
+
* original `LanguageModelV3` prompt/tool objects.
|
|
119
|
+
*/
|
|
120
|
+
export declare function applyCacheBreakpoints(commonParams: Omit<Anthropic.Messages.MessageCreateParamsStreaming, 'stream'>): void;
|
|
108
121
|
/**
|
|
109
122
|
* Translates a V3 `LanguageModelV3Prompt` into Anthropic's Messages API shape
|
|
110
123
|
* — top-level `system` string + `MessageParam[]` of role-tagged content blocks.
|
|
@@ -177,8 +177,76 @@ async function buildAnthropicRequest(ctx, options) {
|
|
|
177
177
|
}
|
|
178
178
|
if (options.stopSequences !== undefined)
|
|
179
179
|
commonParams.stop_sequences = [...options.stopSequences];
|
|
180
|
+
// Anthropic caching is opt-in per request (unlike OpenAI's automatic
|
|
181
|
+
// prefix cache) — nothing caches without an explicit `cache_control`
|
|
182
|
+
// breakpoint. `Model.supportsPromptCache` is the gate (`true` for the
|
|
183
|
+
// Claude family, `false` for GPT); see `applyCacheBreakpoints`.
|
|
184
|
+
if (info.model.supportsPromptCache && !isPromptCacheKillSwitchEngaged())
|
|
185
|
+
applyCacheBreakpoints(commonParams);
|
|
180
186
|
return { client, commonParams, toolSchemas };
|
|
181
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Internal, undocumented escape hatch — NOT a consumer-facing `AgentConfig`
|
|
190
|
+
* field or a documented environment variable. Exists solely so an operator can
|
|
191
|
+
* disable cache-control stamping process-wide if a provider-side caching bug
|
|
192
|
+
* surfaces in production, without a code rollback. Read fresh on every request
|
|
193
|
+
* (not cached) so it can be flipped at runtime.
|
|
194
|
+
*/
|
|
195
|
+
function isPromptCacheKillSwitchEngaged() {
|
|
196
|
+
return process.env['SF_MASTRA_DISABLE_PROMPT_CACHE'] === 'true';
|
|
197
|
+
}
|
|
198
|
+
/** Content-block types that accept `cache_control` on the Anthropic Messages
|
|
199
|
+
* API. Deliberately excludes `thinking` / `redacted_thinking`, which reject it. */
|
|
200
|
+
const CACHE_ELIGIBLE_BLOCK_TYPES = new Set([
|
|
201
|
+
'text',
|
|
202
|
+
'tool_use',
|
|
203
|
+
'tool_result',
|
|
204
|
+
'image',
|
|
205
|
+
'document',
|
|
206
|
+
]);
|
|
207
|
+
/** Shared marker object stamped onto up to 3 locations per request, reused
|
|
208
|
+
* across every request and every agent for the process lifetime — never
|
|
209
|
+
* mutate this in place; construct a new object if a call site ever needs a
|
|
210
|
+
* different breakpoint shape (e.g. a `ttl`). */
|
|
211
|
+
const CACHE_CONTROL_EPHEMERAL = Object.freeze({ type: 'ephemeral' });
|
|
212
|
+
/**
|
|
213
|
+
* Stamps `cache_control: {type:'ephemeral'}` on up to three of Anthropic's four
|
|
214
|
+
* allowed breakpoints: the last tool (caches the constant tools block), the
|
|
215
|
+
* system prompt (sent as a `text` block carrying the breakpoint), and a
|
|
216
|
+
* rolling breakpoint on the last cache-eligible block of the last message —
|
|
217
|
+
* the growing history is the majority of per-step input on a multi-step turn,
|
|
218
|
+
* so this breakpoint is load-bearing, not incidental: dropping it would leave
|
|
219
|
+
* only the turn-constant tools/system cached and forfeit most of the win.
|
|
220
|
+
* Mutates only `commonParams`'s own arrays — which
|
|
221
|
+
* `translate*ToAnthropic` builds fresh per request — never the caller's
|
|
222
|
+
* original `LanguageModelV3` prompt/tool objects.
|
|
223
|
+
*/
|
|
224
|
+
export function applyCacheBreakpoints(commonParams) {
|
|
225
|
+
const tools = commonParams.tools;
|
|
226
|
+
if (tools !== undefined && tools.length > 0) {
|
|
227
|
+
const lastTool = tools[tools.length - 1];
|
|
228
|
+
if (lastTool !== undefined)
|
|
229
|
+
lastTool.cache_control = CACHE_CONTROL_EPHEMERAL;
|
|
230
|
+
}
|
|
231
|
+
if (typeof commonParams.system === 'string') {
|
|
232
|
+
commonParams.system = [{ type: 'text', text: commonParams.system, cache_control: CACHE_CONTROL_EPHEMERAL }];
|
|
233
|
+
}
|
|
234
|
+
else if (Array.isArray(commonParams.system) && commonParams.system.length > 0) {
|
|
235
|
+
commonParams.system[commonParams.system.length - 1].cache_control = CACHE_CONTROL_EPHEMERAL;
|
|
236
|
+
}
|
|
237
|
+
const lastMessage = commonParams.messages[commonParams.messages.length - 1];
|
|
238
|
+
const content = lastMessage?.content;
|
|
239
|
+
if (Array.isArray(content)) {
|
|
240
|
+
for (let i = content.length - 1; i >= 0; i--) {
|
|
241
|
+
const block = content[i];
|
|
242
|
+
if (block !== undefined && CACHE_ELIGIBLE_BLOCK_TYPES.has(block.type)) {
|
|
243
|
+
block.cache_control =
|
|
244
|
+
CACHE_CONTROL_EPHEMERAL;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
182
250
|
async function doGenerate(ctx, options) {
|
|
183
251
|
const { client, commonParams, toolSchemas } = await buildAnthropicRequest(ctx, options);
|
|
184
252
|
// Forward the turn's AbortSignal into the SDK's RequestOptions so firing the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-harness-mastra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.0",
|
|
4
4
|
"description": "Mastra-backed AgentHarness implementation for @salesforce/sfdx-agent-sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"openai": "^7.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@salesforce/sfdx-agent-sdk": "0.
|
|
45
|
+
"@salesforce/sfdx-agent-sdk": "0.80.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@eslint/js": "^10.0.1",
|
|
49
49
|
"@salesforce/harness-conformance": "0.0.1",
|
|
50
|
-
"@salesforce/sfdx-agent-sdk": "0.
|
|
50
|
+
"@salesforce/sfdx-agent-sdk": "0.80.0",
|
|
51
51
|
"@types/node": "^22.20.1",
|
|
52
52
|
"@vitest/coverage-istanbul": "^4.1.11",
|
|
53
53
|
"@vitest/eslint-plugin": "^1.6.27",
|