@salesforce/sfdx-agent-harness-mastra 0.45.0 → 0.46.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,11 @@
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.46.0] - 2026-08-10
7
+
8
+ ### Fixes
9
+ - **harness-mastra**: normalize empty tool-call args to "{}" across all passthroughs @W-23789221@ ([#745](https://github.com/forcedotcom/agentic-dx/pull/745))
10
+
6
11
  ## [0.45.0] - 2026-08-10
7
12
 
8
13
  ### Fixes
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk';
6
6
  import Anthropic from '@anthropic-ai/sdk';
7
- import { TextSpanLifecycle, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapAnthropicFinishReason, mapAnthropicUsage, toolResultOutputToString, } from './v3-stream-mapping.js';
7
+ import { TextSpanLifecycle, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapAnthropicFinishReason, mapAnthropicUsage, normalizeToolArgsString, toolResultOutputToString, } from './v3-stream-mapping.js';
8
8
  /**
9
9
  * Builds the in-process `LanguageModelV3` impl for the Salesforce LLM Gateway's
10
10
  * `/invoke-with-response-stream` (Bedrock-Anthropic pass-through) endpoint.
@@ -266,17 +266,9 @@ async function doStream(ctx, options) {
266
266
  type: 'tool-call',
267
267
  toolCallId: tu.id,
268
268
  toolName: tu.name,
269
- // A no-arg tool (e.g. startBuilding) streams no
270
- // input_json_delta events, so argsBuffer stays ''.
271
- // '' is not a schema-conforming stringified JSON
272
- // object — a strict consumer's JSON.parse throws and
273
- // the model retries. Coerce the empty buffer to '{}'
274
- // (the only valid no-arg value); non-empty buffers,
275
- // even malformed ones, pass through verbatim so we
276
- // never mask a genuinely bad payload. W-23789221;
277
- // matches the non-streaming path + parseToolInput
278
- // (same `.trim()` empty check as the latter).
279
- input: tu.argsBuffer.trim() === '' ? '{}' : tu.argsBuffer,
269
+ // No-arg tool → empty argsBuffer; normalize to '{}'.
270
+ // See normalizeToolArgsString (W-23789221).
271
+ input: normalizeToolArgsString(tu.argsBuffer),
280
272
  });
281
273
  toolUseByIndex.delete(event.index);
282
274
  }
@@ -3,7 +3,7 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  import OpenAI from 'openai';
6
- import { TextSpanLifecycle, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapOpenAIUsage, toolResultOutputToString, } from './v3-stream-mapping.js';
6
+ import { TextSpanLifecycle, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapOpenAIUsage, normalizeToolArgsString, toolResultOutputToString, } from './v3-stream-mapping.js';
7
7
  /**
8
8
  * Builds the in-process `LanguageModelV3` impl that drives the `openai` SDK's
9
9
  * `chat.completions.create({ stream: true })` against an OpenAI-compatible
@@ -94,7 +94,8 @@ async function doGenerate(ctx, options) {
94
94
  type: 'tool-call',
95
95
  toolCallId: tc.id,
96
96
  toolName: tc.function.name,
97
- input: tc.function.arguments ?? '',
97
+ // No-arg tool → empty arguments; normalize to '{}' (W-23789221).
98
+ input: normalizeToolArgsString(tc.function.arguments),
98
99
  });
99
100
  }
100
101
  }
@@ -212,7 +213,8 @@ async function doStream(ctx, options) {
212
213
  type: 'tool-call',
213
214
  toolCallId: acc.id,
214
215
  toolName: acc.name,
215
- input: acc.argsBuffer,
216
+ // No-arg tool → empty argsBuffer; normalize to '{}' (W-23789221).
217
+ input: normalizeToolArgsString(acc.argsBuffer),
216
218
  });
217
219
  }
218
220
  toolCallsByIndex.clear();
@@ -3,7 +3,7 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  import OpenAI from 'openai';
6
- import { TextSpanLifecycle, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapOpenAIFinishReason, mapOpenAIUsage, toolResultOutputToString, } from './v3-stream-mapping.js';
6
+ import { TextSpanLifecycle, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapOpenAIFinishReason, mapOpenAIUsage, normalizeToolArgsString, toolResultOutputToString, } from './v3-stream-mapping.js';
7
7
  /**
8
8
  * Builds the in-process `LanguageModelV3` impl that drives the `openai` SDK's
9
9
  * `responses.create({ stream: true })` against the Salesforce LLM Gateway's
@@ -89,7 +89,8 @@ async function doGenerate(ctx, options) {
89
89
  type: 'tool-call',
90
90
  toolCallId: item.call_id,
91
91
  toolName: item.name,
92
- input: item.arguments ?? '',
92
+ // No-arg tool → empty arguments; normalize to '{}' (W-23789221).
93
+ input: normalizeToolArgsString(item.arguments),
93
94
  });
94
95
  }
95
96
  // Other output-item variants (reasoning, web/file/code search, MCP) are
@@ -149,7 +150,8 @@ async function doStream(ctx, options) {
149
150
  type: 'tool-call',
150
151
  toolCallId: item.call_id,
151
152
  toolName: item.name,
152
- input: finalArgs,
153
+ // No-arg tool → empty finalArgs; normalize to '{}' (W-23789221).
154
+ input: normalizeToolArgsString(finalArgs),
153
155
  });
154
156
  if (item.id)
155
157
  toolCallById.delete(item.id);
@@ -265,6 +265,34 @@ type WireCommunicationLikeEvent = {
265
265
  * worst place for a copy-paste drift to land.
266
266
  */
267
267
  export declare function toolResultOutputToString(output: LanguageModelV3ToolResultOutput): string;
268
+ /**
269
+ * Normalize a streamed/returned tool-call arguments STRING to a
270
+ * schema-conforming stringified JSON object.
271
+ *
272
+ * A no-argument tool (input schema `{ type:'object', properties:{} }`) accrues
273
+ * no argument fragments on the wire — Anthropic streams no `input_json_delta`,
274
+ * the OpenAI Responses/Chat-Completions APIs send no arg deltas — so the
275
+ * accumulated buffer is the empty string `''`. `''` is NOT a schema-conforming
276
+ * stringified JSON object: a strict consumer (Mastra `secureJsonParse`, any
277
+ * `JSON.parse`) throws "the provided JSON could not be parsed" and the model
278
+ * retries, producing the empty-args retry loop (W-23789221; same class as the
279
+ * closed gateway-processor defect W-22696051). `'{}'` is the only valid no-arg
280
+ * value.
281
+ *
282
+ * Empty (including whitespace-only) → `'{}'`. Every non-empty buffer — even a
283
+ * malformed partial like `'{"q":'` — is returned VERBATIM: we never re-parse or
284
+ * re-stringify real content, so a genuinely bad payload still reaches the
285
+ * consumer and is still rejected. Applied at every provider's tool-call
286
+ * emission seam so the rule lives in one auditable place.
287
+ *
288
+ * Sibling to `sfdx-agent-chat-generations`'s `normalizeToolArguments` (the
289
+ * gpt5-processor path). This one additionally coerces a whitespace-only buffer
290
+ * — the pass-through streaming paths accumulate raw wire fragments that could in
291
+ * principle be whitespace, whereas the chat-generations processor normalizes
292
+ * earlier. The packages share no dependency, so the rule is duplicated rather
293
+ * than imported; keep the two in sync if the empty-definition changes.
294
+ */
295
+ export declare function normalizeToolArgsString(args: string | null | undefined): string;
268
296
  /**
269
297
  * Predicate the missing-usage faithfulness invariant (W-22692131) enforces at
270
298
  * every terminal `finish` site: when every input/output token field on the
@@ -595,6 +595,36 @@ export function toolResultOutputToString(output) {
595
595
  return output.reason ?? 'tool execution denied';
596
596
  }
597
597
  }
598
+ /**
599
+ * Normalize a streamed/returned tool-call arguments STRING to a
600
+ * schema-conforming stringified JSON object.
601
+ *
602
+ * A no-argument tool (input schema `{ type:'object', properties:{} }`) accrues
603
+ * no argument fragments on the wire — Anthropic streams no `input_json_delta`,
604
+ * the OpenAI Responses/Chat-Completions APIs send no arg deltas — so the
605
+ * accumulated buffer is the empty string `''`. `''` is NOT a schema-conforming
606
+ * stringified JSON object: a strict consumer (Mastra `secureJsonParse`, any
607
+ * `JSON.parse`) throws "the provided JSON could not be parsed" and the model
608
+ * retries, producing the empty-args retry loop (W-23789221; same class as the
609
+ * closed gateway-processor defect W-22696051). `'{}'` is the only valid no-arg
610
+ * value.
611
+ *
612
+ * Empty (including whitespace-only) → `'{}'`. Every non-empty buffer — even a
613
+ * malformed partial like `'{"q":'` — is returned VERBATIM: we never re-parse or
614
+ * re-stringify real content, so a genuinely bad payload still reaches the
615
+ * consumer and is still rejected. Applied at every provider's tool-call
616
+ * emission seam so the rule lives in one auditable place.
617
+ *
618
+ * Sibling to `sfdx-agent-chat-generations`'s `normalizeToolArguments` (the
619
+ * gpt5-processor path). This one additionally coerces a whitespace-only buffer
620
+ * — the pass-through streaming paths accumulate raw wire fragments that could in
621
+ * principle be whitespace, whereas the chat-generations processor normalizes
622
+ * earlier. The packages share no dependency, so the rule is duplicated rather
623
+ * than imported; keep the two in sync if the empty-definition changes.
624
+ */
625
+ export function normalizeToolArgsString(args) {
626
+ return args && args.trim() !== '' ? args : '{}';
627
+ }
598
628
  /**
599
629
  * Predicate the missing-usage faithfulness invariant (W-22692131) enforces at
600
630
  * every terminal `finish` site: when every input/output token field on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-harness-mastra",
3
- "version": "0.45.0",
3
+ "version": "0.46.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.46.0"
45
+ "@salesforce/sfdx-agent-sdk": "0.47.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.46.0",
50
+ "@salesforce/sfdx-agent-sdk": "0.47.0",
51
51
  "@types/node": "^22.20.1",
52
52
  "@vitest/coverage-istanbul": "^4.1.10",
53
53
  "@vitest/eslint-plugin": "^1.6.26",