fix: a chat switched to Codex no longer sends its model to the previous provider's endpoint

The messaging gateway's /model wrote only model + provider to the session row. A row the
Desktop/TUI had persisted with the Nous Portal route kept that base_url and api_mode, so resuming
a chat switched to openai-codex built provider=openai-codex on the Portal URL and posted
gpt-6-luna-900k to inference-api.nousresearch.com/v1/chat/completions: "Model 'gpt-6-luna-900k'
isn't available on ChatGPT or Codex Subscription".

- SessionDB.update_session_model writes the whole route (provider, base_url, api_mode) in both
  shapes resume reads (top-level for TUI/Desktop, gateway_runtime for the CLI) whenever a
  provider is given; the gateway /model passes the switch result's endpoint.
- Resume readers (TUI/Desktop, CLI, gateway rehydrate) drop a persisted base_url that is another
  built-in provider's canonical endpoint, so rows already written by older builds heal.
- A gateway session override whose credentials failed to re-resolve is resolved for its own
  provider on the turn instead of being layered over the default provider's runtime (which
  produced openai-codex + Nous key + Nous URL). If it still cannot resolve, that turn runs on the
  whole default route with the existing one-shot "Provider fallback" notice; the override is kept
  and retried next turn.
This commit is contained in:
teknium1
2026-09-23 03:43:12 -07:00
committed by Teknium
parent 24f03c41c1
commit 07646a7f72
9 changed files with 127 additions and 10 deletions

View File

@@ -667,15 +667,20 @@ class SessionSessionsMixin:
payload = json.dumps(list(tool_names)) if tool_names is not None else None
self._write_sql("UPDATE sessions SET tool_names = ? WHERE id = ?", (payload, session_id))
def update_session_model(self, session_id: str, model: str, provider: Optional[str] = None) -> None:
def update_session_model(
self, session_id: str, model: str, provider: Optional[str] = None, *,
base_url: Optional[str] = None, api_mode: Optional[str] = None,
) -> None:
"""Set the model after a mid-session /model switch (unconditionally), null system_prompt so
stale Model:/Provider: footers rebuild, and drop any Browser runtime lock (lineage markers
survive). *provider* is merged into model_config so resume recombines model and provider.
survive).
When *provider* is given, it is merged into ``model_config`` alongside the model (``$.model`` /
``$.provider``) so a later resume recombines the persisted model with the provider that actually
serves it instead of the config.yaml primary provider (#79536). Callers without provider knowledge
leave any stored provider untouched.
When *provider* is given the whole route is written, in both shapes resume reads (top-level
keys for the TUI/Desktop, ``gateway_runtime`` for the CLI), so a later resume recombines the
model with the provider that serves it (#79536). ``base_url``/``api_mode`` are always
replaced then (``None`` deletes): the previous provider's endpoint must not survive a switch,
or resume sends the new provider's model to the old host. Callers without provider knowledge
leave the stored route untouched.
"""
# Flush first: a still-queued pre-switch delta applied after this UPDATE would trip the
# first_accounted_route overwrite and resurrect the old route.
@@ -684,7 +689,8 @@ class SessionSessionsMixin:
if model:
patch["model"] = model
if provider:
patch["provider"] = provider
route = {"provider": provider, "base_url": base_url or None, "api_mode": api_mode or None}
patch.update(route, gateway_runtime=route)
self._write_model_config_patch(
session_id, patch, "UPDATE sessions SET model = ?, model_config = ?, "
"system_prompt = NULL, system_prompt_hash = NULL WHERE id = ?",