mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-29 11:32:19 +02:00
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:
co-authored by
RaresKeY
StressTestor
parent
b52296471b
commit
c4369305f0
@@ -510,7 +510,12 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
|
||||
# set/get/list/delete operate on the REAL app settings (the same store
|
||||
# the Settings panel writes), so changing a model / voice / search
|
||||
# engine / reminder channel from chat actually takes effect.
|
||||
from src.settings import load_settings, save_settings, DEFAULT_SETTINGS
|
||||
from src.settings import (
|
||||
DEFAULT_SETTINGS,
|
||||
RETIRED_SETTING_KEYS,
|
||||
load_settings,
|
||||
save_settings,
|
||||
)
|
||||
|
||||
# Secrets/credentials the agent must NOT write: kept read-only (masked)
|
||||
# so API keys never flow through chat. User sets these in the panel.
|
||||
@@ -562,6 +567,9 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
|
||||
return k2
|
||||
return _ALIASES_SET.get(k2, (k or "").strip())
|
||||
|
||||
def _is_managed_key(key):
|
||||
return key in DEFAULT_SETTINGS and key not in RETIRED_SETTING_KEYS
|
||||
|
||||
_ENUMS = {
|
||||
"image_quality": ["low", "medium", "high"],
|
||||
"reminder_channel": ["browser", "email", "ntfy", "webhook"],
|
||||
@@ -624,14 +632,18 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
|
||||
|
||||
if action == "list":
|
||||
s = load_settings()
|
||||
shown = {k: _mask(k, v) for k, v in s.items() if k in DEFAULT_SETTINGS and not isinstance(v, dict)}
|
||||
shown = {
|
||||
k: _mask(k, v)
|
||||
for k, v in s.items()
|
||||
if _is_managed_key(k) and not isinstance(v, dict)
|
||||
}
|
||||
return {"response": f"{len(shown)} settings (use get/set with a key)", "settings": shown, "exit_code": 0}
|
||||
|
||||
elif action == "get":
|
||||
key = _resolve(args.get("key", ""))
|
||||
if not key:
|
||||
return {"error": "key is required", "exit_code": 1}
|
||||
if key not in DEFAULT_SETTINGS:
|
||||
if not _is_managed_key(key):
|
||||
return {"error": f"Unknown setting '{args.get('key')}'. Use action='list' to see them.", "exit_code": 1}
|
||||
val = load_settings().get(key, DEFAULT_SETTINGS.get(key))
|
||||
return {"response": f"{key} = {_mask(key, val)}", "value": _mask(key, val), "exit_code": 0}
|
||||
@@ -642,11 +654,11 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
|
||||
if not raw:
|
||||
return {"error": "key is required", "exit_code": 1}
|
||||
key = _resolve(raw)
|
||||
if key not in DEFAULT_SETTINGS:
|
||||
if not _is_managed_key(key):
|
||||
return {"error": f"Unknown setting '{raw}'. Use action='list' to see available settings.", "exit_code": 1}
|
||||
if _is_secret(key):
|
||||
return {"response": f"'{key}' is a credential/secret. For security I can't set it from chat. Open Settings and set it there.", "exit_code": 0}
|
||||
# Structured settings (dicts/lists like keybinds, default_model_fallbacks)
|
||||
# Structured settings (dicts/lists like keybinds or vision fallbacks)
|
||||
# have no safe scalar coercion; _coerce would pass a bare string
|
||||
# straight through and clobber the structure. Refuse them here; they're
|
||||
# edited in their dedicated panels. (reset/delete still restore the
|
||||
@@ -675,7 +687,7 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
|
||||
|
||||
elif action == "delete" or action == "reset":
|
||||
key = _resolve(args.get("key", ""))
|
||||
if key not in DEFAULT_SETTINGS:
|
||||
if not _is_managed_key(key):
|
||||
return {"error": f"Unknown setting '{args.get('key')}'.", "exit_code": 1}
|
||||
if _is_secret(key):
|
||||
return {"response": f"'{key}' is a credential. Reset it in the panel.", "exit_code": 0}
|
||||
|
||||
Reference in New Issue
Block a user