Skip to content

fix(providers): detect router-prefixed reasoning model ids for the new OpenAI contract - #2815

Merged
esokullu merged 6 commits into
webbrain-one:mainfrom
alectimison-maker:fix/openai-router-reasoning-contract
Aug 17, 2026
Merged

fix(providers): detect router-prefixed reasoning model ids for the new OpenAI contract#2815
esokullu merged 6 commits into
webbrain-one:mainfrom
alectimison-maker:fix/openai-router-reasoning-contract

Conversation

@alectimison-maker

Copy link
Copy Markdown
Contributor

Summary

  • OpenAICompatibleProvider._isNewOpenAIContract() now matches reasoning model ids at the start or after a /, so router-prefixed ids like openai/o1, openai/o3-mini, and openai/gpt-5.6-terra get the new wire contract (max_completion_tokens, no temperature) instead of the legacy one.
  • Local servers and LM Studio keep the legacy contract (guards unchanged).

Motivation

Follow-up to #2807 (Azure reasoning deployments). The anchored regex /^(gpt-5|gpt-4\.1|o1|o3|o4)/ never matched prefixed router ids (OpenRouter-class routers), so a user routing openai/o1 received max_tokens + temperature: 0.7 — both rejected by OpenAI reasoning models — producing the same 400 loop the Azure fix addressed. The comment claiming "OpenRouter still uses the legacy contract" was wrong for reasoning models routed to OpenAI.

Design

One regex change in both trees: (?:^|\/)(?:gpt-5|gpt-4\.1|o1|o3|o4). Unprefixed behavior is preserved exactly (gpt-5.6-terra, o1-mini, gpt-4o classification unchanged); only ids with a prefixing segment newly classify as the new contract.

Testing

  • node test/run.js — 1765 passed, 0 failed (1 new test, both Chrome and Firefox providers)
  • npm run test:security — 60/60 passed
  • npm run test:toolbar-guard — 33 passed

New test asserts: openai/o1, openai/o3-mini, openai/gpt-5.6-terra → new contract (max_completion_tokens, no temperature); openai/gpt-4o, openrouter/deepseek-v3, openrouter/mistral-large → legacy; lmstudio + openai/o1 → legacy (local guard). One pre-existing GPT-5.6 test that encoded the old behavior for openai/gpt-5.6-terra was updated to the corrected contract.

Compatibility and risks

  • The only behavior change is for prefixed reasoning ids that were previously broken (400) — strictly a fix. Unprefixed and non-reasoning ids are byte-identical in behavior.
  • Mirrored in both trees.

@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

@alectimison-maker is attempting to deploy a commit to the esokullu's projects Team on Vercel.

A member of the Team first needs to authorize it.

@esokullu

Copy link
Copy Markdown
Collaborator

Reviewed this alongside #2808, #2809, #2813 and #2814.

Catching router-prefixed ids is the right idea, but the relaxed pattern has no trailing boundary, so it now also matches models that do accept the legacy parameters.

/(?:^|\/)(?:gpt-5|gpt-4\.1|o1|o3|o4)/ at src/chrome/src/providers/openai.js:177 matches openai/gpt-4.1, openai/gpt-5-chat, openai/o1-pro, azure/o3 and my-team/o4-mini-high. Of those, openai/gpt-4.1 on OpenRouter is a plain chat model that supports temperature and max_tokens fine. Neither guard above the regex helps: OpenRouter is category: 'router' in the catalog, not local, and its providerName isn't lmstudio.

Two consequences for that config. _addTemperature at :192 now returns early, so the agent's explicit temperatures are dropped on every call — temperature: 0 on the deterministic paths at agent.js:10749, :10769, :10818 and :14994, and 0.2 for compaction at :16629. Planner JSON and read-scope classification stop being deterministic. And _addMaxTokens switches the cap to max_completion_tokens, which OpenRouter's request schema doesn't model; it drops unrecognized parameters rather than erroring, so the user's maxTokens setting silently stops applying and generations run to the upstream limit.

