mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-04 02:01:48 +03:00
refactor(providers): localize OAuth model discovery
This commit is contained in:
@@ -12,14 +12,16 @@ import httpx
|
||||
import pytest
|
||||
|
||||
from nanobot.providers.oauth_model_catalog import (
|
||||
DEFAULT_OPENAI_CODEX_MODELS_URL,
|
||||
DEFAULT_XAI_GROK_MODELS_URL,
|
||||
OPENAI_CODEX_CATALOG_CLIENT_VERSION,
|
||||
OAuthModelCatalog,
|
||||
OAuthModelInfo,
|
||||
get_oauth_model_catalog,
|
||||
invalidate_oauth_model_catalog,
|
||||
)
|
||||
from nanobot.providers.openai_codex_provider import (
|
||||
DEFAULT_OPENAI_CODEX_MODELS_URL,
|
||||
OPENAI_CODEX_CATALOG_CLIENT_VERSION,
|
||||
)
|
||||
from nanobot.providers.registry import ProviderModelSpec
|
||||
from nanobot.providers.xai_grok_provider import DEFAULT_XAI_GROK_MODELS_URL
|
||||
from nanobot.providers.xai_oauth import XAIToken
|
||||
|
||||
|
||||
@@ -32,8 +34,8 @@ def _clear_oauth_catalogs() -> None:
|
||||
invalidate_oauth_model_catalog(provider)
|
||||
|
||||
|
||||
def _fallback_model() -> OAuthModelInfo:
|
||||
return OAuthModelInfo(id="provider/fallback", label="Fallback")
|
||||
def _fallback_model() -> ProviderModelSpec:
|
||||
return ProviderModelSpec(id="provider/fallback", label="Fallback")
|
||||
|
||||
|
||||
def test_xai_catalog_fetches_remote_models_and_reuses_capability_metadata(
|
||||
@@ -42,9 +44,13 @@ def test_xai_catalog_fetches_remote_models_and_reuses_capability_metadata(
|
||||
) -> None:
|
||||
original_client = httpx.Client
|
||||
captured: dict[str, object] = {}
|
||||
payload = base64.urlsafe_b64encode(
|
||||
json.dumps({"sub": "user-42", "email": "user@example.com"}).encode()
|
||||
).decode().rstrip("=")
|
||||
payload = (
|
||||
base64.urlsafe_b64encode(
|
||||
json.dumps({"sub": "user-42", "email": "user@example.com"}).encode()
|
||||
)
|
||||
.decode()
|
||||
.rstrip("=")
|
||||
)
|
||||
token = XAIToken(
|
||||
access=f"header.{payload}.signature",
|
||||
refresh="refresh-token",
|
||||
@@ -93,14 +99,18 @@ def test_xai_catalog_fetches_remote_models_and_reuses_capability_metadata(
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._xai_oauth_storage_path",
|
||||
"nanobot.providers.xai_grok_provider.get_xai_oauth_storage_path",
|
||||
lambda: tmp_path / "auth" / "xai.json",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._xai_oauth_token",
|
||||
lambda _proxy: token,
|
||||
"nanobot.providers.xai_grok_provider.get_xai_oauth_login_status",
|
||||
lambda: token,
|
||||
)
|
||||
monkeypatch.setattr("nanobot.providers.oauth_model_catalog.httpx.Client", fake_client)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.xai_grok_provider.get_xai_oauth_token",
|
||||
lambda **_kwargs: token,
|
||||
)
|
||||
monkeypatch.setattr("nanobot.providers.xai_grok_provider.httpx.Client", fake_client)
|
||||
|
||||
catalog = get_oauth_model_catalog("xai_grok")
|
||||
|
||||
@@ -181,19 +191,22 @@ def test_openai_codex_catalog_uses_account_catalog_and_filters_hidden_models(
|
||||
follow_redirects=kwargs["follow_redirects"],
|
||||
)
|
||||
|
||||
class Storage:
|
||||
def load(self) -> SimpleNamespace:
|
||||
return SimpleNamespace(access="secret", account_id="account-42")
|
||||
|
||||
def get_token_path(self) -> Path:
|
||||
return tmp_path / "auth" / "openai-codex.json"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._openai_codex_storage_path",
|
||||
lambda: tmp_path / "auth" / "openai-codex.json",
|
||||
"nanobot.providers.openai_codex_provider.FileTokenStorage",
|
||||
lambda **_kwargs: Storage(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._openai_codex_account_key",
|
||||
lambda: "account-key",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"oauth_cli_kit.get_token",
|
||||
"nanobot.providers.openai_codex_provider.get_codex_token",
|
||||
lambda **_kwargs: SimpleNamespace(access="secret", account_id="account-42"),
|
||||
)
|
||||
monkeypatch.setattr("nanobot.providers.oauth_model_catalog.httpx.Client", fake_client)
|
||||
monkeypatch.setattr("nanobot.providers.openai_codex_provider.httpx.Client", fake_client)
|
||||
|
||||
catalog = get_oauth_model_catalog("openai_codex")
|
||||
|
||||
@@ -276,23 +289,11 @@ def test_github_copilot_catalog_only_lists_compatible_chat_models(
|
||||
def get_token_path(self) -> Path:
|
||||
return tmp_path / "auth" / "github-copilot.json"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._github_copilot_storage_path",
|
||||
lambda: tmp_path / "auth" / "github-copilot.json",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._github_copilot_account_key",
|
||||
lambda: "account-key",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.oauth_model_catalog._github_copilot_models_url",
|
||||
lambda: "https://api.githubcopilot.com/models",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.github_copilot_provider.get_storage",
|
||||
lambda: Storage(),
|
||||
)
|
||||
monkeypatch.setattr("nanobot.providers.oauth_model_catalog.httpx.Client", fake_client)
|
||||
monkeypatch.setattr("nanobot.providers.github_copilot_provider.httpx.Client", fake_client)
|
||||
|
||||
catalog = get_oauth_model_catalog("github_copilot")
|
||||
|
||||
@@ -311,12 +312,12 @@ def test_catalog_single_flights_concurrent_refreshes() -> None:
|
||||
calls_lock = threading.Lock()
|
||||
barrier = threading.Barrier(8)
|
||||
|
||||
def fetch(_proxy: str | None) -> tuple[OAuthModelInfo, ...]:
|
||||
def fetch(_proxy: str | None) -> tuple[ProviderModelSpec, ...]:
|
||||
nonlocal calls
|
||||
with calls_lock:
|
||||
calls += 1
|
||||
time.sleep(0.05)
|
||||
return (OAuthModelInfo(id="provider/remote", label="Remote"),)
|
||||
return (ProviderModelSpec(id="provider/remote", label="Remote"),)
|
||||
|
||||
catalog = OAuthModelCatalog(fallback_models=(_fallback_model(),), fetch=fetch)
|
||||
|
||||
@@ -338,14 +339,14 @@ def test_catalog_invalidation_discards_an_inflight_account_refresh() -> None:
|
||||
release = threading.Event()
|
||||
calls = 0
|
||||
|
||||
def fetch(_proxy: str | None) -> tuple[OAuthModelInfo, ...]:
|
||||
def fetch(_proxy: str | None) -> tuple[ProviderModelSpec, ...]:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls == 1:
|
||||
started.set()
|
||||
assert release.wait(timeout=2)
|
||||
return (OAuthModelInfo(id="provider/old-account", label="Old"),)
|
||||
return (OAuthModelInfo(id="provider/new-account", label="New"),)
|
||||
return (ProviderModelSpec(id="provider/old-account", label="Old"),)
|
||||
return (ProviderModelSpec(id="provider/new-account", label="New"),)
|
||||
|
||||
catalog = OAuthModelCatalog(fallback_models=(_fallback_model(),), fetch=fetch)
|
||||
with ThreadPoolExecutor(max_workers=1) as pool:
|
||||
@@ -364,12 +365,12 @@ def test_catalog_returns_stale_then_negative_caches_refresh_failure() -> None:
|
||||
now = [0.0]
|
||||
calls = 0
|
||||
|
||||
def fetch(_proxy: str | None) -> tuple[OAuthModelInfo, ...]:
|
||||
def fetch(_proxy: str | None) -> tuple[ProviderModelSpec, ...]:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls > 1:
|
||||
raise httpx.ConnectError("offline")
|
||||
return (OAuthModelInfo(id="provider/remote", label="Remote"),)
|
||||
return (ProviderModelSpec(id="provider/remote", label="Remote"),)
|
||||
|
||||
catalog = OAuthModelCatalog(
|
||||
fallback_models=(_fallback_model(),),
|
||||
@@ -421,7 +422,7 @@ def test_catalog_returns_stale_then_negative_caches_refresh_failure() -> None:
|
||||
def test_catalog_falls_back_for_remote_failures(failure: Exception) -> None:
|
||||
calls = 0
|
||||
|
||||
def fetch(_proxy: str | None) -> tuple[OAuthModelInfo, ...]:
|
||||
def fetch(_proxy: str | None) -> tuple[ProviderModelSpec, ...]:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
raise failure
|
||||
@@ -444,10 +445,10 @@ def test_catalog_falls_back_for_remote_failures(failure: Exception) -> None:
|
||||
def test_catalog_treats_empty_remote_list_as_failure_and_can_be_invalidated() -> None:
|
||||
calls = 0
|
||||
|
||||
def fetch(_proxy: str | None) -> tuple[OAuthModelInfo, ...]:
|
||||
def fetch(_proxy: str | None) -> tuple[ProviderModelSpec, ...]:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return () if calls == 1 else (OAuthModelInfo(id="provider/new", label="New"),)
|
||||
return () if calls == 1 else (ProviderModelSpec(id="provider/new", label="New"),)
|
||||
|
||||
catalog = OAuthModelCatalog(
|
||||
fallback_models=(_fallback_model(),),
|
||||
|
||||
@@ -11,8 +11,8 @@ import pytest
|
||||
from nanobot.config.schema import Config
|
||||
from nanobot.providers.base import LLMUsage
|
||||
from nanobot.providers.factory import make_provider
|
||||
from nanobot.providers.oauth_model_catalog import OAuthModelCatalogSnapshot, OAuthModelInfo
|
||||
from nanobot.providers.registry import find_by_name
|
||||
from nanobot.providers.oauth_model_catalog import OAuthModelCatalogSnapshot
|
||||
from nanobot.providers.registry import ProviderModelSpec, find_by_name
|
||||
from nanobot.providers.xai_grok_provider import (
|
||||
DEFAULT_XAI_GROK_MODEL,
|
||||
XAIGrokProvider,
|
||||
@@ -51,12 +51,12 @@ def _mock_model_capabilities(
|
||||
def fake_catalog(*_args, **_kwargs):
|
||||
return OAuthModelCatalogSnapshot(
|
||||
models=(
|
||||
OAuthModelInfo(
|
||||
ProviderModelSpec(
|
||||
id="xai-grok/grok-4.5",
|
||||
label="Grok 4.5",
|
||||
supports_backend_search=supports_backend_search,
|
||||
),
|
||||
OAuthModelInfo(
|
||||
ProviderModelSpec(
|
||||
id="xai-grok/grok-4.6",
|
||||
label="Grok 4.6",
|
||||
supports_backend_search=supports_backend_search,
|
||||
@@ -67,7 +67,7 @@ def _mock_model_capabilities(
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.xai_grok_provider.get_oauth_model_catalog",
|
||||
"nanobot.providers.xai_grok_provider.get_xai_grok_model_catalog",
|
||||
fake_catalog,
|
||||
)
|
||||
|
||||
@@ -172,7 +172,7 @@ async def test_explicit_parameterized_x_search_is_preserved_without_catalog_look
|
||||
return "ok", [], "stop", {}, None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.xai_grok_provider.get_oauth_model_catalog",
|
||||
"nanobot.providers.xai_grok_provider.get_xai_grok_model_catalog",
|
||||
unexpected_catalog_lookup,
|
||||
)
|
||||
monkeypatch.setattr("nanobot.providers.xai_grok_provider._request_xai", fake_request)
|
||||
@@ -181,10 +181,12 @@ async def test_explicit_parameterized_x_search_is_preserved_without_catalog_look
|
||||
"allowed_x_handles": ["nanobot_ai"],
|
||||
"from_date": "2026-01-01",
|
||||
}
|
||||
provider = XAIGrokProvider(extra_body={
|
||||
"parallel_tool_calls": False,
|
||||
"tools": [hosted_tool, {"type": "code_interpreter", "container": "auto"}],
|
||||
})
|
||||
provider = XAIGrokProvider(
|
||||
extra_body={
|
||||
"parallel_tool_calls": False,
|
||||
"tools": [hosted_tool, {"type": "code_interpreter", "container": "auto"}],
|
||||
}
|
||||
)
|
||||
|
||||
response = await provider.chat(
|
||||
[{"role": "user", "content": "search"}],
|
||||
@@ -235,7 +237,7 @@ async def test_explicit_empty_tools_disables_catalog_lookup_and_hosted_tool(monk
|
||||
return "ok", [], "stop", {}, None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"nanobot.providers.xai_grok_provider.get_oauth_model_catalog",
|
||||
"nanobot.providers.xai_grok_provider.get_xai_grok_model_catalog",
|
||||
unexpected_catalog_lookup,
|
||||
)
|
||||
monkeypatch.setattr("nanobot.providers.xai_grok_provider._request_xai", fake_request)
|
||||
@@ -243,23 +245,27 @@ async def test_explicit_empty_tools_disables_catalog_lookup_and_hosted_tool(monk
|
||||
|
||||
response = await provider.chat(
|
||||
[{"role": "user", "content": "hello"}],
|
||||
tools=[{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "read_file",
|
||||
"description": "Read a file",
|
||||
"parameters": {"type": "object"},
|
||||
},
|
||||
}],
|
||||
tools=[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "read_file",
|
||||
"description": "Read a file",
|
||||
"parameters": {"type": "object"},
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert response.content == "ok"
|
||||
assert bodies[0]["tools"] == [{
|
||||
"type": "function",
|
||||
"name": "read_file",
|
||||
"description": "Read a file",
|
||||
"parameters": {"type": "object"},
|
||||
}]
|
||||
assert bodies[0]["tools"] == [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "read_file",
|
||||
"description": "Read a file",
|
||||
"parameters": {"type": "object"},
|
||||
}
|
||||
]
|
||||
assert "max_turns" not in bodies[0]
|
||||
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ from nanobot.config.schema import Config, InlineFallbackConfig, ModelPresetConfi
|
||||
from nanobot.llm_usage import get_llm_usage_store
|
||||
from nanobot.llm_usage.models import LLMCallRecord
|
||||
from nanobot.providers.base import LLMUsage
|
||||
from nanobot.providers.oauth_model_catalog import OAuthModelCatalogSnapshot, OAuthModelInfo
|
||||
from nanobot.providers.registry import find_by_name
|
||||
from nanobot.providers.oauth_model_catalog import OAuthModelCatalogSnapshot
|
||||
from nanobot.providers.registry import ProviderModelSpec, find_by_name
|
||||
from nanobot.session.manager import SessionManager
|
||||
from nanobot.session.model_selection import SESSION_MODEL_PRESET_METADATA_KEY
|
||||
from nanobot.webui.settings_api import (
|
||||
@@ -184,11 +184,13 @@ def test_update_api_settings_requires_key_for_network_access(
|
||||
with pytest.raises(WebUISettingsError, match="API key"):
|
||||
update_api_settings({"host": ["0.0.0.0"], "port": ["8900"]})
|
||||
|
||||
payload = update_api_settings({
|
||||
"host": ["0.0.0.0"],
|
||||
"port": ["9900"],
|
||||
"api_key": ["secret-token"],
|
||||
})
|
||||
payload = update_api_settings(
|
||||
{
|
||||
"host": ["0.0.0.0"],
|
||||
"port": ["9900"],
|
||||
"api_key": ["secret-token"],
|
||||
}
|
||||
)
|
||||
saved = load_config(config_path)
|
||||
assert saved.api.host == "0.0.0.0"
|
||||
assert saved.api.port == 9900
|
||||
@@ -347,13 +349,15 @@ def test_create_model_configuration_rejects_dynamic_custom_provider_without_api_
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
config_path = tmp_path / "config.json"
|
||||
config = Config.model_validate({
|
||||
"providers": {
|
||||
DYNAMIC_PROVIDER_NAME: {
|
||||
"apiKey": "sk-test",
|
||||
config = Config.model_validate(
|
||||
{
|
||||
"providers": {
|
||||
DYNAMIC_PROVIDER_NAME: {
|
||||
"apiKey": "sk-test",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
save_config(config, config_path)
|
||||
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
|
||||
|
||||
@@ -498,9 +502,7 @@ def test_update_model_configuration_rolls_back_sessions_when_config_save_fails(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
config_path = tmp_path / "config.json"
|
||||
config = Config(
|
||||
model_presets={"openai": ModelPresetConfig(model="openai/gpt-4.1")}
|
||||
)
|
||||
config = Config(model_presets={"openai": ModelPresetConfig(model="openai/gpt-4.1")})
|
||||
save_config(config, config_path)
|
||||
calls: list[tuple[str, str]] = []
|
||||
|
||||
@@ -891,11 +893,13 @@ def test_update_provider_settings_updates_and_clears_oauth_proxy(
|
||||
},
|
||||
)
|
||||
|
||||
payload = update_provider_settings({
|
||||
"provider": [provider_name],
|
||||
"proxy": [" http://127.0.0.1:7890 "],
|
||||
"extraBody": [json.dumps({"tools": []})],
|
||||
})
|
||||
payload = update_provider_settings(
|
||||
{
|
||||
"provider": [provider_name],
|
||||
"proxy": [" http://127.0.0.1:7890 "],
|
||||
"extraBody": [json.dumps({"tools": []})],
|
||||
}
|
||||
)
|
||||
|
||||
providers = {row["name"]: row for row in payload["providers"]}
|
||||
assert providers[provider_name]["proxy"] == "http://127.0.0.1:7890"
|
||||
@@ -1100,15 +1104,17 @@ def test_settings_payload_groups_opencode_compatibility_alias(tmp_path, monkeypa
|
||||
|
||||
def test_settings_payload_keeps_configured_opencode_legacy_alias(tmp_path, monkeypatch) -> None:
|
||||
config_path = tmp_path / "config.json"
|
||||
config = Config.model_validate({
|
||||
"providers": {"opencodeZen": {"apiKey": "legacy-key"}},
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"provider": "opencode_zen",
|
||||
"model": "opencode/deepseek-v4-pro",
|
||||
}
|
||||
},
|
||||
})
|
||||
config = Config.model_validate(
|
||||
{
|
||||
"providers": {"opencodeZen": {"apiKey": "legacy-key"}},
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"provider": "opencode_zen",
|
||||
"model": "opencode/deepseek-v4-pro",
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
save_config(config, config_path)
|
||||
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
|
||||
|
||||
@@ -1125,13 +1131,15 @@ def test_settings_payload_marks_dynamic_custom_provider_without_api_base_unconfi
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
config_path = tmp_path / "config.json"
|
||||
config = Config.model_validate({
|
||||
"providers": {
|
||||
DYNAMIC_PROVIDER_NAME: {
|
||||
"apiKey": "sk-test",
|
||||
config = Config.model_validate(
|
||||
{
|
||||
"providers": {
|
||||
DYNAMIC_PROVIDER_NAME: {
|
||||
"apiKey": "sk-test",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
save_config(config, config_path)
|
||||
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
|
||||
|
||||
@@ -1467,16 +1475,18 @@ def test_settings_payload_includes_token_usage_summary(
|
||||
config = Config()
|
||||
save_config(config, config_path)
|
||||
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
|
||||
get_llm_usage_store().record(LLMCallRecord(
|
||||
started_at_ms=int(time.time() * 1000),
|
||||
duration_ms=1,
|
||||
provider="openai",
|
||||
model="gpt-5",
|
||||
source="user",
|
||||
stream=False,
|
||||
finish_reason="stop",
|
||||
usage=LLMUsage.reported(input_tokens=10, output_tokens=5),
|
||||
))
|
||||
get_llm_usage_store().record(
|
||||
LLMCallRecord(
|
||||
started_at_ms=int(time.time() * 1000),
|
||||
duration_ms=1,
|
||||
provider="openai",
|
||||
model="gpt-5",
|
||||
source="user",
|
||||
stream=False,
|
||||
finish_reason="stop",
|
||||
usage=LLMUsage.reported(input_tokens=10, output_tokens=5),
|
||||
)
|
||||
)
|
||||
|
||||
payload = settings_payload()
|
||||
|
||||
@@ -1497,16 +1507,18 @@ def test_settings_usage_payload_returns_lightweight_token_usage(
|
||||
config = Config()
|
||||
save_config(config, config_path)
|
||||
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
|
||||
get_llm_usage_store().record(LLMCallRecord(
|
||||
started_at_ms=int(time.time() * 1000),
|
||||
duration_ms=1,
|
||||
provider="openai",
|
||||
model="gpt-5",
|
||||
source="user",
|
||||
stream=False,
|
||||
finish_reason="stop",
|
||||
usage=LLMUsage.reported(input_tokens=20, output_tokens=2),
|
||||
))
|
||||
get_llm_usage_store().record(
|
||||
LLMCallRecord(
|
||||
started_at_ms=int(time.time() * 1000),
|
||||
duration_ms=1,
|
||||
provider="openai",
|
||||
model="gpt-5",
|
||||
source="user",
|
||||
stream=False,
|
||||
finish_reason="stop",
|
||||
usage=LLMUsage.reported(input_tokens=20, output_tokens=2),
|
||||
)
|
||||
)
|
||||
|
||||
payload = settings_usage_payload()
|
||||
|
||||
@@ -1930,9 +1942,7 @@ def test_xai_grok_login_reports_upstream_failure_as_bad_gateway(
|
||||
)
|
||||
|
||||
assert exc.value.status == 502
|
||||
assert str(exc.value) == (
|
||||
"xAI OAuth login failed: Could not reach xAI sign-in: ConnectError."
|
||||
)
|
||||
assert str(exc.value) == ("xAI OAuth login failed: Could not reach xAI sign-in: ConnectError.")
|
||||
assert exc.value.__cause__ is failure
|
||||
|
||||
|
||||
@@ -2003,7 +2013,7 @@ def test_provider_models_payload_returns_online_openai_codex_models(
|
||||
"nanobot.webui.settings_models.get_oauth_model_catalog",
|
||||
lambda *_args, **_kwargs: OAuthModelCatalogSnapshot(
|
||||
models=(
|
||||
OAuthModelInfo(
|
||||
ProviderModelSpec(
|
||||
id="openai-codex/gpt-5.6-sol",
|
||||
label="GPT-5.6-Sol",
|
||||
description="Latest frontier agentic coding model.",
|
||||
@@ -2041,7 +2051,7 @@ def test_provider_models_payload_returns_online_github_copilot_models(
|
||||
"nanobot.webui.settings_models.get_oauth_model_catalog",
|
||||
lambda *_args, **_kwargs: OAuthModelCatalogSnapshot(
|
||||
models=(
|
||||
OAuthModelInfo(
|
||||
ProviderModelSpec(
|
||||
id="github-copilot/claude-sonnet",
|
||||
label="Claude Sonnet",
|
||||
owned_by="GitHub Copilot",
|
||||
@@ -2068,7 +2078,7 @@ def test_provider_models_payload_returns_online_xai_grok_models(
|
||||
"nanobot.webui.settings_models.get_oauth_model_catalog",
|
||||
lambda *_args, **_kwargs: OAuthModelCatalogSnapshot(
|
||||
models=(
|
||||
OAuthModelInfo(
|
||||
ProviderModelSpec(
|
||||
id="xai-grok/grok-4.6",
|
||||
label="Grok 4.6",
|
||||
description="Latest frontier model",
|
||||
@@ -2077,7 +2087,7 @@ def test_provider_models_payload_returns_online_xai_grok_models(
|
||||
reasoning_efforts=("xhigh", "high", "medium", "low"),
|
||||
supports_backend_search=True,
|
||||
),
|
||||
OAuthModelInfo(
|
||||
ProviderModelSpec(
|
||||
id="xai-grok/grok-4.5",
|
||||
label="Grok 4.5",
|
||||
owned_by="xAI",
|
||||
@@ -2115,7 +2125,7 @@ def test_provider_models_payload_returns_online_xai_grok_models(
|
||||
"context_window": 500000,
|
||||
"reasoning_efforts": ["high", "medium", "low"],
|
||||
"supports_backend_search": True,
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user