fix: preserve cross-provider model selection across conversation turns

Two-part fix for the model selector jumping back to the profile default
after the first message when a cross-provider model (e.g. a custom relay
like custom:glm-free-relay) is selected.

Backend (api/routes.py):
- _catalog_has_provider() now matches compound provider IDs (e.g.
  custom:zenmux-relay) whose normalized form (custom) is a raw
  catalog provider_id.  Previously the three existing checks missed
  normalized-vs-raw comparison for compound hints, causing the catalog
  lookup to fail and the fallback branch to silently revert the model
  to the profile default on every non-explicit turn.

Frontend (static/messages.js):
- The explicit_model_pick marker was consumed after the first send(),
  so subsequent sends lost the signal and the server repaired the
  model back to the profile default (#3737 regression).
- Now, when the session has a non-default model from a different
  provider than the profile's active provider, every send() sends
  explicit_model_pick=true so the server honors the user's choice
  across the entire conversation.
- The pending onchange marker is still consumed after the first send
  (unchanged behavior); only the explicit_pick inference is broadened.
This commit is contained in:
jja881 authored and nesquena-hermes committed 2026-06-20 18:44:25 +00:00
1 parent c6ac31b71e
commit 96265f6ae4
2 files changed
+28 -2

No files matched your search

+12
View File
@@ -3196,6 +3196,18 @@ def _catalog_has_provider(
provider_raw in raw_provider_ids
or (provider_normalized and provider_normalized in raw_provider_ids)
or (provider_normalized and provider_normalized in normalized_provider_ids)
# Compound provider IDs like "custom:zenmux-relay" normalize to
# "custom" which is a raw catalog provider_id. The three checks
# above compare raw-vs-raw and normalized-vs-normalized but miss
# normalized-vs-raw for compound hints. Without this, a
# @custom:relay-name:model selection is silently reverted to the
# profile default on every non-explicit turn because the catalog
# lookup fails and the fallback branch at line ~3932 fires.
or (
provider_normalized
and ":" in provider_raw
and provider_normalized in raw_provider_ids
)
)
+16 -2
View File
@@ -1267,15 +1267,29 @@ async function send(){
const _pendingPick=(typeof _readPendingSessionModel==='function')
? _readPendingSessionModel(activeSid)
: null;
const _explicitPick=_pendingPick
const _pendingPickMatch=_pendingPick
&& _pendingPick.model===_modelState.model
&& String(_pendingPick.model_provider||'')===String(_modelState.model_provider||'');
// ── Persisted cross-provider pick (#3737 follow-up) ──
// The onchange marker is consumed after the first send, so subsequent sends
// lose explicit_model_pick and the server "repairs" the model back to the
// profile default. When the session has a non-default model from a different
// provider than the profile's active provider, treat every send as explicit
// so the server honors the user's choice across the entire conversation.
const _defaultModel=(typeof window!=='undefined' && window._defaultModel)||'';
const _activeProvider=(typeof window!=='undefined' && window._activeProvider)||null;
const _isCrossProviderPick = _modelState.model
&& _modelState.model_provider
&& _defaultModel
&& _modelState.model !== _defaultModel
&& String(_modelState.model_provider||'') !== String(_activeProvider||'');
const _explicitPick = _pendingPickMatch || _isCrossProviderPick;
// Consume the pending explicit-pick marker for THIS send only. The marker is
// recorded on modelSelect.onchange and intentionally kept (not cleared on
// session-update) so it survives the normal pick→update→send flow; clear it here
// once read so a later send of an unchanged dropdown isn't treated as an explicit
// pick. (#3739/#3737, Codex catch)
if(_explicitPick && typeof _clearPendingSessionModel==='function') _clearPendingSessionModel(activeSid);
if(_pendingPickMatch && typeof _clearPendingSessionModel==='function') _clearPendingSessionModel(activeSid);
explicitPickForPostStart=_explicitPick;
const startData=await api('/api/chat/start',{method:'POST',body:JSON.stringify({
session_id:activeSid,message:msgText,