That last part is what the edited assertion at test/run.js:48496 was guarding. The original assert.equal(compatibleProviderBody.max_tokens, 5, 'compatible providers should keep their requested token cap') matched OpenRouter's documented schema, and the deleted doc line in openai.js ("Local OpenAI-compatible servers and OpenRouter still use the legacy contract") was the written record of why. Rewriting the assertion turns a caught regression into expected behavior.

A trailing boundary would fix most of it — something along the lines of (?:gpt-5|o1|o3|o4)(?:$|[-_.\/]) with gpt-4.1 handled separately, since gpt-4.1 genuinely isn't in the same contract family as the o-series.

Two smaller things:

The LM Studio guard is dead for the catalog config and case-sensitive for every other one. :176 uses a bare === on this.config.providerName, unlike _shouldRequestStreamUsage at :231 and _headers at :130, which both normalize with .toLowerCase(). The catalog lmstudio entry sets category: 'local', so line 175 already caught it; the only configs that reach line 176 are hand-built or duplicated ones, and manager.js:827 only assigns category: 'local' for the fixed built-in id list. So a user who duplicates LM Studio or adds it as a custom OpenAI-compatible endpoint with providerName: 'LMStudio' gets max_completion_tokens and no temperature once the loaded model id is something like openai/o1, and the guard meant to stop that never fires. The test at test/run.js:50223-50228 sets both category and providerName, so it can't tell the two guards apart.

The settings panel keeps the old pattern. automaticTokenField() at src/chrome/src/ui/settings.js:2311 (firefox :1938) still has /^(gpt-5|gpt-4\.1|o1|o3|o4)/. With openai/o1 on OpenRouter and maxTokensField: 'auto', the provider sends max_completion_tokens while Settings › Compatibility reports max_tokens. Someone debugging a 400 who pins the displayed value re-breaks the model. Worth updating both, or better, moving the heuristic into provider-compatibility.js and having settings call it — #2808 adds a third copy of the same regex in azure-openai.js:73, so the two PRs together leave three copies per tree with two different patterns and a comment in azure-openai.js claiming they match.

…settings

The router-prefix regex had no trailing boundary, so it matched
openai/gpt-4.1 (a chat model that accepts temperature and max_tokens) and
look-alikes like o365-assistant, dropping explicit temperatures and
sending max_completion_tokens that OpenRouter silently ignores. Bound the
pattern to (?:^|\/)(?:gpt-5|o1|o3|o4)(?:$|[-_.\/]) and exclude gpt-4.1,
which stays on the legacy contract. Move the predicate to a shared
provider-compatibility helper used by both the provider and the settings
Compatibility panel, and make the LM Studio guard case-insensitive.
@alectimison-maker
alectimison-maker force-pushed the fix/openai-router-reasoning-contract branch from d57d4ad to a5e459f Compare August 16, 2026 13:02
@alectimison-maker

Copy link
Copy Markdown
Contributor Author

Reworked per review. The trailing boundary was the key miss — fixed and expanded:

  • Bounded pattern (?:^|\/)(?:gpt-5|o1|o3|o4)(?:$|[-_.\/])openai/gpt-4.1, gpt-4.1, openai/gpt-4o, and look-alikes like o365-assistant now stay on the legacy contract. gpt-4.1 is deliberately excluded from the new-contract family (it accepts both parameter sets), so explicit temperatures on the deterministic planner/compaction paths are preserved.
  • Single shared source — the predicate now lives in provider-compatibility.js as isNewOpenAIContractModel, used by both the provider and automaticTokenField in Settings, so the Compatibility panel and the wire contract can no longer drift (this also removes the duplicated regex you flagged).
  • Case-insensitive LM Studio guardString(providerName).toLowerCase() === lmstudio, so a duplicated config with providerName: LMStudio gets the legacy contract.

The previously-edited max_tokens assertion for openai/gpt-5.6-terra on OpenRouter remains correct (that id is still in the new-contract family), and I restored the doc comment about OpenRouter's legacy behavior where it still applies.

