fix #84887: avoid duplicate provider prefix in runtime LLM allowlist diagnostics#84946
Conversation
…stics
normalizeAllowedModelRef() and the resolved override ref interpolated
${provider}/${model} after normalizeModelRef(), so a provider-qualified
model id like openrouter/gpt-5.4-mini surfaced as
openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy
denial message, masking the actionable model ref.
Route both sites through modelKey() (src/agents/model-ref-shared.ts)
so the provider segment is collapsed when the model id already starts
with it. Add regression tests covering allowlist hit and denial paths
for the OpenRouter shape.
Fixes openclaw#84887
|
Codex review: needs maintainer review before merge. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. Current main has a clear source-level reproduction path: provider PR rating Rank-up moves:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. Real behavior proof Risk before merge
Maintainer options:
Next step before merge Security Review detailsBest possible solution: Land this narrow runtime LLM formatter fix after normal CI/maintainer review, then close the linked issue; handle gateway fallback normalization separately or by choosing #84940 if maintainers want the broader scope. Do we have a high-confidence way to reproduce the issue? Yes. Current main has a clear source-level reproduction path: provider Is this the best way to solve the issue? Yes. Reusing existing Label changes:
Label justifications:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 6ccca4ae9529. |
|
ClawSweeper PR egg ✨ Hatched: 🌱 uncommon Pearl Diff Drake Hatch commandComment Hatchability rules:
Rarity: 🌱 uncommon. What is this egg doing here?
|
|
Maintainer verification before merge:
Known proof gap: I did not run a fresh local |
|
@vincentkoc FYI following up on the broader gateway fallback piece noted in your merge comment as out of scope for this narrow runtime LLM merge: I opened #85445 for that separate path. Facts checked there:
ClawSweeper has now accepted the production-source gateway fallback smoke proof on #85445 ( |
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
…stics (openclaw#84946) normalizeAllowedModelRef() and the resolved override ref interpolated ${provider}/${model} after normalizeModelRef(), so a provider-qualified model id like openrouter/gpt-5.4-mini surfaced as openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy denial message, masking the actionable model ref. Route both sites through modelKey() (src/agents/model-ref-shared.ts) so the provider segment is collapsed when the model id already starts with it. Add regression tests covering allowlist hit and denial paths for the OpenRouter shape. Fixes openclaw#84887
Summary
api.runtime.llm.completeresolves a selection whose modelId already starts with the provider segment (e.g. OpenRouter resolvesprovider="openrouter",modelId="openrouter/gpt-5.4-mini"), the runtime LLM policy path interpolated${provider}/${model}afternormalizeModelRef(), producingopenrouter/openrouter/gpt-5.4-mini. The malformed ref both missed the user's allowlist and showed up in the denial diagnostic, sending plugin authors chasing the wrong allowlist key.modelKey()helper, which leaves the model id untouched when it already starts with the provider segment.src/plugins/runtime/runtime-llm.runtime.ts(import + two interpolation sites); regression coverage added insrc/plugins/runtime/runtime-llm.runtime.test.ts.normalizeModelRef()semantics, the parallelnormalizeAllowedModelRefinsrc/gateway/server-plugins.ts(subagent fallback policy), and any other model-ref formatter — kept the change scoped to the runtime LLM policy path the issue narrowed.Fixes #84887
Real behavior proof
openrouter/openrouter/gpt-5.4-minifor an actionableopenrouter/gpt-5.4-miniselection, leading users to add the wrong allowlist entries.proof_repro.tsimports the real source helpers and exercises the exact normalization path the runtime LLM policy uses:openrouter/gpt-5.4-mini, hits the user's allowlist, and the denial diagnostic (when the model is genuinely outside the allowlist) reports"openrouter/gpt-5.5"instead of"openrouter/openrouter/gpt-5.5". The bare-id and prefix-mismatch shapes are unchanged.Regression Test Plan
src/plugins/runtime/runtime-llm.runtime.test.tsprovider="openrouter",modelId="openrouter/gpt-5.4-mini") — one case asserts the allowlist hit no longer doubles the prefix; a second case asserts the denial message contains"openrouter/gpt-5.5"and neveropenrouter/openrouter/.Root Cause
normalizeAllowedModelRef()and the resolved-override formatter both rebuilt a model ref via${normalized.provider}/${normalized.model}afternormalizeModelRef(), andnormalizeModelRef()does not collapse a provider segment that the modelId already carries (its job is to normalize provider/model ids, not to dedupe the prefix). The allowlist Set therefore storedopenrouter/openrouter/gpt-5.4-miniwhile the user-facing actionable ref wasopenrouter/gpt-5.4-mini.openai-codex/gpt-5.4-mini. The new tests close that gap by pinning the OpenRouter shape on both the hit and denial paths.