mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
feat(providers): add Grok 4.6 subscription model
This commit is contained in:
@@ -344,7 +344,7 @@ remain accepted as no-op compatibility aliases.
|
|||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `nanobot provider login openai-codex --set-main` | Authenticate Codex and select its current default model |
|
| `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 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 openai-codex` | Remove OpenAI Codex OAuth state |
|
||||||
| `nanobot provider logout xai-grok --config <path>` | Remove the selected nanobot instance's xAI OAuth state |
|
| `nanobot provider logout xai-grok --config <path>` | Remove the selected nanobot instance's xAI OAuth state |
|
||||||
|
|||||||
@@ -764,7 +764,7 @@ nanobot provider login xai-grok --set-main
|
|||||||
nanobot agent -m "Hello from Grok."
|
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`
|
The provider reads xAI's model catalog and includes the server-hosted `x_search`
|
||||||
tool only when the selected model advertises `supportsBackendSearch`. Models
|
tool only when the selected model advertises `supportsBackendSearch`. Models
|
||||||
without that capability continue normally without hosted X Search. When enabled,
|
without that capability continue normally without hosted X Search. When enabled,
|
||||||
|
|||||||
+1
-1
@@ -578,7 +578,7 @@ For an eligible X Premium / Grok subscription:
|
|||||||
nanobot provider login xai-grok --set-main
|
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
|
exposes the hosted `x_search` tool only when the selected model advertises
|
||||||
`supportsBackendSearch`; otherwise the model runs without hosted X Search.
|
`supportsBackendSearch`; otherwise the model runs without hosted X Search.
|
||||||
When enabled, Grok can search current X posts and return inline source links
|
When enabled, Grok can search current X posts and return inline source links
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ _PROVIDER_DISPLAY: dict[str, str] = {
|
|||||||
|
|
||||||
_OAUTH_PROVIDER_DEFAULT_MODELS: dict[str, str] = {
|
_OAUTH_PROVIDER_DEFAULT_MODELS: dict[str, str] = {
|
||||||
"openai_codex": "openai-codex/gpt-5.6-sol",
|
"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",
|
"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.model_preset = None
|
||||||
config.agents.defaults.provider = provider_name
|
config.agents.defaults.provider = provider_name
|
||||||
config.agents.defaults.model = selected_model
|
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
|
config.agents.defaults.context_window_tokens = 500_000
|
||||||
save_config(config, resolved_config_path)
|
save_config(config, resolved_config_path)
|
||||||
|
|
||||||
|
|||||||
@@ -461,6 +461,12 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
|
|||||||
display_name="xAI Grok",
|
display_name="xAI Grok",
|
||||||
model_catalog="builtin",
|
model_catalog="builtin",
|
||||||
builtin_models=(
|
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(
|
ProviderModelSpec(
|
||||||
id="xai-grok/grok-4.5",
|
id="xai-grok/grok-4.5",
|
||||||
label="Grok 4.5",
|
label="Grok 4.5",
|
||||||
|
|||||||
@@ -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_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_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
|
_MODEL_CAPABILITIES_TTL_S = 5 * 60
|
||||||
_MAX_ERROR_BODY_CHARS = 1000
|
_MAX_ERROR_BODY_CHARS = 1000
|
||||||
_SENSITIVE_ERROR_KEYS = {
|
_SENSITIVE_ERROR_KEYS = {
|
||||||
|
|||||||
@@ -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")))
|
saved = Config.model_validate(json.loads(config_path.read_text(encoding="utf-8")))
|
||||||
assert saved.agents.defaults.provider == "xai_grok"
|
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.context_window_tokens == 500_000
|
||||||
assert saved.agents.defaults.model_preset is None
|
assert saved.agents.defaults.model_preset is None
|
||||||
assert make_provider(saved).__class__.__name__ == "XAIGrokProvider"
|
assert make_provider(saved).__class__.__name__ == "XAIGrokProvider"
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ def _mock_model_capabilities(
|
|||||||
supports_backend_search: bool,
|
supports_backend_search: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
async def fake_fetch(*_args, **_kwargs):
|
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(
|
monkeypatch.setattr(
|
||||||
"nanobot.providers.xai_grok_provider._fetch_xai_model_capabilities",
|
"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")
|
spec = find_by_name("xai_grok")
|
||||||
|
|
||||||
assert spec is not None
|
assert spec is not None
|
||||||
assert spec.is_oauth is True
|
assert spec.is_oauth is True
|
||||||
assert spec.backend == "xai_grok"
|
assert spec.backend == "xai_grok"
|
||||||
assert spec.builtin_models[0].id == DEFAULT_XAI_GROK_MODEL
|
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 spec.builtin_models[0].context_window == 500000
|
||||||
assert "when supported" in spec.builtin_models[0].description
|
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)"
|
assert response.content == "answer [[1]](https://x.com/example/status/1)"
|
||||||
url, headers, body = calls[0]
|
url, headers, body = calls[0]
|
||||||
assert url == "https://cli-chat-proxy.grok.com/v1/responses"
|
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"] == [
|
assert body["tools"] == [
|
||||||
{
|
{
|
||||||
"type": "function",
|
"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-authenticateresponse"] == "authenticate-response"
|
||||||
assert headers["x-grok-client-identifier"] == "nanobot"
|
assert headers["x-grok-client-identifier"] == "nanobot"
|
||||||
assert headers["x-grok-client-mode"] == "headless"
|
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
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -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"]})
|
payload = provider_models_payload({"provider": ["xai_grok"]})
|
||||||
|
|
||||||
assert payload["status"] == "available"
|
assert payload["status"] == "available"
|
||||||
assert payload["catalog_kind"] == "builtin"
|
assert payload["catalog_kind"] == "builtin"
|
||||||
assert payload["models"] == [
|
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",
|
"id": "xai-grok/grok-4.5",
|
||||||
"label": "Grok 4.5",
|
"label": "Grok 4.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user