@webbrain-one webbrain-one left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One routed-model contract gap remains after bounding the regex: active GPT-5 Pro slugs on OpenRouter do not all advertise max_completion_tokens.

*/
export function isNewOpenAIContractModel(model) {
const m = String(model || '').toLowerCase();
return /(?:^|\/)(?:gpt-5|o1|o3|o4)(?:$|[-_.\/])/.test(m);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Do not classify the entire routed GPT-5 namespace as max_completion_tokens

The trailing delimiter prevents look-alike matches, but it still treats every GPT-5 suffix as the same wire contract. For example, this returns true for openai/gpt-5.5-pro (and openai/gpt-5.2-pro). OpenRouter's current model metadata and model page advertise max_tokens, but not max_completion_tokens, for GPT-5.5 Pro: https://openrouter.ai/openai/gpt-5.5-pro/api

Because OpenRouter stays on Chat Completions here, this branch sends max_completion_tokens and drops max_tokens; the configured output cap can therefore be ignored or rejected. The shared settings helper also reports the same incorrect automatic field.

Please classify the actual routed model families instead of the whole gpt-5 prefix (or rely on an explicit provider compatibility choice), and add regression coverage for at least openai/gpt-5.5-pro and openai/gpt-5.2-pro alongside the positive openai/gpt-5.6-terra case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch now includes the maintainer-directed fix 3292cd7f (preserved through the non-rewriting merge f1df7bdf). Routed GPT-5 Pro families (openai/gpt-5.5-pro, openai/gpt-5.2-pro, including dated/batch suffixes) remain on max_tokens; the positive openai/gpt-5.6-terra case remains on max_completion_tokens. The Chrome/Firefox shared helper and regression table cover both cases, while direct OpenAI Responses routing remains unchanged. Verified after the merge: node test/run.js 1772 passed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up d9000664 also closes the final review-pass finding: provider compatibility is now config-aware. The shared isNewOpenAIContractConfig keeps local/LM Studio and non-OpenRouter slash-prefixed model ids on legacy fields, while OpenRouter retains the maintainer-approved Pro exceptions and Terra/o-series behavior. Both provider request construction and Settings call this shared predicate, with Chrome/Firefox regression coverage. node test/run.js: 1773 passed.

@webbrain-one

Copy link
Copy Markdown
Owner

Blocking compatibility finding from a second pass against OpenRouter's current model metadata:

isNewOpenAIContractModel() still classifies routed families too broadly. It returns true for router-prefixed o-series IDs such as openai/o1 and openai/o3-mini, but OpenRouter currently advertises max_tokens—not max_completion_tokens—for those models. The same problem affects multiple :batch, image, and GPT-5 Pro variants. Examples include openai/gpt-5.5-pro and openai/gpt-5.2-pro.

Sources:

This is a regression for existing OpenRouter configurations: before this PR, those router-prefixed IDs stayed on max_tokens; now the configured cap may be ignored or rejected, and temperature may also be omitted. The current green tests encode the broad matcher, so they do not protect the live provider contract.

Please do not merge as-is. Classify actual provider/model capabilities (or require an explicit compatibility selection), and add negative regression cases for at least openai/o1, openai/o3-mini, openai/gpt-5.5-pro, openai/gpt-5.2-pro, plus representative batch/image IDs.

@alectimison-maker

Copy link
Copy Markdown
Contributor Author

Addressed the latest OpenRouter compatibility blocker in 0ac69371:

  • OpenRouter now uses a narrow GPT-5.6 Terra allowlist for max_completion_tokens.
  • Routed openai/o1, openai/o3-mini, openai/o4-mini:image, GPT-5 Pro, batch, and image variants remain on max_tokens with temperature preserved.
  • Chrome, Firefox, provider request construction, and Settings share the same provider-aware predicate.
  • Regression coverage now includes Terra positive variants and the requested o-series/Pro/batch/image negatives.

Verification: node test/run.js — 1773 passed.

@esokullu
esokullu merged commit 5ec4832 into webbrain-one:main Aug 17, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants