@salesforce/sfdx-agent-harness-mastra 0.100.0 → 0.101.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.101.0] - 2026-10-13
7
+
8
+ ### Features
9
+ - **harness-mastra**: migrate pass-throughs to LanguageModelV4 and remove chat-generations @W-24224624@ ([#876](https://github.com/forcedotcom/agentic-dx/pull/876))
10
+
6
11
  ## [0.100.0] - 2026-10-09
7
12
 
8
13
  ### Chores
package/README.md CHANGED
@@ -59,8 +59,8 @@ Linux and macOS release the handle eagerly; the retry options are no-ops on thos
59
59
  | ----------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60
60
  | `MastraHarnessFactory` | class | `HarnessFactory` that produces a Mastra-backed `AgentHarness` bound to a storage root. |
61
61
  | `MastraHarnessFactoryConfig` | interface | Configuration for `MastraHarnessFactory` (`toolApprovalTimeoutMs?`, `languageModelBuilders?`). |
62
- | `MastraLanguageModelBuilderContext` | type | Context bag the harness threads into each `LanguageModelV3` builder (`getInfo`, `logBus`, `emitWireCommunication`, `innerFetch`). |
63
- | `MastraLanguageModelBuilder` | type | Factory function shape for a consumer-injected builder: `(ctx: MastraLanguageModelBuilderContext) => LanguageModelV3`. |
62
+ | `MastraLanguageModelBuilderContext` | type | Context bag the harness threads into each `LanguageModelV4` builder (`getInfo`, `logBus`, `emitWireCommunication`, `innerFetch`). |
63
+ | `MastraLanguageModelBuilder` | type | Factory function shape for a consumer-injected builder: `(ctx: MastraLanguageModelBuilderContext) => LanguageModelV4`. |
64
64
  | `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. |
65
65
  | `MASTRA_BUILT_IN_TOOL_NAMES` | const | The four built-in tool leaf names `MASTRA_BUILT_IN_TOOL_POLICIES` covers. |
66
66
 
@@ -81,20 +81,14 @@ no `globalThis` mutation. When no proxy env is set, `fetch` runs unwrapped (zero
81
81
 
82
82
  #### Injecting custom wire shapes
83
83
 
84
- A consumer can register a `LanguageModelV3` builder for a custom `providerHint` via
84
+ A consumer can register a `LanguageModelV4` builder for a custom `providerHint` via
85
85
  `MastraHarnessFactoryConfig.languageModelBuilders` without editing the harness's internal switch. Each injected key
86
- becomes a supported provider hint and a dispatch arm. Used to inject wire shapes the harness does not ship natively
87
- (e.g., a temporary GovCloud `chat/generations` fallback).
86
+ becomes a supported provider hint and a dispatch arm. Used to inject wire shapes the harness does not ship natively.
88
87
 
89
88
  ```ts
90
- import {
91
- CHAT_GENERATIONS_PROVIDER_HINT,
92
- buildChatGenerationsLanguageModel,
93
- } from '@salesforce/sfdx-agent-chat-generations';
94
-
95
89
  const factory = new MastraHarnessFactory({
96
90
  languageModelBuilders: {
97
- [CHAT_GENERATIONS_PROVIDER_HINT]: buildChatGenerationsLanguageModel,
91
+ 'my-custom-wire-shape': (ctx) => buildMyLanguageModel(ctx),
98
92
  },
99
93
  });
100
94
  ```
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { LogBus } from '@salesforce/agentic-common';
2
2
  import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
3
- import type { LanguageModelV3 } from '@ai-sdk/provider';
3
+ import type { LanguageModelV4 } from '@ai-sdk/provider';
4
4
  export { MastraHarnessFactory, type MastraHarnessFactoryConfig } from './mastra-harness-factory.js';
5
5
  export type { MastraAgentHarness } from './mastra-harness.js';
6
6
  export { MASTRA_BUILT_IN_TOOL_NAMES, MASTRA_BUILT_IN_TOOL_POLICIES } from './mastra-built-in-policies.js';
7
7
  export { type MastraAgentConfig, type MastraSkillSearchConfig, type MastraToolSearchConfig, type SkillSearchHandle, type ToolSearchHandle, } from './tool-search.js';
8
8
  /**
9
- * Context the harness threads into each `LanguageModelV3` builder — both the
9
+ * Context the harness threads into each `LanguageModelV4` builder — both the
10
10
  * built-in builders and any consumer-injected builders registered via
11
11
  * `MastraHarnessFactoryConfig.languageModelBuilders`. The shape mirrors what
12
12
  * `MastraHarness.buildLanguageModel` currently hands its internal builders, so an
@@ -33,10 +33,10 @@ export type MastraLanguageModelBuilderContext = {
33
33
  innerFetch: typeof fetch;
34
34
  };
35
35
  /**
36
- * Factory function shape for a consumer-injected `LanguageModelV3` builder.
36
+ * Factory function shape for a consumer-injected `LanguageModelV4` builder.
37
37
  * Registered via `MastraHarnessFactoryConfig.languageModelBuilders` keyed by a custom
38
38
  * `providerHint`, used to inject wire shapes the harness does not ship natively
39
- * (e.g. a temporary GovCloud `chat/generations` fallback). The builder receives the
39
+ * (e.g. a bespoke gateway wire format). The builder receives the
40
40
  * same context bag the harness's built-in pass-through builders get, so an injected
41
41
  * builder can emit `WireCommunicationEvent`s and honor `HTTPS_PROXY` routing without
42
42
  * duplicating the harness's own fetch wrapper / wire-monitoring plumbing. The
@@ -55,4 +55,4 @@ export type MastraLanguageModelBuilderContext = {
55
55
  * });
56
56
  * ```
57
57
  */
58
- export type MastraLanguageModelBuilder = (ctx: MastraLanguageModelBuilderContext) => LanguageModelV3;
58
+ export type MastraLanguageModelBuilder = (ctx: MastraLanguageModelBuilderContext) => LanguageModelV4;
@@ -12,10 +12,10 @@ export interface MastraHarnessFactoryConfig {
12
12
  */
13
13
  toolApprovalTimeoutMs?: number;
14
14
  /**
15
- * Consumer-registered `LanguageModelV3` builders keyed by `providerHint`. Each key
15
+ * Consumer-registered `LanguageModelV4` builders keyed by `providerHint`. Each key
16
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
17
+ * to inject wire shapes the harness does not ship natively (e.g. a bespoke
18
+ * gateway wire format). Keys MUST NOT collide with the
19
19
  * harness's built-in hints (`anthropic`, `bedrock-anthropic`, `openai`,
20
20
  * `openai-responses`, `openai-compatible`) — constructing with a colliding key
21
21
  * throws at factory construction time rather than silently shadowing the built-in.
@@ -8,7 +8,7 @@ import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
8
8
  import { type HttpErrorCapture } from './mcp-config-mapper.js';
9
9
  import { type MastraAgentConfig, type SkillSearchHandle, type ToolSearchHandle } from './tool-search.js';
10
10
  import { type MultimodalFile } from '@salesforce/sfdx-agent-sdk';
11
- import type { LanguageModelV3 } from '@ai-sdk/provider';
11
+ import type { LanguageModelV4 } from '@ai-sdk/provider';
12
12
  import { Clock, type LogRecord, type BackoffRetryerDeps, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
13
13
  import { type ToolsInput } from './utils.js';
14
14
  import { Workspace } from '@mastra/core/workspace';
@@ -247,13 +247,13 @@ export interface MastraHarnessOptions {
247
247
  */
248
248
  innerFetch?: typeof fetch;
249
249
  /**
250
- * Consumer-registered `LanguageModelV3` builders keyed by `providerHint`.
250
+ * Consumer-registered `LanguageModelV4` builders keyed by `providerHint`.
251
251
  * Forwarded from `MastraHarnessFactoryConfig.languageModelBuilders`. The
252
252
  * harness consults this map in `buildLanguageModel`'s `default` arm before
253
253
  * throwing `MODEL_NOT_SUPPORTED_BY_HARNESS`, so an injected builder can
254
254
  * handle a custom hint without editing the harness's internal switch.
255
255
  */
256
- languageModelBuilders?: Readonly<Record<string, (ctx: MastraLanguageModelBuilderContext) => LanguageModelV3>>;
256
+ languageModelBuilders?: Readonly<Record<string, (ctx: MastraLanguageModelBuilderContext) => LanguageModelV4>>;
257
257
  /**
258
258
  * Clock / sleep / RNG for the pre-first-token LLM retry loop (W-24236393), whose policy is
259
259
  * `PRE_FIRST_TOKEN_RETRY_OPTIONS`. Tests inject a no-sleep `sleep`.
@@ -514,7 +514,7 @@ export declare class MastraHarness implements MastraAgentHarness {
514
514
  * non-empty paths.
515
515
  */
516
516
  /**
517
- * Dispatches on the bag's `providerHint` and returns a `LanguageModelV3` that
517
+ * Dispatches on the bag's `providerHint` and returns a `LanguageModelV4` that
518
518
  * speaks the provider-native pass-through endpoint. The builder closes over
519
519
  * `getInfo` so within-shape `modelId` swaps, JWT rotations, and
520
520
  * premium-rate-limit gate flips land on the next outbound call without
@@ -588,8 +588,8 @@ export declare class MastraHarness implements MastraAgentHarness {
588
588
  * `agent.stream()` accepts.
589
589
  *
590
590
  * `text` parts collapse into text content blocks; `image` / `file` parts become v5 `file`
591
- * content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra forwards to the
592
- * gateway bridge, which collects them into `ChatMessageIn.files[]`. Pre-stream validation and the
591
+ * content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra normalizes to V4
592
+ * `file` parts for the provider pass-through translators. Pre-stream validation and the
593
593
  * input-part guard live in the SDK's {@link lowerStreamInput}, so file caps and the
594
594
  * `MULTIMODAL_NOT_SUPPORTED` / `INVALID_MESSAGE_CONTENT` semantics are identical to the Claude
595
595
  * harness; this method only supplies the Mastra-specific lowering of each part.
@@ -27,7 +27,7 @@ import { validateMultimodalFiles } from '@salesforce/sfdx-agent-sdk';
27
27
  import { buildAnthropicDirectLanguageModel, buildAnthropicLanguageModel } from './pass-through-anthropic.js';
28
28
  import { buildOpenAIChatCompletionsLanguageModel } from './pass-through-openai-chat-completions.js';
29
29
  import { buildOpenAILanguageModel } from './pass-through-openai.js';
30
- import { readToolArgsRetryScope, TOOL_ARGS_RETRY_SCOPE_AGENT_ID, TOOL_ARGS_RETRY_SCOPE_PROVIDER, TOOL_ARGS_RETRY_SCOPE_THREAD_ID, } from './v3-stream-mapping.js';
30
+ import { readToolArgsRetryScope, TOOL_ARGS_RETRY_SCOPE_AGENT_ID, TOOL_ARGS_RETRY_SCOPE_PROVIDER, TOOL_ARGS_RETRY_SCOPE_THREAD_ID, } from './stream-mapping.js';
31
31
  import { ToolResultRedactionProcessor } from './mastra-tool-result-redaction-processor.js';
32
32
  import { backfillCreatedAt, buildSummaryPrompt, Clock, getErrorMessage, RealClock, splitFrontmatterAndBody, UUIDGenerator, } from '@salesforce/agentic-common';
33
33
  import { withPreFirstTokenRetry } from './retry-pre-first-token.js';
@@ -1550,7 +1550,7 @@ export class MastraHarness {
1550
1550
  * non-empty paths.
1551
1551
  */
1552
1552
  /**
1553
- * Dispatches on the bag's `providerHint` and returns a `LanguageModelV3` that
1553
+ * Dispatches on the bag's `providerHint` and returns a `LanguageModelV4` that
1554
1554
  * speaks the provider-native pass-through endpoint. The builder closes over
1555
1555
  * `getInfo` so within-shape `modelId` swaps, JWT rotations, and
1556
1556
  * premium-rate-limit gate flips land on the next outbound call without
@@ -1671,9 +1671,9 @@ export class MastraHarness {
1671
1671
  }));
1672
1672
  default: {
1673
1673
  // Consult the injected builders map before throwing. This is the
1674
- // extension point for consumer-registered wire shapes (e.g. a temporary
1675
- // GovCloud chat/generations fallback, W-23560954). The builder receives
1676
- // the same context bag the built-in builders get.
1674
+ // extension point for consumer-registered wire shapes (e.g. a bespoke
1675
+ // gateway wire format). The builder receives the same context bag the
1676
+ // built-in builders get.
1677
1677
  const custom = this.languageModelBuilders?.[initial.providerHint];
1678
1678
  if (custom) {
1679
1679
  return this.wrapLanguageModel(custom({ getInfo, logBus, emitWireCommunication, innerFetch }));
@@ -1773,8 +1773,8 @@ export class MastraHarness {
1773
1773
  * `agent.stream()` accepts.
1774
1774
  *
1775
1775
  * `text` parts collapse into text content blocks; `image` / `file` parts become v5 `file`
1776
- * content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra forwards to the
1777
- * gateway bridge, which collects them into `ChatMessageIn.files[]`. Pre-stream validation and the
1776
+ * content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra normalizes to V4
1777
+ * `file` parts for the provider pass-through translators. Pre-stream validation and the
1778
1778
  * input-part guard live in the SDK's {@link lowerStreamInput}, so file caps and the
1779
1779
  * `MULTIMODAL_NOT_SUPPORTED` / `INVALID_MESSAGE_CONTENT` semantics are identical to the Claude
1780
1780
  * harness; this method only supplies the Mastra-specific lowering of each part.
@@ -1,10 +1,10 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
- import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3Prompt } from '@ai-sdk/provider';
2
+ import type { LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4Prompt } from '@ai-sdk/provider';
3
3
  import type { LogBus } from '@salesforce/agentic-common';
4
4
  import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
5
- import { type GatewayFetchWireHooks } from './v3-stream-mapping.js';
5
+ import { type GatewayFetchWireHooks } from './stream-mapping.js';
6
6
  /**
7
- * Builds the in-process `LanguageModelV3` impl for the Salesforce LLM Gateway's
7
+ * Builds the in-process `LanguageModelV4` impl for the Salesforce LLM Gateway's
8
8
  * `/invoke-with-response-stream` (Bedrock-Anthropic pass-through) endpoint.
9
9
  * Drives `@anthropic-ai/bedrock-sdk` with `skipAuth: true` so the connectivity
10
10
  * resolver's headers (Salesforce JWT, tenant key, etc.) are the single source
@@ -66,12 +66,12 @@ export declare function buildAnthropicLanguageModel(opts: {
66
66
  * `LanguageModel`. Defaults to a single per-instance map for callers that
67
67
  * don't thread it (tests / consumer-injected builders).
68
68
  */
69
- getToolArgsRetryCounts?: (options: LanguageModelV3CallOptions) => Map<string, number>;
69
+ getToolArgsRetryCounts?: (options: LanguageModelV4CallOptions) => Map<string, number>;
70
70
  /** Per-read idle timeout (ms) for the gateway response body; see `createGatewayFetch`. */
71
71
  idleReadTimeoutMs?: number;
72
- }): LanguageModelV3;
72
+ }): LanguageModelV4;
73
73
  /**
74
- * Builds the in-process `LanguageModelV3` impl for direct-Anthropic API access
74
+ * Builds the in-process `LanguageModelV4` impl for direct-Anthropic API access
75
75
  * (`api.anthropic.com/v1/messages` or any Anthropic-shaped endpoint). Drives
76
76
  * `@anthropic-ai/sdk` with the api-key peeled from the resolver's headers.
77
77
  *
@@ -101,10 +101,10 @@ export declare function buildAnthropicDirectLanguageModel(opts: {
101
101
  /** See {@link buildAnthropicLanguageModel} `getConsumerToolSchemas`. */
