refactor(model-routing): centralize explicit foreground fallback policy (#6020)

* refactor(model-routing): centralize explicit foreground fallback policy

Make foreground fallback an explicit per-user, availability-only policy shared by streaming Chat, non-stream Chat, and Agent runs.

Preserve strict defaults, owner/model and credential boundaries, pinned Agent routes, and truthful per-round provenance/accounting. Carry provider-reported model identifiers through native streaming adapters, non-stream responses, and caches, and keep legacy default_model_fallbacks as tombstoned raw storage that generic settings APIs and agent tools cannot expose or mutate.

* fix(agent-loop): restore rebase-dropped qwen routing, workspace prompt, and temperature clamp

* fix(model-routing): thread selected endpoint identity, fix cost classification and fallback eligibility

* fix(chat): restore stream helpers and harden run stop lifecycle

* fix(model-routing): let numeric provider codes win over symbolic rate-limit statuses

* fix(agent-loop): apply qwen temperature and notes-tool clamps per fallback candidate

* fix(chat): honor queued stop across resend and reload canonical terminal on EOF

* fix(chat): track stop queue and cleanup ownership by per-send generation

* fix(agent-loop): preserve requested temperature for non-qwen fallback candidates

* fix(chat): reserve send ownership before any await and scope stop to the current send

* fix(chat): clear the previous run identity at send reservation

---------

Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com>
Co-authored-by: StressTestor <212606152+StressTestor@users.noreply.github.com>
This commit is contained in:
Alexandre Teixeira
2026-08-14 08:10:30 +01:00
committed by GitHub
co-authored by RaresKeY StressTestor
parent b52296471b
commit c4369305f0
46 changed files with 11418 additions and 1006 deletions
-73
View File
@@ -445,14 +445,7 @@ async function initDefaultChat() {
var epSel = el('set-defaultEpSelect');
var modelSel = el('set-defaultModelSelect');
var msg = el('set-defaultChatMsg');
var fbContainer = el('set-defaultFallbacks');
var addFbBtn = el('set-defaultAddFallback');
var _endpoints = [];
var _fallbacks = []; // Hidden legacy DOM hook; stored values are not loaded or saved.
function enabledEndpoints() {
return _endpoints.filter(function(e) { return e.is_enabled; });
}
// Fill any <select> with the models for a given endpoint id.
function fillModels(selectEl, epId, selected) {
@@ -469,64 +462,6 @@ async function initDefaultChat() {
function refreshEndpointOptions(selectedEndpoint, selectedModel) {
_fillEndpointSelect(epSel, _endpoints, selectedEndpoint !== undefined ? selectedEndpoint : epSel.value, false);
refreshModels(selectedModel !== undefined ? selectedModel : modelSel.value);
renderFallbacks();
}
// Render the fallback chain. Each row is endpoint + model + remove.
function renderFallbacks() {
fbContainer.innerHTML = '';
_fallbacks.forEach(function(fb, idx) {
var row = document.createElement('div');
row.className = 'settings-fallback-row';
var num = document.createElement('span');
num.className = 'settings-fallback-num';
num.textContent = (idx + 1) + '.';
var epS = document.createElement('select');
epS.className = 'settings-select';
enabledEndpoints().forEach(function(ep) {
var o = document.createElement('option');
o.value = ep.id;
o.textContent = ep.name + (ep.online ? '' : ' (offline)');
epS.appendChild(o);
});
var first = enabledEndpoints()[0];
epS.value = fb.endpoint_id || (first ? first.id : '');
var mS = document.createElement('select');
mS.className = 'settings-select';
fillModels(mS, epS.value, fb.model);
// Keep the model in sync with the values actually shown.
fb.endpoint_id = epS.value;
fb.model = mS.value;
epS.addEventListener('change', function() {
fb.endpoint_id = epS.value;
fillModels(mS, epS.value, '');
fb.model = mS.value;
saveDefault();
});
mS.addEventListener('change', function() { fb.model = mS.value; saveDefault(); });
var rm = document.createElement('button');
rm.type = 'button';
rm.className = 'settings-fallback-remove';
rm.title = 'Remove fallback';
rm.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>';
rm.addEventListener('click', function() {
_fallbacks.splice(idx, 1);
renderFallbacks();
saveDefault();
});
row.appendChild(num);
row.appendChild(epS);
row.appendChild(mS);
row.appendChild(rm);
fbContainer.appendChild(row);
});
}
try {
@@ -534,7 +469,6 @@ async function initDefaultChat() {
var settings = await res.json();
if (settings.default_endpoint_id) epSel.value = settings.default_endpoint_id;
refreshModels(settings.default_model || '');
renderFallbacks();
} catch (e) { console.warn('Failed to load default chat settings', e); }
epSel.addEventListener('change', function() { refreshModels(''); saveDefault(); });
@@ -554,13 +488,6 @@ async function initDefaultChat() {
} catch (e) { msg.textContent = 'Failed to save'; msg.style.color = 'var(--red)'; }
}
if (addFbBtn) addFbBtn.addEventListener('click', function() {
var first = enabledEndpoints()[0];
_fallbacks.push({ endpoint_id: first ? first.id : '', model: '' });
renderFallbacks();
saveDefault();
});
_registerAiEndpointRefresh(function(endpoints) {
_endpoints = endpoints;
refreshEndpointOptions(epSel.value, modelSel.value);