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 exampleUsage 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:
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 runsprocessAPIErroron error processors, so an instance registered only ininputProcessorsnever runs its reactive rules. - Preemptive rules run from either list.
processLLMRequestruns over the input processors and over the resolved error processors, so aProviderHistoryCompatinerrorProcessorsgets its prompt rewrites as well as its reactive recovery. An instance registered ininputProcessorsruns 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 parametersConstructor parameters的直接链接
opts?:
additionalRules?:
PropertiesProperties的直接链接
id:
name:
processLLMRequest:
processAPIError:
Built-in rulesBuilt-in rules的直接链接
ProviderHistoryCompat includes these built-in compatibility rules:
| Rule | Provider | Timing | Behavior |
|---|---|---|---|
strip-foreign-provider-executed-tools | Anthropic, OpenAI, Azure OpenAI, Google/Vertex | Preemptive prompt rewrite | Removes 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-format | Anthropic | Preemptive prompt rewrite | Rewrites 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-content | Cerebras | Preemptive prompt rewrite | Removes assistant reasoning parts from the outbound prompt so they're not serialized as unsupported reasoning_content fields. |
anthropic-strip-empty-signed-reasoning-content | Anthropic | Preemptive prompt rewrite | Removes 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-content | Anthropic | Preemptive prompt rewrite | Removes non-Anthropic assistant reasoning parts from the outbound prompt. Anthropic-native thinking history is preserved. |
anthropic-strip-foreign-signed-reasoning | Anthropic-compatible | Preemptive prompt rewrite | Drops 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-transform | Azure OpenAI | Preemptive prompt rewrite | Renames <system-reminder> wrappers in user text and system instructions to <memory-context> for the outbound request. Stored history remains unchanged. |
openai-orphan-item-id | OpenAI, Azure OpenAI (Responses) | Reactive API error recovery | Recovers 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-step | Anthropic | Reactive API error recovery | Recovers 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-turn | Amazon Bedrock, Google/Vertex Gemini | Preemptive prompt rewrite | Inserts 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.
CompatRulecompatrule的直接链接
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:
errorPatterns?:
fix?:
applyToPrompt?:
Custom rulesCustom rules的直接链接
Pass custom rules through additionalRules. Custom rules run after the built-in rules:
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-scopedTool 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 fallbackReactive 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.