Skip to main content

ProviderHistoryCompat

The ProviderHistoryCompat processor handles provider-specific history incompatibilities. It can rewrite the outbound language model prompt before a provider call, or react to API errors and retry with repaired message history.

Use it when an agent may switch between model providers or reuse message history across providers. It also handles providers that reject fields emitted by another provider.

Usage example
Direct link to Usage example

Mastra adds this processor to every agent's errorProcessors by default when your list doesn't already contain that id. Among the added defaults it comes first. Its outbound prompt rules run before the other defaults, including the retry processor's blind resend. Supply your own instance to add custom rules or position it yourself:

src/mastra/agents/my-agent.ts
import { Agent } from '@mastra/core/agent'
import { ProviderHistoryCompat } from '@mastra/core/processors'

export const agent = new Agent({
id: 'my-agent',
name: 'my-agent',
instructions: 'You are a helpful assistant.',
model: 'anthropic/claude-sonnet-4-5',
errorProcessors: [new ProviderHistoryCompat({ additionalRules: [/* ... */] })],
})

A supplied ProviderHistoryCompat is used instead of the default. The default is only added when no processor in your list carries that id. Place an instance in inputProcessors when you want the prompt-rewriting rules to run in the input lane instead.

The placement decides which half of the processor works:

  • Reactive rules need errorProcessors. An agent wraps its input processors in a single processor workflow, and the API-error pass only runs processAPIError on error processors, so an instance registered only in inputProcessors never runs its reactive rules.
  • Preemptive rules run from either list. processLLMRequest runs over the input processors and over the resolved error processors, so a ProviderHistoryCompat in errorProcessors gets its prompt rewrites as well as its reactive recovery. An instance registered in inputProcessors runs once, in its input position.

Registering in errorProcessors alone, as the example above does, covers both halves. Double registration can't cause a double retry: the runner stops at the first processor that asks for one, and each reactive rule bails once retryCount is above zero, so a repaired turn is attempted exactly once more.

Constructor parameters
Direct link to Constructor parameters

opts?:

{ additionalRules?: CompatRule[] }
Configuration options for provider history compatibility rules.
Options

additionalRules?:

CompatRule[]
Custom compatibility rules to run after the built-in rules. Rules can rewrite the outbound prompt or repair persisted messages after matching an API error.

Properties
Direct link to Properties

id:

'provider-history-compat'
Processor identifier.

name:

'Provider History Compat'
Processor display name.

processLLMRequest:

(args: ProcessLLMRequestArgs) => ProcessLLMRequestResult
Runs preemptive compatibility rules against the converted LanguageModelV2Prompt immediately before the provider call. Returned prompt changes are transient and are not persisted to memory or message history.

processAPIError:

(args: ProcessAPIErrorArgs) => Promise<ProcessAPIErrorResult | void>
Runs reactive compatibility rules when a provider rejects the request. Matching rules can mutate the message list and return retry: true on the first retry attempt.

Built-in rules
Direct link to Built-in rules

ProviderHistoryCompat includes these built-in compatibility rules:

RuleProviderTimingBehavior
strip-foreign-provider-executed-toolsAnthropic, OpenAI, Azure OpenAI, Google/VertexPreemptive prompt rewriteRemoves provider-executed tool calls and their paired results from the outbound prompt when they came from a different provider family, since the receiving provider can't resolve another provider's tool IDs. Persisted history is left untouched.
anthropic-tool-id-formatAnthropicPreemptive prompt rewriteRewrites tool call IDs that contain characters outside [a-zA-Z0-9_-] in the outbound prompt, on both the call and its result. A sanitized ID that would collide with one already in the prompt gets a _2, _3, … suffix. The provider's rejection never fires, and persisted history keeps the original IDs. A reactive fallback covers providers the preemptive check doesn't recognize, such as Vertex-hosted Claude.
cerebras-strip-reasoning-contentCerebrasPreemptive prompt rewriteRemoves assistant reasoning parts from the outbound prompt so they're not serialized as unsupported reasoning_content fields.
anthropic-strip-empty-signed-reasoning-contentAnthropicPreemptive prompt rewriteRemoves assistant reasoning parts that carry an Anthropic signature but have empty text, which legacy records can contain. Anthropic signs the exact thinking text, so the mismatched pair would be rejected.
anthropic-strip-foreign-reasoning-contentAnthropicPreemptive prompt rewriteRemoves non-Anthropic assistant reasoning parts from the outbound prompt. Anthropic-native thinking history is preserved.
anthropic-strip-foreign-signed-reasoningAnthropic-compatiblePreemptive prompt rewriteDrops signed thinking from the outbound prompt when its origin turn was stamped with a different provider (for example Kimi For Coding ↔ anthropic/claude-sonnet-4-6), since the receiving provider can't verify another provider's signature. Turns emptied of all content by the drop are removed from the prompt. Unstamped history is left untouched.
azure-system-reminder-transformAzure OpenAIPreemptive prompt rewriteRenames <system-reminder> wrappers in user text and system instructions to <memory-context> for the outbound request. Stored history remains unchanged.
openai-orphan-item-idOpenAI, Azure OpenAI (Responses)Reactive API error recoveryRecovers history that OpenAI rejects with Item 'msg_…' of type 'message' was provided without its required 'reasoning' item. Drops the Responses itemId from every item-bearing part of an assistant message that carries one but has no reasoning part, and retries. The message then replays by value instead of as an unsatisfiable item_reference. The repair is applied to the in-memory list for the current turn.
anthropic-orphaned-thinking-stepAnthropicReactive API error recoveryRecovers history that Anthropic rejects because thinking or redacted_thinking blocks in the latest assistant message cannot be modified. Removes steps that contain only reasoning when more assistant content follows them, and retries. Covers history saved by older @mastra/memory releases (#22798).
ensure-user-first-turnAmazon Bedrock, Google/Vertex GeminiPreemptive prompt rewriteInserts a minimal user turn (.) before a leading assistant turn, because these providers reject conversations whose first non-system turn is the assistant's. This happens when an agent greets first or lastMessages cuts into a thread. Runs last, so it sees the prompt after other rules have dropped emptied turns.

Preemptive rules run through processLLMRequest after Mastra converts messages to the model prompt format and before the prompt is sent to the provider. These rewrites affect only the current provider call. Persisted history is never changed, so the IDs and content in memory and in the Studio stay exactly as written.

Reactive rules run through processAPIError after a provider rejection. They can update the persisted messageList and request a retry.

anthropic-tool-id-format implements both. Its preemptive hook normally prevents the rejection from happening at all, which leaves the reactive hook dormant. See Tool call ID repair is prompt-scoped below.

CompatRule
Direct link to compatrule

A CompatRule defines one provider history compatibility fix:

import type { CompatRule } from '@mastra/core/processors'

const removeUnsupportedPromptParts: CompatRule = {
name: 'remove-unsupported-prompt-parts',
applyToPrompt({ prompt, model }) {
// Return a modified LanguageModelV2Prompt, or undefined to leave it unchanged.
return undefined
},
}

name:

string
Human-readable rule identifier for logs and debugging.

errorPatterns?:

RegExp[]
Patterns matched against provider API error messages and response bodies. Required for reactive rules that implement fix.

fix?:

(messages: MastraDBMessage[]) => boolean
Reactive fix that mutates persisted database messages after a matching API error. Return true when the rule changed messages and the request should retry.

applyToPrompt?:

(args: { prompt: LanguageModelV2Prompt; model: unknown }) => LanguageModelV2Prompt | undefined
Preemptive fix that rewrites the outbound prompt for the current provider call. Return undefined when no prompt change is needed.

Custom rules
Direct link to Custom rules

Pass custom rules through additionalRules. Custom rules run after the built-in rules:

src/mastra/agents/custom-provider-compat.ts
import { Agent } from '@mastra/core/agent'
import { ProviderHistoryCompat, type CompatRule } from '@mastra/core/processors'

const stripUnsupportedAssistantMetadata: CompatRule = {
name: 'strip-unsupported-assistant-metadata',
applyToPrompt({ prompt, model }) {
if (typeof model !== 'string' || !model.startsWith('example-provider/')) {
return undefined
}

let changed = false
const nextPrompt = prompt.map(message => {
if (message.role !== 'assistant' || typeof message.content === 'string') {
return message
}

const nextContent = message.content.map(part => {
if (!('providerOptions' in part)) return part
changed = true
const { providerOptions: _providerOptions, ...rest } = part
return rest
})

return { ...message, content: nextContent }
})

return changed ? nextPrompt : undefined
},
}

export const agent = new Agent({
id: 'custom-provider-agent',
name: 'custom-provider-agent',
instructions: 'You are a helpful assistant.',
model: 'example-provider/model',
inputProcessors: [
new ProviderHistoryCompat({
additionalRules: [stripUnsupportedAssistantMetadata],
}),
],
})

Use applyToPrompt for provider-specific rewrites that shouldn't be saved to memory. This is what new rules should normally reach for, and it's the hook the built-in rules use. Use fix with errorPatterns only when the repaired history must persist and be reused on future turns, and when the provider can't be stopped from rejecting the request up front.

Tool call ID repair is prompt-scoped
Direct link to Tool call ID repair is prompt-scoped

Anthropic enforces ^[a-zA-Z0-9_-]+$ on tool_use.id. A tool call whose ID came from another provider, containing . or :, is rejected. The rule sanitizes those IDs in the outbound request:

// What the provider receives
tool_use.id: 'call_abc_1'
// What stays in memory, in the returned messages, and in the Studio
toolCallId: 'call.abc:1'

Both the tool-call and its paired tool-result are rewritten from the same map, so call/result pairing survives the rewrite. Replacement IDs are assigned in encounter order and never collide with an ID the prompt already carries: if a.b sanitizes to a_b and the prompt already has an a_b, the rewritten ID becomes a_b_2.

Because nothing is persisted, a caller that reuses the same message list in a later turn sees the original IDs again, and the repair runs again on that request.

Reactive fallback
Direct link to Reactive fallback

The rule's errorPatterns and fix hooks repair the same rejection after the provider raises it. The preemptive rewrite only runs when isMaybeAnthropic(model) matches. The reactive path stays the fallback for Claude served through a provider the check doesn't recognize, such as Vertex-hosted Claude, and the only repair once a call has already been rejected. A rule can use both hooks together, and fix on the CompatRule interface itself isn't deprecated: custom rules legitimately use it.