Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 58 additions & 11 deletions static/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -2852,7 +2852,18 @@ function _modelStateForSelect(sel, modelId){
const value=String(modelId||'').trim();
if(!value) return {model:'',model_provider:null};
const explicitProvider=_providerFromModelValue(value);
if(explicitProvider) return {model:value,model_provider:explicitProvider};
if(explicitProvider){
const selected=sel&&sel.options
?Array.from(sel.options).find(o=>String(o.value||'')===value)
:null;
const routedModel=selected&&selected.dataset&&selected.dataset.model;
// Read the provider from the matched option's authoritative data-provider
// rather than re-parsing the value at its LAST colon: a colon-bearing model
// id (e.g. model-a:free) synthesized as @custom:backup:model-a:free would
// otherwise mis-parse to provider "custom:backup:model-a" (#6221 re-gate).
const routedProvider=selected?String(_getOptionProviderId(selected)||'').trim():'';
return {model:routedModel||value,model_provider:routedProvider||explicitProvider};
}
// Resolve the provider from the option whose VALUE matches the requested
// model — never blindly from sel.selectedOptions[0] (#5567). During a profile
// /tab switch or a model-list rebuild the dropdown transiently still has the
Expand Down Expand Up @@ -3242,19 +3253,33 @@ function _applyModelToDropdown(modelId, sel, preferredProviderId, opts){
function _ensureModelOptionInDropdown(modelId, sel, preferredProviderId){
if(!modelId||!sel) return null;
if(typeof _deduplicateModelPickerOptions==='function') _deduplicateModelPickerOptions(sel,sel.value);
const applied=_applyModelToDropdown(modelId,sel,preferredProviderId);
if(applied) return applied;
const value=modelId;
const requestedProvider=String(preferredProviderId||_providerFromModelValue(modelId)||'').trim();
const applied=_applyModelToDropdown(modelId,sel,requestedProvider||null);
if(applied){
const appliedState=typeof _modelStateForSelect==='function'
?_modelStateForSelect(sel,applied)
:{model:applied,model_provider:null};
if(!requestedProvider||String(appliedState&&appliedState.model_provider||'').toLowerCase()===requestedProvider.toLowerCase()) return applied;
}
const explicitPrefix=requestedProvider?`@${requestedProvider}:`:'';
const rawModel=String(modelId||'');
const bareModel=explicitPrefix&&rawModel.toLowerCase().startsWith(explicitPrefix.toLowerCase())
?rawModel.slice(explicitPrefix.length)
:rawModel;
const value=requestedProvider?`${explicitPrefix}${bareModel}`:rawModel;
const opt=document.createElement('option');
opt.value=modelId;
opt.value=value;
opt.textContent=typeof getModelLabel==='function'?getModelLabel(modelId):modelId;
opt.dataset.custom='1';
const badge=(window._configuredModelBadges||{})[value];
const rawBadge=(window._configuredModelBadges||{})[rawModel];
if(badge&&badge.provider) opt.dataset.provider=badge.provider;
const provider=preferredProviderId||(badge&&badge.provider)||_providerFromModelValue(modelId)||'';
if(rawBadge&&rawBadge.provider) opt.dataset.provider=rawBadge.provider;
Comment on lines 3276 to +3277

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Dead dataset.provider assignments always overwritten

The two opt.dataset.provider writes on these lines are unconditionally overwritten four lines later by if(provider) opt.dataset.provider=provider;. provider is computed as requestedProvider||(badge&&badge.provider)||(rawBadge&&rawBadge.provider)||..., so it can never be falsy while either badge.provider or rawBadge.provider is truthy — the exact conditions that guard lines 3276–3277. Readers may expect the intermediate assignments to survive in some case, but they never do.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

if(requestedProvider) opt.dataset.model=bareModel;
const provider=requestedProvider||(badge&&badge.provider)||(rawBadge&&rawBadge.provider)||_providerFromModelValue(value)||'';
if(provider) opt.dataset.provider=provider;
sel.appendChild(opt);
sel.value=modelId;
sel.value=value;
if(sel.id==='modelSelect'){
if(typeof syncModelChip==='function') syncModelChip();
_refreshOpenModelDropdown();
Expand All @@ -3263,7 +3288,7 @@ function _ensureModelOptionInDropdown(modelId, sel, preferredProviderId){
if(typeof syncSettingsModelChip==='function') syncSettingsModelChip();
_refreshOpenModelDropdown();
}
return modelId;
return value;
}
function _modelStateFromAppliedDropdown(sel, modelValue){
const state=(typeof _modelStateForSelect==='function')
Expand Down Expand Up @@ -3646,6 +3671,30 @@ function _normalizeConfiguredModelKey(modelId){
return s.replace(/-/g,'.');
}

function _isEquivalentConfiguredModelEntry(modelId,badge,entries){
const normalized=_normalizeConfiguredModelKey(modelId);
const provider=String(badge&&badge.provider||'').toLowerCase();
const matchingEntries=(entries||[]).filter(existing=>
_normalizeConfiguredModelKey(existing.value)===normalized
);
if(matchingEntries.some(existing=>{
const entryProvider=String(existing.providerId||'').toLowerCase();
return !provider||!entryProvider||entryProvider===provider;
})) return true;
// @provider:model is an equivalent routing spelling only when an existing
// picker row belongs to that same provider. This supports named custom
// providers (@custom:name:model) without collapsing matching model IDs from
// different providers.
const rawId=String(modelId||'');
const prefix=provider?`@${provider}:`:'';
if(!prefix||!rawId.toLowerCase().startsWith(prefix)) return false;
const routedId=rawId.slice(prefix.length);
return (entries||[]).some(entry=>
String(entry.providerId||'').toLowerCase()===provider
&&_normalizeConfiguredModelKey(entry.value)===_normalizeConfiguredModelKey(routedId)
);
}

function _getConfiguredModelBadge(modelId,badgeMap,providerId){
const map=badgeMap||window._configuredModelBadges||{};
if(!modelId||!map) return null;
Expand Down Expand Up @@ -4090,17 +4139,15 @@ function renderModelDropdown(){
_groupMeta.get(groupKey).modelCount++;
}
}
const _existingConfiguredKeys=new Set(_modelData.map(existing=>_normalizeConfiguredModelKey(existing.value)));
for(const [modelId,badge] of Object.entries(_badgeMap)){
if(_existingConfiguredKeys.has(_normalizeConfiguredModelKey(modelId))) continue;
if(_isEquivalentConfiguredModelEntry(modelId,badge,_modelData)) continue;
_modelData.push({
value:modelId,
name:esc(getModelLabel(modelId)),
id:esc(modelId),
group:'',
badge,
});
_existingConfiguredKeys.add(_normalizeConfiguredModelKey(modelId));
}
// Create search input FIRST before filterModels definition
const _scopeNote=document.createElement('div');
Expand Down
106 changes: 106 additions & 0 deletions tests/test_configured_model_picker_dedup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Regression coverage for duplicate configured model entries in the picker."""

from __future__ import annotations

import json
import shutil
import subprocess
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).parent.parent.resolve()
UI_JS_PATH = REPO_ROOT / "static" / "ui.js"
NODE = shutil.which("node")

pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH")


_DRIVER = r"""
const fs = require('fs');
const ui = fs.readFileSync(process.argv[2], 'utf8');
function extractFunc(name) {
const re = new RegExp('function\\s+' + name + '\\s*\\(');
const start = ui.search(re);
if (start < 0) throw new Error(name + ' not found');
let i = ui.indexOf('{', start); let depth = 1; i++;
while (depth > 0 && i < ui.length) {
if (ui[i] === '{') depth++;
else if (ui[i] === '}') depth--;
i++;
}
return ui.slice(start, i);
}
eval(extractFunc('_normalizeConfiguredModelKey'));
eval(extractFunc('_isEquivalentConfiguredModelEntry'));
const cases = JSON.parse(process.argv[3]);
const result = cases.map(c => _isEquivalentConfiguredModelEntry(c.modelId, c.badge, c.entries));
process.stdout.write(JSON.stringify(result));
"""


def _equivalent_cases(tmp_path, cases):
driver = tmp_path / "driver.js"
driver.write_text(_DRIVER, encoding="utf-8")
assert NODE is not None
result = subprocess.run(
[NODE, str(driver), str(UI_JS_PATH), json.dumps(cases)],
capture_output=True,
text=True,
timeout=30,
)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)


def test_picker_rows_preserve_provider_id_for_equivalence_check():
"""The synthesis loop must compare badge routes against real row providers."""
ui = UI_JS_PATH.read_text(encoding="utf-8")

assert "const providerId=child.dataset&&child.dataset.provider?child.dataset.provider:'';" in ui
assert "providerId,modelsEndpointError,badge:_getConfiguredModelBadge" in ui
assert "providerId,\n modelsEndpointError," in ui

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Whitespace-sensitive substring assertion will break on reformatting

assert "providerId,\n modelsEndpointError," in ui encodes a specific newline and exactly 10 spaces of indentation from the overflow-model push in renderModelDropdown. Any auto-formatter, minifier, or even an unrelated whitespace tweak in that block will silently turn this assertion into a false positive (the assert passes at import/read time but stops testing the intended invariant). The structural invariant — that overflow entries carry providerId — could be checked by the same Node driver used in the other tests rather than by substring scan.

assert "if(_isEquivalentConfiguredModelEntry(modelId,badge,_modelData)) continue;" in ui
assert "_existingConfiguredKeys" not in ui


def test_named_custom_provider_routing_id_does_not_duplicate_picker_row(tmp_path):
entries = [{"value": "model-a", "providerId": "custom:example"}]
results = _equivalent_cases(
tmp_path,
[
{
"modelId": "@custom:example:model-a",
"badge": {"provider": "custom:example"},
"entries": entries,
},
{
"modelId": "model-a",
"badge": {"provider": "custom:example"},
"entries": entries,
},
],
)

assert results == [True, True]


def test_same_model_id_from_another_provider_remains_distinct(tmp_path):
entries = [{"value": "model-a", "providerId": "custom:primary"}]
results = _equivalent_cases(
tmp_path,
[
{
"modelId": "@custom:backup:model-a",
"badge": {"provider": "custom:backup"},
"entries": entries,
},
{
"modelId": "model-a",
"badge": {"provider": "custom:backup"},
"entries": entries,
},
],
)

assert results == [False, False]
Loading
Loading