fix(#5989): keep colon-suffixed proxy models distinct

This commit is contained in:
Rod Boev authored and nesquena-hermes committed 2026-07-13 18:57:15 +00:00
1 parent ff89f2ebd6
commit 3ada146d48
2 files changed
+54 -23

No files matched your search

+38 -23
View File
@@ -2732,12 +2732,20 @@ function _providerFromModelValue(modelId){
if(value.startsWith('@')&&value.includes(':')) return value.slice(1,value.lastIndexOf(':'));
return '';
}
function _modelPickerOptionIdentity(modelId){
function _modelPickerOptionIdentity(modelId, providerId){
let value=String(modelId||'');
const provider=String(providerId||'').trim();
if(value.startsWith('@')&&value.includes(':')){
value=value.startsWith('@custom:')
? value.substring(value.lastIndexOf(':')+1)
: value.substring(value.indexOf(':')+1);
const exactPrefix=provider ? `@${provider}:` : '';
if(exactPrefix && value.toLowerCase().startsWith(exactPrefix.toLowerCase())){
value=value.substring(exactPrefix.length);
}else if(value.startsWith('@custom:')){
const namedProvider=value.substring('@custom:'.length);
const splitAt=namedProvider.indexOf(':');
value=splitAt>=0 ? namedProvider.substring(splitAt+1) : namedProvider;
}else{
value=value.substring(value.indexOf(':')+1);
}
}
return value.replace(/-/g,'.').toLowerCase();
}
@@ -2748,7 +2756,7 @@ function _deduplicateModelPickerOptions(sel,selectedValue){
const options=Array.from(group.children||[]).filter(opt=>opt&&opt.tagName==='OPTION');
const byIdentity=new Map();
for(const opt of options){
const identity=_modelPickerOptionIdentity(opt.value);
const identity=_modelPickerOptionIdentity(opt.value,_getOptionProviderId(opt));
if(!identity) continue;
if(!byIdentity.has(identity)) byIdentity.set(identity,[]);
byIdentity.get(identity).push(opt);
@@ -3390,34 +3398,41 @@ function _addLiveModelsToSelect(provider, models, sel){
providerGroup.dataset.provider=provider;
}
const existingIds=new Set([...sel.options].map(o=>o.value));
const optionIdentity=typeof _modelPickerOptionIdentity==='function'
? _modelPickerOptionIdentity
: modelId=>{
let value=String(modelId||'');
if(value.startsWith('@')&&value.includes(':')){
value=value.startsWith('@custom:')
? value.substring(value.lastIndexOf(':')+1)
: value.substring(value.indexOf(':')+1);
}
return value.split('/').pop().replace(/-/g,'.').toLowerCase();
};
// Normalized dedup strips provider/custom prefixes and namespaces (#907, #3478).
const existingNorm=new Set([...sel.options].map(o=>optionIdentity(o.value)));
const hasExistingNorm=identity=>existingNorm.has(identity);
let added=0;
const _ap=(window._activeProvider||'').toLowerCase();
const _providerLower=String(provider||'').toLowerCase();
const _isNamedCustomActiveProvider=_ap.startsWith('custom:');
const _isPortalFetch=_ap && _ap!=='openrouter' && _ap!=='custom' && _ap!=='openai-codex' && (_providerLower===_ap||_isNamedCustomActiveProvider&&_providerLower===_ap);
// Keep existingNorm.has( within the #907 source slice.
const optionIdentity=typeof _modelPickerOptionIdentity==='function'
? (modelId,providerId)=>_modelPickerOptionIdentity(modelId,providerId)
: (modelId,providerId)=>{
let value=String(modelId||'');
const provider=String(providerId||'').trim();
if(value.startsWith('@')&&value.includes(':')){
const exactPrefix=provider ? `@${provider}:` : '';
if(exactPrefix && value.toLowerCase().startsWith(exactPrefix.toLowerCase())){
value=value.substring(exactPrefix.length);
}else if(value.startsWith('@custom:')){
const namedProvider=value.substring('@custom:'.length);
const splitAt=namedProvider.indexOf(':');
value=splitAt>=0 ? namedProvider.substring(splitAt+1) : namedProvider;
}else{
value=value.substring(value.indexOf(':')+1);
}
}
return value.split('/').pop().replace(/-/g,'.').toLowerCase();
};
const existingNorm=new Set([...sel.options].map(o=>optionIdentity(o.value,_getOptionProviderId(o))));
let added=0;
for(const m of models){
let mid=m.id;
if(_isPortalFetch && !mid.startsWith('@')){
mid=`@${provider}:${mid}`;
}
if(existingIds.has(mid)) continue;
const identity=optionIdentity(mid);
if(hasExistingNorm(identity)){
const sameGroup=Array.from(providerGroup.children||[]).find(o=>optionIdentity(o.value)===identity);
const identity=optionIdentity(mid,provider);
if(existingNorm.has(identity)){
const sameGroup=Array.from(providerGroup.children||[]).find(o=>optionIdentity(o.value,_getOptionProviderId(o))===identity);
if(sameGroup){
const incomingRoutable=String(mid).startsWith('@');
const existingRoutable=String(sameGroup.value||'').startsWith('@');
@@ -26,10 +26,12 @@ def _function(source, name):
def _run_harness():
provider_helper = _function(UI_JS, "_getOptionProviderId")
identity = _function(UI_JS, "_modelPickerOptionIdentity")
helper = _function(UI_JS, "_deduplicateModelPickerOptions")
live_add = _function(UI_JS, "_addLiveModelsToSelect")
script = f"""
{provider_helper}
{identity}
{helper}
{live_add}
@@ -87,6 +89,9 @@ const sameSuffix=makeSelect();
addCatalog(sameSuffix,'vendor-a/deepseek-v4-pro');
addCatalog(sameSuffix,'vendor-b/catalog/deepseek-v4-pro');
sameSuffix.value='vendor-a/deepseek-v4-pro';
const sameGroupColonSuffix=makeSelect();
_addLiveModelsToSelect('custom:llm-proxy',[{{id:'@custom:llm-proxy:deepseek/deepseek-r1:free',label:'DeepSeek R1 Free'}}],sameGroupColonSuffix);
_addLiveModelsToSelect('custom:llm-proxy',[{{id:'@custom:llm-proxy:meta-llama/llama-3.3:free',label:'Llama 3.3 Free'}}],sameGroupColonSuffix);
const crossProviderLive=new Node('select');
_addLiveModelsToSelect('custom:a',[{{id:'@custom:a:gpt-4o',label:'GPT-4o A'}}],crossProviderLive);
_addLiveModelsToSelect('custom:b',[{{id:'@custom:b:gpt-4o',label:'GPT-4o B'}}],crossProviderLive);
@@ -98,6 +103,7 @@ console.log(JSON.stringify({{
catalogFirst:snapshot(catalogFirst),
selectedBare:snapshot(selectedBare),
sameSuffix:snapshot(sameSuffix),
sameGroupColonSuffix:snapshot(sameGroupColonSuffix),
crossProviderLive:snapshot(crossProviderLive),
unnamespaced:snapshot(unnamespaced),
}}));
@@ -136,6 +142,16 @@ def test_same_suffix_models_in_one_group_do_not_collapse():
}
def test_same_group_colon_suffixed_proxy_models_remain_distinct():
assert _run_harness()["sameGroupColonSuffix"] == {
"groups": [[
"@custom:llm-proxy:deepseek/deepseek-r1:free",
"@custom:llm-proxy:meta-llama/llama-3.3:free",
]],
"selected": "@custom:llm-proxy:deepseek/deepseek-r1:free",
}
def test_live_models_with_same_identity_survive_in_different_provider_groups():
assert _run_harness()["crossProviderLive"] == {
"groups": [["@custom:a:gpt-4o"], ["@custom:b:gpt-4o"]],