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. On the standard loop, processAPIError runs from any of the three lists, so an input-processor registration does repair the message list, but the retry it asks for is only honored when the agent has a retry budget. With the default error processors in place the resolved list is non-empty, so a safety cap of 3 applies. The budget is left undefined only when the caller runs with no error processors at all (errorProcessorDefaults: false with none configured) and doesn't set maxProcessorRetries explicitly. Registered only as an input processor in that no-budget state, a reactive rule repairs the history and the request still fails. On the durable path the API-error pass additionally requires a non-empty errorProcessors list. If that list is empty, an input-processor registration can't trigger reactive recovery there at all, even with maxProcessorRetries set.
  • 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
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-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.

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.