Skip to content

fix(models): resolve openrouter compat aliases#68579

Merged
vincentkoc merged 4 commits into
mainfrom
fix/openrouter-alias-cleanup
Apr 18, 2026
Merged

fix(models): resolve openrouter compat aliases#68579
vincentkoc merged 4 commits into
mainfrom
fix/openrouter-alias-cleanup

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • Problem: openrouter:free and openrouter:auto fall through the providerless model path, so openrouter:free gets mis-resolved under the default provider and openrouter:auto is not treated as the canonical OpenRouter auto model.
  • Why it matters: users see nonsense like anthropic/openrouter:free and then hit unknown-model failures even though the issue is only the compatibility alias resolver.
  • What changed: parseModelRef now normalizes openrouter:auto to the canonical openrouter/auto ref, and configured-model resolution now maps openrouter:free to the first configured concrete openrouter/...:free model from allowlist/config instead of shoving it under the default provider.
  • What did NOT change (scope boundary): this PR does not hardcode a specific vendor free model, and it does not touch OpenRouter baseUrl repair or reasoning_details parsing.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: providerless model parsing only split on /, so OpenRouter compatibility aliases using : never got their own path and were treated like bare model names under the default provider.
  • Missing detection / guardrail: there was no regression coverage for OpenRouter compatibility aliases in either generic parsing or configured-model resolution.
  • Contributing context (if known): a previous attempt hardcoded openrouter:free to one vendor model, but that is the wrong architectural shape because the right free target depends on user config.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/model-selection.test.ts
  • Scenario the test should lock in: openrouter:auto resolves canonically, and openrouter:free resolves to a configured concrete free OpenRouter model instead of the default provider.
  • Why this is the smallest reliable guardrail: the bug is entirely in model parsing and configured-model resolution.
  • Existing test that already covers this (if any): none for these aliases before this PR.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • openrouter:auto now resolves to openrouter/auto.
  • openrouter:free now resolves to the first configured concrete openrouter/...:free model when one exists.
  • OpenClaw no longer rewrites those aliases under the default provider.

Diagram (if applicable)

Before:
openrouter:free -> providerless fallback -> anthropic/openrouter:free -> unknown model

After:
openrouter:free -> configured OpenRouter free model lookup -> openrouter/<vendor>/<model>:free

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local dev worktree
  • Model/provider: OpenRouter model selection
  • Integration/channel (if any): N/A
  • Relevant config (redacted): agent default model set to openrouter:auto or openrouter:free

Steps

  1. Configure an agent default model as openrouter:auto or openrouter:free.
  2. Include at least one concrete openrouter/...:free model in the allowlist or models.providers.openrouter.models for the free case.
  3. Resolve the configured model ref.

Expected

  • openrouter:auto resolves to openrouter/auto.
  • openrouter:free resolves to a concrete configured OpenRouter free model.

Actual

  • The aliases were treated as bare providerless strings and could fall back to the default provider.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: ran pnpm test:serial src/agents/model-selection.test.ts and confirmed the new parse case plus both configured resolution paths.
  • Edge cases checked: openrouter:auto; openrouter:free via allowlist config; openrouter:free via models.providers.openrouter.models when allowlist is absent.
  • What you did not verify: live OpenRouter execution against a real account.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: openrouter:free could resolve to an unexpected free model when multiple concrete free models are configured.
    • Mitigation: the behavior is deterministic and intentionally follows first configured model order; it avoids hardcoded vendor bias and keeps the alias tied to the user’s own config.

@vincentkoc vincentkoc self-assigned this Apr 18, 2026
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels Apr 18, 2026
@vincentkoc vincentkoc marked this pull request as ready for review April 18, 2026 13:38
@greptile-apps

greptile-apps Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the mis-resolution of openrouter:auto and openrouter:free compatibility aliases. parseModelRef now intercepts openrouter:auto before the slash-split logic and returns the canonical openrouter/auto ref; configured model resolution adds resolveConfiguredOpenRouterCompatAlias to also handle openrouter:free by scanning the configured allowlist and provider models for a concrete :free model. Tests are added for both paths and the CHANGELOG is updated.

Confidence Score: 5/5

Safe to merge — core fix is correct and well-tested; all remaining findings are style/cleanup suggestions.

The fix correctly handles both aliases: openrouter:auto is resolved globally via parseModelRef, and openrouter:free is resolved in configured-model paths. Three new test cases pin the regression. No P0/P1 issues found.

CHANGELOG.md (wrong version block + mid-section insertion); src/agents/model-selection.ts (minor indirection and known gap in interactive model-switch path for openrouter:free).

