From 2389ab1f5af1cf8d15e6f22917a058d911435e51 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:39:07 +0800 Subject: [PATCH] feat(providers): add Grok 4.6 subscription model --- docs/cli-reference.md | 2 +- docs/configuration.md | 2 +- docs/providers.md | 2 +- nanobot/cli/provider.py | 7 +++++-- nanobot/providers/registry.py | 6 ++++++ nanobot/providers/xai_grok_provider.py | 2 +- tests/cli/test_commands.py | 2 +- tests/providers/test_xai_grok_provider.py | 15 +++++++++++---- tests/webui/test_settings_api.py | 9 ++++++++- 9 files changed, 35 insertions(+), 12 deletions(-) diff --git a/docs/cli-reference.md b/docs/cli-reference.md index b57e9e268..f42836397 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -344,7 +344,7 @@ remain accepted as no-op compatibility aliases. | Command | Description | |---|---| | `nanobot provider login openai-codex --set-main` | Authenticate Codex and select its current default model | -| `nanobot provider login xai-grok --set-main` | Authenticate an eligible X Premium / Grok subscription and select Grok 4.5; hosted X Search is enabled for models that advertise support | +| `nanobot provider login xai-grok --set-main` | Authenticate an eligible X Premium / Grok subscription and select Grok 4.6; hosted X Search is enabled for models that advertise support | | `nanobot provider login github-copilot --set-main` | Authenticate GitHub Copilot and select its current default model | | `nanobot provider logout openai-codex` | Remove OpenAI Codex OAuth state | | `nanobot provider logout xai-grok --config ` | Remove the selected nanobot instance's xAI OAuth state | diff --git a/docs/configuration.md b/docs/configuration.md index b6a6986ae..0b6393eb2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -764,7 +764,7 @@ nanobot provider login xai-grok --set-main nanobot agent -m "Hello from Grok." ``` -The default model is `xai-grok/grok-4.5` with a 500,000-token context window. +The default model is `xai-grok/grok-4.6` with a 500,000-token context window. The provider reads xAI's model catalog and includes the server-hosted `x_search` tool only when the selected model advertises `supportsBackendSearch`. Models without that capability continue normally without hosted X Search. When enabled, diff --git a/docs/providers.md b/docs/providers.md index 9d778cc1b..c52438e9a 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -578,7 +578,7 @@ For an eligible X Premium / Grok subscription: nanobot provider login xai-grok --set-main ``` -This selects `xai-grok/grok-4.5`. The provider reads xAI's model catalog and +This selects `xai-grok/grok-4.6`. The provider reads xAI's model catalog and exposes the hosted `x_search` tool only when the selected model advertises `supportsBackendSearch`; otherwise the model runs without hosted X Search. When enabled, Grok can search current X posts and return inline source links diff --git a/nanobot/cli/provider.py b/nanobot/cli/provider.py index d0769bc99..2a4c4d201 100644 --- a/nanobot/cli/provider.py +++ b/nanobot/cli/provider.py @@ -29,7 +29,7 @@ _PROVIDER_DISPLAY: dict[str, str] = { _OAUTH_PROVIDER_DEFAULT_MODELS: dict[str, str] = { "openai_codex": "openai-codex/gpt-5.6-sol", - "xai_grok": "xai-grok/grok-4.5", + "xai_grok": "xai-grok/grok-4.6", "github_copilot": "github-copilot/gpt-5.4-mini", } @@ -134,7 +134,10 @@ def _set_oauth_provider_as_main( config.agents.defaults.model_preset = None config.agents.defaults.provider = provider_name config.agents.defaults.model = selected_model - if provider_name == "xai_grok" and selected_model == "xai-grok/grok-4.5": + if provider_name == "xai_grok" and selected_model in { + "xai-grok/grok-4.5", + "xai-grok/grok-4.6", + }: config.agents.defaults.context_window_tokens = 500_000 save_config(config, resolved_config_path) diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py index 46d414012..be6d83364 100644 --- a/nanobot/providers/registry.py +++ b/nanobot/providers/registry.py @@ -461,6 +461,12 @@ PROVIDERS: tuple[ProviderSpec, ...] = ( display_name="xAI Grok", model_catalog="builtin", builtin_models=( + ProviderModelSpec( + id="xai-grok/grok-4.6", + label="Grok 4.6", + description="Grok via xAI subscription; X Search is enabled when supported.", + context_window=500000, + ), ProviderModelSpec( id="xai-grok/grok-4.5", label="Grok 4.5", diff --git a/nanobot/providers/xai_grok_provider.py b/nanobot/providers/xai_grok_provider.py index 3d2ec7b2b..88c401e14 100644 --- a/nanobot/providers/xai_grok_provider.py +++ b/nanobot/providers/xai_grok_provider.py @@ -35,7 +35,7 @@ from nanobot.providers.xai_oauth import ( DEFAULT_XAI_GROK_URL = "https://cli-chat-proxy.grok.com/v1/responses" DEFAULT_XAI_GROK_MODELS_URL = "https://cli-chat-proxy.grok.com/v1/models" -DEFAULT_XAI_GROK_MODEL = "xai-grok/grok-4.5" +DEFAULT_XAI_GROK_MODEL = "xai-grok/grok-4.6" _MODEL_CAPABILITIES_TTL_S = 5 * 60 _MAX_ERROR_BODY_CHARS = 1000 _SENSITIVE_ERROR_KEYS = { diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py index 41d33edc3..69f83badd 100644 --- a/tests/cli/test_commands.py +++ b/tests/cli/test_commands.py @@ -799,7 +799,7 @@ def test_provider_login_can_set_xai_grok_as_main_provider(tmp_path): saved = Config.model_validate(json.loads(config_path.read_text(encoding="utf-8"))) assert saved.agents.defaults.provider == "xai_grok" - assert saved.agents.defaults.model == "xai-grok/grok-4.5" + assert saved.agents.defaults.model == "xai-grok/grok-4.6" assert saved.agents.defaults.context_window_tokens == 500_000 assert saved.agents.defaults.model_preset is None assert make_provider(saved).__class__.__name__ == "XAIGrokProvider" diff --git a/tests/providers/test_xai_grok_provider.py b/tests/providers/test_xai_grok_provider.py index 5d9629a2f..ce8eb005e 100644 --- a/tests/providers/test_xai_grok_provider.py +++ b/tests/providers/test_xai_grok_provider.py @@ -52,7 +52,10 @@ def _mock_model_capabilities( supports_backend_search: bool, ) -> None: async def fake_fetch(*_args, **_kwargs): - return {"grok-4.5": supports_backend_search} + return { + "grok-4.5": supports_backend_search, + "grok-4.6": supports_backend_search, + } monkeypatch.setattr( "nanobot.providers.xai_grok_provider._fetch_xai_model_capabilities", @@ -60,13 +63,17 @@ def _mock_model_capabilities( ) -def test_xai_grok_registry_exposes_curated_x_search_model() -> None: +def test_xai_grok_registry_exposes_curated_x_search_models() -> None: spec = find_by_name("xai_grok") assert spec is not None assert spec.is_oauth is True assert spec.backend == "xai_grok" assert spec.builtin_models[0].id == DEFAULT_XAI_GROK_MODEL + assert [model.id for model in spec.builtin_models] == [ + "xai-grok/grok-4.6", + "xai-grok/grok-4.5", + ] assert spec.builtin_models[0].context_window == 500000 assert "when supported" in spec.builtin_models[0].description @@ -117,7 +124,7 @@ async def test_provider_injects_hosted_x_search_and_required_proxy_headers(monke assert response.content == "answer [[1]](https://x.com/example/status/1)" url, headers, body = calls[0] assert url == "https://cli-chat-proxy.grok.com/v1/responses" - assert body["model"] == "grok-4.5" + assert body["model"] == "grok-4.6" assert body["tools"] == [ { "type": "function", @@ -137,7 +144,7 @@ async def test_provider_injects_hosted_x_search_and_required_proxy_headers(monke assert headers["x-authenticateresponse"] == "authenticate-response" assert headers["x-grok-client-identifier"] == "nanobot" assert headers["x-grok-client-mode"] == "headless" - assert headers["x-grok-model-override"] == "grok-4.5" + assert headers["x-grok-model-override"] == "grok-4.6" @pytest.mark.asyncio diff --git a/tests/webui/test_settings_api.py b/tests/webui/test_settings_api.py index de40dea33..562ac1e84 100644 --- a/tests/webui/test_settings_api.py +++ b/tests/webui/test_settings_api.py @@ -2015,12 +2015,19 @@ def test_provider_models_payload_returns_curated_openai_codex_models() -> None: ] -def test_provider_models_payload_returns_xai_grok_model() -> None: +def test_provider_models_payload_returns_xai_grok_models() -> None: payload = provider_models_payload({"provider": ["xai_grok"]}) assert payload["status"] == "available" assert payload["catalog_kind"] == "builtin" assert payload["models"] == [ + { + "id": "xai-grok/grok-4.6", + "label": "Grok 4.6", + "description": "Grok via xAI subscription; X Search is enabled when supported.", + "owned_by": "xAI Grok", + "context_window": 500000, + }, { "id": "xai-grok/grok-4.5", "label": "Grok 4.5",