fix(anthropic): default new Claude models to the modern thinking contract - #42991
Conversation
…ract
New Anthropic models without a recognized version substring (claude-fable-5
and future named/numbered releases) were classified as legacy and routed down
the manual-thinking path, which made OpenRouter emit thinking.type.disabled —
a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400.
Invert the brittle version-substring allowlists to default-to-modern (mirroring
_get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/
no-sampling contract, with an explicit legacy list for older families. Non-Claude
Anthropic-Messages models (minimax, qwen3, …) keep the manual path.
- anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort /
_forbids_sampling_params now default unknown Claude models to modern; legacy
families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS.
- openrouter profile: omit reasoning entirely (→ adaptive default) instead of
forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy
Anthropic + all non-Anthropic models still pass the disable form through.
- model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out).
Tests assert the invariant ("unknown Claude model -> modern contract; legacy
stays manual; non-Claude unaffected"), not specific model names.
🔎 Lint report:
|
|
Verification comment from automated code review Reviewed this PR as part of a scheduled review pass. The change is clean and well-structured. Criteria matched: security-critical (model contract defaults), non-trivial logic (legacy→modern inversion), zero existing reviews, good test coverage (8 test functions across 3 files) Observations: The default-to-modern architecture is sound — inverting the allowlist to a legacy-blocklist means future Claude releases (fable, mythos, numbered 5.x) work without code changes. The _ANTHROPIC_REASONING_OPTIONAL_SUBSTRINGS in the OpenRouter plugin mirrors the same legacy list, ensuring consistency across the Anthropic adapter and OpenRouter profile. Test coverage is thorough: fable-class models route as adaptive, legacy stays manual, non-Claude Anthropic models use the manual path, 4.6 is adaptive-but-not-xhigh/no-sampling, and the OpenRouter reasoning-disable guard is tested for mandatory, legacy, non-Anthropic, and enabled-on-mandatory paths. No issues found. LGTM. 👍 |
…dels (#43012) The previous fix (#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. #42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the #42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes #43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (NousResearch#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. NousResearch#42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the NousResearch#42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes NousResearch#43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
…ract (#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (#43012) The previous fix (#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. #42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the #42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes #43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (NousResearch#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. NousResearch#42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the NousResearch#42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes NousResearch#43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (NousResearch#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. NousResearch#42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the NousResearch#42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes NousResearch#43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (NousResearch#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. NousResearch#42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the NousResearch#42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes NousResearch#43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
…ract (NousResearch#42991) New Anthropic models without a recognized version substring (claude-fable-5 and future named/numbered releases) were classified as legacy and routed down the manual-thinking path, which made OpenRouter emit thinking.type.disabled — a form reasoning-mandatory Claude models reject with a non-retryable HTTP 400. Invert the brittle version-substring allowlists to default-to-modern (mirroring _get_anthropic_max_output): unknown Claude models get the adaptive/xhigh/ no-sampling contract, with an explicit legacy list for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) keep the manual path. - anthropic_adapter: _supports_adaptive_thinking / _supports_xhigh_effort / _forbids_sampling_params now default unknown Claude models to modern; legacy families enumerated in _LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS. - openrouter profile: omit reasoning entirely (→ adaptive default) instead of forwarding {enabled:false} for reasoning-mandatory Anthropic models; legacy Anthropic + all non-Anthropic models still pass the disable form through. - model_metadata + output-limit table: register claude-fable-5 (1M ctx, 128K out). Tests assert the invariant ("unknown Claude model -> modern contract; legacy stays manual; non-Claude unaffected"), not specific model names.
…dels (NousResearch#43012) The previous fix (NousResearch#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
…hropic models (NousResearch#43436) * fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over OpenRouter ignore reasoning.effort and use adaptive thinking. NousResearch#42991 correctly stopped Hermes from sending a reasoning field to them (it 400s), but put nothing in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter path: the model always ran at its adaptive default (high) regardless of config. OpenRouter honors the requested effort on the top-level verbosity field instead (maps to Anthropic output_config.effort). Route the existing reasoning_config[effort] there for these models while still never emitting a reasoning field, preserving the NousResearch#42991 fix. No new config arg — the value the user already sets via agent.reasoning_effort now flows to verbosity. - low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the extended scale for Claude; verified live HTTP 200 + monotonic token spend). - effort unset/none/disabled omits verbosity so the model keeps its default. - native Anthropic transport already correct; unchanged. Fixes NousResearch#43432 * test(openrouter): cover real effort range (add minimal, frame max as passthrough) Adversarial review noted the verbosity tests looped over 'max' — a value parse_reasoning_effort can never produce — while omitting 'minimal', which it can. Align the routing test with the real config range (VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate value-agnostic passthrough test that documents why xhigh/max must survive verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the extended scale for Claude). * docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models Document that reasoning_effort transparently maps to OpenRouter's verbosity field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire- only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
Summary
New Anthropic models (claude-fable-5 and future named/numbered releases) now route correctly instead of dying with a non-retryable HTTP 400 on the first API call.
Root cause: Hermes decided Anthropic thinking behavior by substring-matching the model name against hardcoded version numbers (
"4.6","4.7","4.8").claude-fable-5contains none of them, so it fell through to the legacy manual-thinking path. Over OpenRouter that path forwardsreasoning: {enabled: false}when reasoning is off, which OpenRouter translates into Anthropic'sthinking: {type: "disabled"}— a form reasoning-mandatory Claude models (4.6+, fable-class) reject outright ("thinking.type.disabled" is not supported for this model).The defect is the asymmetry vs.
_get_anthropic_max_output, which already defaults unknown models to the newest behavior. The thinking-mode classifiers did the opposite — defaulting unknown models to legacy — so every new un-numbered model re-breaks.Changes
agent/anthropic_adapter.py: invert the version-substring allowlists._supports_adaptive_thinking/_supports_xhigh_effort/_forbids_sampling_paramsnow default unknown Claude models to the modern contract, with an explicit legacy list (_LEGACY_MANUAL_THINKING_CLAUDE_SUBSTRINGS) for older families. Non-Claude Anthropic-Messages models (minimax, qwen3, …) stay on the manual path.plugins/model-providers/openrouter/__init__.py: for reasoning-mandatory Anthropic models, omitreasoningentirely (→ adaptive default) instead of forwarding{enabled: false}. Legacy Anthropic + all non-Anthropic models still pass the disable form through unchanged.agent/model_metadata.py+_ANTHROPIC_OUTPUT_LIMITS: registerclaude-fable-5(1M context, 128K output).No
"fable"magic string in any behavior path — the durable fix is the default direction, so the next un-numbered release works with zero code changes.Validation
Live OpenRouter calls against
anthropic/claude-fable-5:{enabled: false})thinking.type.disabledeffort: none)Tests assert the invariant ("unknown Claude → modern; legacy stays manual; 4.6 adaptive-but-not-xhigh; non-Claude unaffected"), not specific model names.
tests/agent/test_anthropic_adapter.py+tests/providers/— 258 passed; full reasoning/thinking sweep across run_agent/model_metadata/fast_command — 301 passed.