Files
isparkclaw-webui/tests/test_issue854_live_model_prefix.py
nesquena-hermes 498156a3e8 fix(settings): show live models in default model picker and apply to new chats (#872) (#900)
* fix(settings): show live models in default model picker and apply to new chats (#872)

Two related bugs:
1. Settings > Preferences > Default Model dropdown only showed static models
   from /api/models — live-fetched models (e.g. @nous:anthropic/claude-opus-4.7)
   were missing. Now calls _fetchLiveModels() on the settings picker too.
2. New chats ignored the saved default model preference — they always used the
   chat-header dropdown value (which reflects the previous session's model).
   Now newSession() uses the saved default_model and syncs the dropdown.

Extracted _addLiveModelsToSelect() from _fetchLiveModels() so cached live models
can be applied to any <select> element (chat-header or settings picker).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(tests): update live-model prefix tests for _addLiveModelsToSelect extraction

The tests searched for og.dataset.provider, _isPortalFetch, and openrouter
exclusion patterns inside _fetchLiveModels(). These were extracted into
_addLiveModelsToSelect() as part of the #872 fix. Updated regex targets to
check _addLiveModelsToSelect first, falling back to _fetchLiveModels.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: add multi-tab note on window._defaultModel

Clarifies that window._defaultModel is per-page-load and not synced
across browser tabs, following maintainer feedback on #889.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: CHANGELOG for v0.50.170

* chore: trigger PR refresh after rebase

---------

Co-authored-by: fr33m1nd <bergeouss@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-04-23 09:58:15 -07:00

101 lines
4.7 KiB
Python

"""Regression tests for #854 — live-fetched models must route through the
configured portal provider, not OpenRouter."""
import os
import re
_SRC = os.path.join(os.path.dirname(__file__), "..")
def _read(name):
return open(os.path.join(_SRC, name), encoding="utf-8").read()
class TestLiveModelPrefix:
"""_fetchLiveModels() must apply @provider: prefix to live-fetched model
IDs when the fetch is for the active portal provider (Nous, OpenCode,
etc.) — including IDs that already contain a slash (the upstream vendor
namespace), since those would otherwise be mis-routed via OpenRouter."""
def test_apply_prefix_to_any_non_at_id(self):
"""The prefix check must not gate on `!mid.includes('/')`. The bug
scenario in #854 is precisely about slash-prefixed IDs like
`minimax/minimax-m2.7` from Nous's live catalog — excluding them
leaves the bug unfixed."""
js = _read("static/ui.js")
# Live model prefix logic was extracted to _addLiveModelsToSelect (#872)
m = re.search(r'function _addLiveModelsToSelect\(.*?\n\}', js, re.DOTALL)
if not m:
m = re.search(r'async function _fetchLiveModels\(.*?\n\}', js, re.DOTALL)
assert m, "_addLiveModelsToSelect or _fetchLiveModels not found"
fn = m.group(0)
# The prefix application block must NOT have `!mid.includes('/')`
# as a guard — slash-prefixed IDs from portal providers also need
# the prefix.
prefix_block = re.search(
r"if\s*\(\s*[^)]*!mid\.startsWith\(['\"]@['\"]\)[^)]*\)\s*\{\s*mid\s*=\s*`@",
fn,
)
assert prefix_block, "@provider: prefix application not found"
# The block must prefix when portal-fetch is true and not already @-prefixed.
# It must NOT check for slash presence — that's the bug.
assert "!mid.includes('/')" not in prefix_block.group(0), (
"The prefix application must NOT exclude slash-prefixed IDs — "
"portal catalogs return `minimax/minimax-m2.7` and similar that "
"need `@nous:` prefix to route through the configured portal (#854)"
)
def test_portal_fetch_flag_semantics(self):
"""The flag controlling prefix application should be named/structured
so the prefix is ADDED when the flag is true (portal fetch), not when
false. Earlier revision used `!_needsPrefix` (inverted)."""
js = _read("static/ui.js")
# Live model prefix logic was extracted to _addLiveModelsToSelect (#872)
m = re.search(r'function _addLiveModelsToSelect\(.*?\n\}', js, re.DOTALL)
if not m:
m = re.search(r'async function _fetchLiveModels\(.*?\n\}', js, re.DOTALL)
assert m
fn = m.group(0)
# New flag: _isPortalFetch (positive semantics)
assert "_isPortalFetch" in fn, (
"flag should be named _isPortalFetch to reflect positive semantics "
"(prefix ADDED when true, not when false)"
)
# And the prefix application should be guarded BY the flag (not by its negation)
gate = re.search(
r"if\s*\(\s*_isPortalFetch\s*&&\s*!mid\.startsWith",
fn,
)
assert gate, "prefix application must be guarded by _isPortalFetch (true ⇒ prefix)"
def test_portal_fetch_excludes_openrouter_and_custom(self):
"""OpenRouter IDs are cross-namespace by design, and `custom` providers
use user-defined bare names — neither should get a `@provider:` prefix."""
js = _read("static/ui.js")
# Live model prefix logic was extracted to _addLiveModelsToSelect (#872)
m = re.search(r'function _addLiveModelsToSelect\(.*?\n\}', js, re.DOTALL)
if not m:
m = re.search(r'async function _fetchLiveModels\(.*?\n\}', js, re.DOTALL)
assert m
fn = m.group(0)
assert "_ap!=='openrouter'" in fn or "_ap !== 'openrouter'" in fn, (
"portal flag must exclude openrouter"
)
assert "_ap!=='custom'" in fn or "_ap !== 'custom'" in fn, (
"portal flag must exclude custom"
)
class TestCheckProviderMismatchAtPrefix:
"""_checkProviderMismatch() must not warn on `@provider:`-prefixed IDs —
the prefix itself is an explicit provider hint, so there's no mismatch."""
def test_returns_null_for_at_prefix_ids(self):
js = _read("static/ui.js")
m = re.search(r'function _checkProviderMismatch\(.*?\n\}', js, re.DOTALL)
assert m, "_checkProviderMismatch not found"
fn = m.group(0)
assert "modelId.startsWith('@')" in fn or 'modelId.startsWith("@")' in fn, (
"_checkProviderMismatch must return null early for @provider: prefixed IDs"
)