Comments Outside Diff (1)

  1. src/agents/model-selection.ts, line 783-834 (link)

    P2 openrouter:free still mis-resolves through the interactive model-switch path

    resolveAllowedModelRef (used for runtime model switching) calls resolveModelRefFromStringparseModelRef. Because parseModelRef only special-cases openrouter:auto, a user who types openrouter:free in the model picker will still get {provider: <defaultProvider>, model: "openrouter:free"} — the same wrong behavior the PR fixes for configured defaults.

    openrouter:auto is fully fixed in all paths because parseModelRef handles it globally; openrouter:free remains a configured-default-only fix. Worth tracking as a follow-up if users are expected to use the alias interactively.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/model-selection.ts
    Line: 783-834
    
    Comment:
    **`openrouter:free` still mis-resolves through the interactive model-switch path**
    
    `resolveAllowedModelRef` (used for runtime model switching) calls `resolveModelRefFromString``parseModelRef`. Because `parseModelRef` only special-cases `openrouter:auto`, a user who types `openrouter:free` in the model picker will still get `{provider: <defaultProvider>, model: "openrouter:free"}` — the same wrong behavior the PR fixes for configured defaults.
    
    `openrouter:auto` is fully fixed in all paths because `parseModelRef` handles it globally; `openrouter:free` remains a configured-default-only fix. Worth tracking as a follow-up if users are expected to use the alias interactively.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: CHANGELOG.md
Line: 121

Comment:
**CHANGELOG placement: wrong section and mid-block insertion**

The new entry lands inside the `## 2026.4.15` block (a past release, cut April 15) rather than in the `## Unreleased` section at the top, and it was inserted at line 121 — in the middle of that section's `### Fixes` list — rather than appended at the end. Per `CLAUDE.md`: *"in the active version block, append new entries to the **end** of the target section; do not insert new entries at the top of a section."*

Move the line to `## Unreleased → ### Fixes` and append it after the last existing entry in that block.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/model-selection.ts
Line: 293-313

Comment:
**Redundant `parseModelRef` round-trip for `openrouter:auto`**

When `normalized === "openrouter:auto"`, this function calls `parseModelRef("openrouter:auto", ...)` which immediately re-hits the `OPENROUTER_AUTO_COMPAT_ALIAS` guard in `model-selection-normalize.ts` and calls `normalizeModelRef("openrouter", "auto", opts)` anyway. The double dispatch is harmless but adds a subtle indirection that makes the intent harder to read.

```suggestion
  if (normalized === "openrouter:auto") {
    return normalizeModelRef("openrouter", "auto", {
      allowPluginNormalization: params.allowPluginNormalization,
    });
  }
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/model-selection.ts
Line: 783-834

Comment:
**`openrouter:free` still mis-resolves through the interactive model-switch path**

`resolveAllowedModelRef` (used for runtime model switching) calls `resolveModelRefFromString``parseModelRef`. Because `parseModelRef` only special-cases `openrouter:auto`, a user who types `openrouter:free` in the model picker will still get `{provider: <defaultProvider>, model: "openrouter:free"}` — the same wrong behavior the PR fixes for configured defaults.

`openrouter:auto` is fully fixed in all paths because `parseModelRef` handles it globally; `openrouter:free` remains a configured-default-only fix. Worth tracking as a follow-up if users are expected to use the alias interactively.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(models): resolve openrouter compat a..." | Re-trigger Greptile

Comment thread CHANGELOG.md Outdated
Comment thread src/agents/model-selection.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3750120ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/model-selection.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd6a90a972

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/model-selection.ts
@vincentkoc vincentkoc force-pushed the fix/openrouter-alias-cleanup branch from 3c65036 to adc0915 Compare April 18, 2026 15:12
@vincentkoc vincentkoc force-pushed the fix/openrouter-alias-cleanup branch from adc0915 to b06e8d3 Compare April 18, 2026 15:19
@vincentkoc vincentkoc merged commit d13869a into main Apr 18, 2026
49 checks passed
@vincentkoc vincentkoc deleted the fix/openrouter-alias-cleanup branch April 18, 2026 15:24

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b06e8d3e64

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +853 to +857
const openrouterCompatRef = resolveConfiguredOpenRouterCompatAlias({
cfg: params.cfg,
raw: trimmed,
defaultProvider: params.defaultProvider,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve alias precedence for OpenRouter compat aliases

resolveAllowedModelRef now short-circuits through resolveConfiguredOpenRouterCompatAlias before alias lookup, so a user-defined alias named openrouter:free/openrouter:auto is ignored and resolves to the first configured compat target instead. This is a regression from prior behavior and also diverges from model-selection-resolve.ts, where resolveModelRefFromString still checks aliasIndex first; in practice the same config can resolve to different models depending on which resolver path is used.

Useful? React with 👍 / 👎.

ender-wiggin-ai pushed a commit to stroupaloop/openclaw that referenced this pull request Apr 18, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
Mquarmoc pushed a commit to Mquarmoc/openclaw that referenced this pull request Apr 20, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenRouter compatibility aliases: openrouter:free resolves to anthropic/openrouter:free; openrouter:auto should also be handled explicitly

1 participant