102
102
  getConsumerToolSchemas?: () => ReadonlyMap<string, Record<string, unknown>>;
103
103
  /** See {@link buildAnthropicLanguageModel} `getToolArgsRetryCounts`. */
104
- getToolArgsRetryCounts?: (options: LanguageModelV3CallOptions) => Map<string, number>;
104
+ getToolArgsRetryCounts?: (options: LanguageModelV4CallOptions) => Map<string, number>;
105
105
  /** Per-read idle timeout (ms) for the gateway response body; see `createGatewayFetch`. */
106
106
  idleReadTimeoutMs?: number;
107
- }): LanguageModelV3;
107
+ }): LanguageModelV4;
108
108
  /**
109
109
  * Stamps `cache_control: {type:'ephemeral'}` on up to three of Anthropic's four
110
110
  * allowed breakpoints: the last tool (caches the constant tools block), the
@@ -115,25 +115,25 @@ export declare function buildAnthropicDirectLanguageModel(opts: {
115
115
  * only the turn-constant tools/system cached and forfeit most of the win.
116
116
  * Mutates only `commonParams`'s own arrays — which
117
117
  * `translate*ToAnthropic` builds fresh per request — never the caller's
118
- * original `LanguageModelV3` prompt/tool objects.
118
+ * original `LanguageModelV4` prompt/tool objects.
119
119
  */
120
120
  export declare function applyCacheBreakpoints(commonParams: Omit<Anthropic.Messages.MessageCreateParamsStreaming, 'stream'>): void;
121
121
  /**
122
- * Translates a V3 `LanguageModelV3Prompt` into Anthropic's Messages API shape
122
+ * Translates a V4 `LanguageModelV4Prompt` into Anthropic's Messages API shape
123
123
  * — top-level `system` string + `MessageParam[]` of role-tagged content blocks.
124
124
  *
125
- * V3 keeps system / user / assistant / tool messages flat. Anthropic combines
125
+ * V4 keeps system / user / assistant / tool messages flat. Anthropic combines
126
126
  * tool results onto a synthetic user-role turn carrying `tool_result` blocks,
127
- * so we collapse consecutive tool-only V3 messages into the surrounding user
127
+ * so we collapse consecutive tool-only V4 messages into the surrounding user
128
128
  * turn shape.
129
129
  */
130
- export declare function translatePromptToAnthropic(prompt: LanguageModelV3Prompt): {
130
+ export declare function translatePromptToAnthropic(prompt: LanguageModelV4Prompt): {
131
131
  system?: string;
132
132
  messages: Anthropic.Messages.MessageParam[];
133
133
  };
134
134
  /**
135
- * Translates V3 function-tool definitions into Anthropic's `Tool[]` shape.
135
+ * Translates V4 function-tool definitions into Anthropic's `Tool[]` shape.
136
136
  * Skips non-function variants (provider-defined / dynamic) — the pass-through
137
137
  * path supports only the function-tool subset that Mastra's tool mapper produces.
138
138
  */
139
- export declare function translateToolsToAnthropic(options: LanguageModelV3CallOptions): Anthropic.Messages.Tool[];
139
+ export declare function translateToolsToAnthropic(options: LanguageModelV4CallOptions): Anthropic.Messages.Tool[];
@@ -5,9 +5,9 @@
5
5
  import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk';
6
6
  import Anthropic from '@anthropic-ai/sdk';
7
7
  import { EmptyStreamError, NetworkStreamError, isStreamTransportError, recognizeBedrockValidationError, } from './stream-errors.js';
8
- import { SpanLifecycle, ToolInputSpanLifecycle, buildValidatedToolCallChunk, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapAnthropicFinishReason, mapAnthropicUsage, toolResultOutputToString, } from './v3-stream-mapping.js';
8
+ import { SpanLifecycle, ToolInputSpanLifecycle, buildValidatedToolCallChunk, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapAnthropicFinishReason, mapAnthropicUsage, toolResultOutputToString, v4FileDataToBase64, } from './stream-mapping.js';
9
9
  /**
10
- * Builds the in-process `LanguageModelV3` impl for the Salesforce LLM Gateway's
10
+ * Builds the in-process `LanguageModelV4` impl for the Salesforce LLM Gateway's
11
11
  * `/invoke-with-response-stream` (Bedrock-Anthropic pass-through) endpoint.
12
12
  * Drives `@anthropic-ai/bedrock-sdk` with `skipAuth: true` so the connectivity
13
13
  * resolver's headers (Salesforce JWT, tenant key, etc.) are the single source
@@ -44,7 +44,7 @@ export function buildAnthropicLanguageModel(opts) {
44
44
  }),
45
45
  };
46
46
  return {
47
- specificationVersion: 'v3',
47
+ specificationVersion: 'v4',
48
48
  provider: 'salesforce-llm-gateway-pass-through-anthropic',
49
49
  // Mastra captures `modelId` once at construction. The actual wire id
50
50
  // sent on every call is read from the live bag via `commonParams.model`
@@ -57,7 +57,7 @@ export function buildAnthropicLanguageModel(opts) {
57
57
  };
58
58
  }
59
59
  /**
60
- * Builds the in-process `LanguageModelV3` impl for direct-Anthropic API access
60
+ * Builds the in-process `LanguageModelV4` impl for direct-Anthropic API access
61
61
  * (`api.anthropic.com/v1/messages` or any Anthropic-shaped endpoint). Drives
62
62
  * `@anthropic-ai/sdk` with the api-key peeled from the resolver's headers.
63
63
  *
@@ -104,7 +104,7 @@ export function buildAnthropicDirectLanguageModel(opts) {
104
104
  }),
105
105
  };
106
106
  return {
107
- specificationVersion: 'v3',
107
+ specificationVersion: 'v4',
108
108
  provider: 'anthropic-direct-pass-through',
109
109
  modelId: initialInfo.nativeModelId,
110
110
  supportedUrls: {},
@@ -231,7 +231,7 @@ const CACHE_CONTROL_EPHEMERAL = Object.freeze({ type: 'ephemeral' });
231
231
  * only the turn-constant tools/system cached and forfeit most of the win.
232
232
  * Mutates only `commonParams`'s own arrays — which
233
233
  * `translate*ToAnthropic` builds fresh per request — never the caller's
234
- * original `LanguageModelV3` prompt/tool objects.
234
+ * original `LanguageModelV4` prompt/tool objects.
235
235
  */
236
236
  export function applyCacheBreakpoints(commonParams) {
237
237
  const tools = commonParams.tools;
@@ -348,7 +348,7 @@ async function doGenerate(ctx, options) {
348
348
  content.push(...buildValidatedToolCallChunk(block.id, block.name, argsBuffer, toolSchemas.get(block.name), ctx.getConsumerToolNames().has(block.name), { retryCounts: ctx.getToolArgsRetryCounts(options), logBus: ctx.logBus }));
349
349
  }
350
350
  // Other block types (redacted_thinking, server-tool-result, etc.) are
351
- // not in the V3 ChatEvent surface — same posture as doStream.
351
+ // not in the V4 ChatEvent surface — same posture as doStream.
352
352
  }
353
353
  const finishReason = mapAnthropicFinishReason(message.stop_reason, sawToolCalls);
354
354
  const usage = mapAnthropicUsage(message.usage);
@@ -398,7 +398,7 @@ async function doStream(ctx, options) {
398
398
  const stream = new ReadableStream({
399
399
  start: async (controller) => {
400
400
  const textSpan = new SpanLifecycle();
401
- // Extended-thinking narration streams on its own V3 reasoning span,
401
+ // Extended-thinking narration streams on its own V4 reasoning span,
402
402
  // so it surfaces as `reasoning-delta` (→ AG-UI REASONING_MESSAGE_CONTENT)
403
403
  // rather than bleeding into the answer's `text-delta`.
404
404
  const reasoningSpan = new SpanLifecycle('reasoning');
@@ -432,7 +432,7 @@ async function doStream(ctx, options) {
432
432
  case 'content_block_start': {
433
433
  const block = event.content_block;
434
434
  if (block.type === 'text') {
435
- // Open the V3 text span on the first text block of the message
435
+ // Open the V4 text span on the first text block of the message
436
436
  // (or after a tool_use rotated us forward).
437
437
  textSpan.openIfNeeded(controller);
438
438
  }
@@ -460,7 +460,7 @@ async function doStream(ctx, options) {
460
460
  });
461
461
  }
462
462
  // Other block types (redacted_thinking, server-tool result, etc.)
463
- // are intentionally ignored — they're not in the V3 ChatEvent surface.
463
+ // are intentionally ignored — they're not in the V4 ChatEvent surface.
464
464
  break;
465
465
  }
466
466
  case 'content_block_delta': {
@@ -481,7 +481,7 @@ async function doStream(ctx, options) {
481
481
  }
482
482
  }
483
483
  // signature_delta / citations_delta are not surfaced — the
484
- // encrypted thinking signature has no V3 stream-part and no
484
+ // encrypted thinking signature has no V4 stream-part and no
485
485
  // consumer surface (AG-UI ReasoningEncryptedValue is deferred).
486
486
  break;
487
487
  }
@@ -526,7 +526,7 @@ async function doStream(ctx, options) {
526
526
  }
527
527
  // Stream ended without an explicit `message_stop`. If we saw at least
528
528
  // one recognized content / usage event, close out the span and
529
- // synthesize a terminal `finish` so V3 consumers see exactly one. If
529
+ // synthesize a terminal `finish` so V4 consumers see exactly one. If
530
530
  // we saw NONE — a clean-but-empty body — surface a classified `error`
531
531
  // instead of a false-success empty `finish` (8a). A cancelled turn is
532
532
  // exempt: an aborted stream ends cleanly here too (the SDK `Stream`
@@ -556,7 +556,7 @@ async function doStream(ctx, options) {
556
556
  // Trailing-JSON #396: the gateway sometimes appends a JSON error envelope
557
557
  // after the terminal eventstream frame on otherwise-successful responses,
558
558
  // which the Anthropic SDK's parser surfaces as a late throw. Drop it
559
- // silently — V3 consumers must see exactly one terminal `finish`.
559
+ // silently — V4 consumers must see exactly one terminal `finish`.
560
560
  controller.close();
561
561
  return;
562
562
  }
@@ -574,7 +574,7 @@ async function doStream(ctx, options) {
574
574
  });
575
575
  return { stream };
576
576
  }
577
- /** Default Anthropic `max_tokens` when V3 doesn't supply one. The Bedrock invoke
577
+ /** Default Anthropic `max_tokens` when V4 doesn't supply one. The Bedrock invoke
578
578
  * API requires the field; Mastra leaves it unset for many turns. 4096 mirrors
579
579
  * the `@ai-sdk/anthropic` provider's default. */
580
580
  const DEFAULT_MAX_TOKENS = 4096;
@@ -590,12 +590,12 @@ const DEFAULT_MAX_TOKENS = 4096;
590
590
  */
591
591
  const SUMMARY_MAX_OUTPUT_TOKENS = 16384;
592
592
  /**
593
- * Translates a V3 `LanguageModelV3Prompt` into Anthropic's Messages API shape
593
+ * Translates a V4 `LanguageModelV4Prompt` into Anthropic's Messages API shape
594
594
  * — top-level `system` string + `MessageParam[]` of role-tagged content blocks.
595
595
  *
596
- * V3 keeps system / user / assistant / tool messages flat. Anthropic combines
596
+ * V4 keeps system / user / assistant / tool messages flat. Anthropic combines
597
597
  * tool results onto a synthetic user-role turn carrying `tool_result` blocks,
598
- * so we collapse consecutive tool-only V3 messages into the surrounding user
598
+ * so we collapse consecutive tool-only V4 messages into the surrounding user
599
599
  * turn shape.
600
600
  */
601
601
  export function translatePromptToAnthropic(prompt) {
@@ -669,7 +669,7 @@ export function translatePromptToAnthropic(prompt) {
669
669
  return system !== undefined ? { system, messages } : { messages };
670
670
  }
671
671
  /**
672
- * Translates V3 function-tool definitions into Anthropic's `Tool[]` shape.
672
+ * Translates V4 function-tool definitions into Anthropic's `Tool[]` shape.
673
673
  * Skips non-function variants (provider-defined / dynamic) — the pass-through
674
674
  * path supports only the function-tool subset that Mastra's tool mapper produces.
675
675
  */
@@ -685,13 +685,11 @@ export function translateToolsToAnthropic(options) {
685
685
  }));
686
686
  }
687
687
  function toAnthropicFileBlock(part) {
688
- if (typeof part.data !== 'string') {
689
- throw new Error(`Unsupported file data for "${part.mediaType}": expected a base64 string, got ${part.data instanceof URL ? 'a URL' : 'binary data'}. Only base64-encoded file data is supported.`);
690
- }
688
+ const base64 = v4FileDataToBase64(part.data, part.mediaType);
691
689
  if (part.mediaType === 'application/pdf') {
692
690
  return {
693
691
  type: 'document',
694
- source: { type: 'base64', media_type: 'application/pdf', data: part.data },
692
+ source: { type: 'base64', media_type: 'application/pdf', data: base64 },
695
693
  };
696
694
  }
697
695
  if (part.mediaType === 'image/png' ||
@@ -700,7 +698,7 @@ function toAnthropicFileBlock(part) {
700
698
  part.mediaType === 'image/webp') {
701
699
  return {
702
700
  type: 'image',
703
- source: { type: 'base64', media_type: part.mediaType, data: part.data },
701
+ source: { type: 'base64', media_type: part.mediaType, data: base64 },
704
702
  };
705
703
  }
706
704
  throw new Error(`Unsupported file media type "${part.mediaType}". Anthropic supports image/png, image/jpeg, image/gif, image/webp, application/pdf.`);
@@ -1,10 +1,10 @@
1
1
  import type { ChatCompletionMessageParam, ChatCompletionTool } from 'openai/resources/chat/completions';
2
- import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3FinishReason, LanguageModelV3Prompt } from '@ai-sdk/provider';
2
+ import type { LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4FinishReason, LanguageModelV4Prompt } from '@ai-sdk/provider';
3
3
  import type { LogBus } from '@salesforce/agentic-common';
4
4
  import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
5
- import { type GatewayFetchWireHooks } from './v3-stream-mapping.js';
5
+ import { type GatewayFetchWireHooks } from './stream-mapping.js';
6
6
  /**
7
- * Builds the in-process `LanguageModelV3` impl that drives the `openai` SDK's
7
+ * Builds the in-process `LanguageModelV4` impl that drives the `openai` SDK's
8
8
  * `chat.completions.create({ stream: true })` against an OpenAI-compatible
9
9
  * endpoint (LLM Gateway Express, LiteLLM, vLLM, etc. — anything that speaks
10
10
  * `/v1/chat/completions`).
@@ -15,7 +15,7 @@ import { type GatewayFetchWireHooks } from './v3-stream-mapping.js';
15
15
  * `input` split); (3) streaming events are `ChatCompletionChunk` objects with
16
16
  * `choices[0].delta` text/tool-call deltas and the terminal `finish_reason`
17
17
  * landing on the same chunk shape rather than a discrete `response.completed`
18
- * event. The V3 mapping output is identical — same `text-delta` / `tool-call` /
18
+ * event. The V4 mapping output is identical — same `text-delta` / `tool-call` /
19
19
  * `finish` parts the Responses builder produces.
20
20
  *
21
21
  * Per-call freshness contract is the same as the other builders: every HTTP
@@ -35,28 +35,28 @@ export declare function buildOpenAIChatCompletionsLanguageModel(opts: {
35
35
  /** See `buildAnthropicLanguageModel` `getConsumerToolSchemas`. */
36
36
  getConsumerToolSchemas?: () => ReadonlyMap<string, Record<string, unknown>>;
37
37
  /** See `buildAnthropicLanguageModel` `getToolArgsRetryCounts`. */
38
- getToolArgsRetryCounts?: (options: LanguageModelV3CallOptions) => Map<string, number>;
38
+ getToolArgsRetryCounts?: (options: LanguageModelV4CallOptions) => Map<string, number>;
39
39
  /** Per-read idle timeout (ms) for the gateway response body; see `createGatewayFetch`. */
40
40
  idleReadTimeoutMs?: number;
41
- }): LanguageModelV3;
41
+ }): LanguageModelV4;
42
42
  /**
43
- * Translates a V3 `LanguageModelV3Prompt` into the OpenAI Chat Completions
43
+ * Translates a V4 `LanguageModelV4Prompt` into the OpenAI Chat Completions
44
44
  * `messages` array shape. System messages stay as `system` role; user / tool
45
45
  * follow naturally. Assistant text + tool-calls collapse onto a single
46
46
  * assistant message carrying a `content` string (when text is present) plus
47
47
  * `tool_calls` (when calls are present). Tool results land on `tool` role
48
48
  * with `tool_call_id`.
49
49
  */
50
- export declare function translatePromptToChatCompletions(prompt: LanguageModelV3Prompt): ChatCompletionMessageParam[];
50
+ export declare function translatePromptToChatCompletions(prompt: LanguageModelV4Prompt): ChatCompletionMessageParam[];
51
51
  /**
52
- * Translates V3 function-tool definitions into the Chat Completions `tools[]`
52
+ * Translates V4 function-tool definitions into the Chat Completions `tools[]`
53
53
  * shape. Skips non-function variants — same posture as the Responses builder.
54
54
  */
55
- export declare function translateToolsToChatCompletions(options: LanguageModelV3CallOptions): ChatCompletionTool[];
55
+ export declare function translateToolsToChatCompletions(options: LanguageModelV4CallOptions): ChatCompletionTool[];
56
56
  /**
57
- * Maps Chat Completions `finish_reason` onto V3's unified shape. The Chat
57
+ * Maps Chat Completions `finish_reason` onto V4's unified shape. The Chat
58
58
  * Completions API uses `'stop' | 'length' | 'tool_calls' | 'content_filter' |
59
59
  * 'function_call' | null`. `function_call` is the legacy shape from before
60
60
  * `tool_calls` existed; modern endpoints emit `'tool_calls'`.
61
61
  */
62
- export declare function mapChatCompletionFinishReason(raw: string | null, sawToolCalls: boolean): LanguageModelV3FinishReason;
62
+ export declare function mapChatCompletionFinishReason(raw: string | null, sawToolCalls: boolean): LanguageModelV4FinishReason;
@@ -4,9 +4,9 @@
4
4
  */
5
5
  import OpenAI from 'openai';
6
6
  import { EmptyStreamError } from './stream-errors.js';
7
- import { SpanLifecycle, ToolInputSpanLifecycle, buildValidatedToolCallChunk, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapOpenAIUsage, toolResultOutputToString, } from './v3-stream-mapping.js';
7
+ import { SpanLifecycle, ToolInputSpanLifecycle, buildValidatedToolCallChunk, createGatewayFetch, isEmptyUsage, isParameterExcluded, mapOpenAIUsage, toolResultOutputToString, v4FileDataToBase64, } from './stream-mapping.js';
8
8
  /**
9
- * Builds the in-process `LanguageModelV3` impl that drives the `openai` SDK's
9
+ * Builds the in-process `LanguageModelV4` impl that drives the `openai` SDK's
10
10
  * `chat.completions.create({ stream: true })` against an OpenAI-compatible
11
11
  * endpoint (LLM Gateway Express, LiteLLM, vLLM, etc. — anything that speaks
12
12
  * `/v1/chat/completions`).
@@ -17,7 +17,7 @@ import { SpanLifecycle, ToolInputSpanLifecycle, buildValidatedToolCallChunk, cre
17
17
  * `input` split); (3) streaming events are `ChatCompletionChunk` objects with
18
18
  * `choices[0].delta` text/tool-call deltas and the terminal `finish_reason`
19
19
  * landing on the same chunk shape rather than a discrete `response.completed`
20
- * event. The V3 mapping output is identical — same `text-delta` / `tool-call` /
20
+ * event. The V4 mapping output is identical — same `text-delta` / `tool-call` /
21
21
  * `finish` parts the Responses builder produces.
22
22
  *
23
23
  * Per-call freshness contract is the same as the other builders: every HTTP
@@ -41,7 +41,7 @@ export function buildOpenAIChatCompletionsLanguageModel(opts) {
41
41
  idleReadTimeoutMs,
42
42
  };
43
43
  return {
44
- specificationVersion: 'v3',
44
+ specificationVersion: 'v4',
45
45
  provider: 'openai-compatible-chat-completions-pass-through',
46
46
  modelId: initialInfo.nativeModelId,
47
47
  supportedUrls: {},
@@ -131,7 +131,7 @@ async function doStream(ctx, options) {
131
131
  // `stream_options.include_usage` opts the upstream into emitting a final
132
132
  // chunk with cumulative usage on `usage`. Many OpenAI-compatible gateways
133
133
  // honor this; those that don't simply omit the field, which we degrade
134
- // to `undefined` `LanguageModelV3Usage` per the missing-usage contract.
134
+ // to `undefined` `LanguageModelV4Usage` per the missing-usage contract.
135
135
  stream_options: { include_usage: true },
136
136
  };
137
137
  // See doGenerate: thread the AbortSignal into the SDK so a mid-stream abort
@@ -174,11 +174,11 @@ async function doStream(ctx, options) {
174
174
  sawContentOrUsage = true;
175
175
  }
176
176
  if (delta.tool_calls) {
177
- // V3 ordering: any open text span must close BEFORE tool-call parts
177
+ // V4 ordering: any open text span must close BEFORE tool-call parts
178
178
  // land. Without this rotate, a `delta.content` chunk followed by a
179
179
  // `delta.tool_calls` chunk in the same stream produces
180
180
  // `text-start → text-delta → tool-call → text-end → finish`, which
181
- // violates the V3 lifecycle (peer builders pass-through-anthropic.ts
181
+ // violates the V4 lifecycle (peer builders pass-through-anthropic.ts
182
182
  // and pass-through-openai.ts both rotate eagerly at the same moment).
183
183
  // `rotate` is a no-op when no text span is open.
184
184
  textSpan.rotate(controller);
@@ -297,7 +297,7 @@ async function doStream(ctx, options) {
297
297
  if (terminalEmitted) {
298
298
  // Symmetric defense with the Anthropic / OpenAI Responses builders —
299
299
  // a trailing-JSON throw after the terminal `finish` is enqueued must
300
- // not corrupt the V3 stream contract.
300
+ // not corrupt the V4 stream contract.
301
301
  controller.close();
302
302
  return;
303
303
  }
@@ -309,7 +309,7 @@ async function doStream(ctx, options) {
309
309
  return { stream };
310
310
  }
311
311
  /**
312
- * Translates a V3 `LanguageModelV3Prompt` into the OpenAI Chat Completions
312
+ * Translates a V4 `LanguageModelV4Prompt` into the OpenAI Chat Completions
313
313
  * `messages` array shape. System messages stay as `system` role; user / tool
314
314
  * follow naturally. Assistant text + tool-calls collapse onto a single
315
315
  * assistant message carrying a `content` string (when text is present) plus
@@ -378,7 +378,7 @@ export function translatePromptToChatCompletions(prompt) {
378
378
  return messages;
379
379
  }
380
380
  /**
381
- * Translates V3 function-tool definitions into the Chat Completions `tools[]`
381
+ * Translates V4 function-tool definitions into the Chat Completions `tools[]`
382
382
  * shape. Skips non-function variants — same posture as the Responses builder.
383
383
  */
384
384
  export function translateToolsToChatCompletions(options) {
@@ -396,16 +396,14 @@ export function translateToolsToChatCompletions(options) {
396
396
  }));
397
397
  }
398
398
  function toChatCompletionsFilePart(part) {
399
- if (typeof part.data !== 'string') {
400
- throw new Error(`Unsupported file data for "${part.mediaType}": expected a base64 string, got ${part.data instanceof URL ? 'a URL' : 'binary data'}. Only base64-encoded file data is supported.`);
401
- }
399
+ const base64 = v4FileDataToBase64(part.data, part.mediaType);
402
400
  if (part.mediaType === 'image/png' ||
403
401
  part.mediaType === 'image/jpeg' ||
404
402
  part.mediaType === 'image/gif' ||
405
403
  part.mediaType === 'image/webp') {
406
404
  return {
407
405
  type: 'image_url',
408
- image_url: { url: `data:${part.mediaType};base64,${part.data}`, detail: 'auto' },
406
+ image_url: { url: `data:${part.mediaType};base64,${base64}`, detail: 'auto' },
409
407
  };
410
408
  }
411
409
  // Chat Completions doesn't accept inline file (PDF) parts the way Anthropic
@@ -414,7 +412,7 @@ function toChatCompletionsFilePart(part) {
414
412
  // PDF support should use the Responses API path.
415
413
  if (part.mediaType === 'application/pdf') {
416
414
  const filename = part.filename ?? 'attachment.pdf';
417
- const sizeKb = Math.round((part.data.length * 0.75) / 1024);
415
+ const sizeKb = Math.round((base64.length * 0.75) / 1024);
418
416
  return {
419
417
  type: 'text',
420
418
  text: `[file: ${filename} (application/pdf, ~${sizeKb} KB) — file content not supported by /chat/completions; consider switching to providerHint: 'openai-responses' for PDF support]`,
@@ -424,7 +422,7 @@ function toChatCompletionsFilePart(part) {
424
422
  `Supported: image/png, image/jpeg, image/gif, image/webp.`);
425
423
  }
426
424
  /**
427
- * Maps Chat Completions usage onto the V3 `LanguageModelV3Usage` shape via the
425
+ * Maps Chat Completions usage onto the V4 `LanguageModelV4Usage` shape via the
428
426
  * shared OpenAI usage mapper — Chat Completions and Responses surface the same
429
427
  * post-normalisation shape (`prompt_tokens` / `completion_tokens` /
430
428
  * `*_tokens_details`).
@@ -433,7 +431,7 @@ function mapChatCompletionUsage(usage) {
433
431
  return mapOpenAIUsage(usage ?? undefined);
434
432
  }
435
433
  /**
436
- * Maps Chat Completions `finish_reason` onto V3's unified shape. The Chat
434
+ * Maps Chat Completions `finish_reason` onto V4's unified shape. The Chat
437
435
  * Completions API uses `'stop' | 'length' | 'tool_calls' | 'content_filter' |
438
436
  * 'function_call' | null`. `function_call` is the legacy shape from before
439
437
  * `tool_calls` existed; modern endpoints emit `'tool_calls'`.