feat(providers): add xAI Grok OAuth with capability-gated X Search (#5035)

This commit is contained in:
chengyongru
2026-07-23 11:55:16 +08:00
committed by GitHub
parent c22efb5f7a
commit c7393c785e
38 changed files with 3881 additions and 104 deletions
+20
View File
@@ -37,6 +37,7 @@ from nanobot.optional_features import (
)
from nanobot.pairing import approve_code, deny_code, list_pending
from nanobot.webui.cli_apps_api import cli_apps_action, cli_apps_payload
from nanobot.webui.http_utils import case_insensitive_header
from nanobot.webui.http_utils import is_local_browser_request as _is_local_browser_request
from nanobot.webui.http_utils import query_first as _query_first
from nanobot.webui.mcp_presets_api import mcp_presets_settings_action
@@ -47,6 +48,7 @@ from nanobot.webui.nanobot_features_api import (
)
from nanobot.webui.settings_api import (
WebUISettingsError,
complete_oauth_provider,
create_model_configuration,
decorate_settings_payload,
login_oauth_provider,
@@ -73,6 +75,8 @@ _CHANNEL_VALUES_HEADER = "X-Nanobot-Channel-Values"
_CHANNEL_VALUES_HEADER_MAX_BYTES = 64 * 1024
_API_SERVICE_VALUES_HEADER = "X-Nanobot-API-Service-Values"
_API_SERVICE_VALUES_HEADER_MAX_BYTES = 8 * 1024
_OAUTH_CODE_HEADER = "X-Nanobot-OAuth-Code"
_OAUTH_CODE_HEADER_MAX_BYTES = 8 * 1024
_SKIP_FIELD = object()
_CHANNEL_CONNECT_ACTIONS = frozenset({"start", "poll", "cancel"})
@@ -146,6 +150,8 @@ class WebUISettingsRouter:
return await self._handle_settings_provider_models(request)
if path == "/api/settings/provider/oauth-login":
return await self._handle_settings_provider_oauth(request, "login")
if path == "/api/settings/provider/oauth-login/complete":
return await self._handle_settings_provider_oauth(request, "complete")
if path == "/api/settings/provider/oauth-logout":
return await self._handle_settings_provider_oauth(request, "logout")
if path == "/api/settings/web-search/update":
@@ -378,10 +384,24 @@ class WebUISettingsRouter:
try:
if action == "login":
payload = await asyncio.to_thread(login_oauth_provider, query)
elif action == "complete":
authorization_code = case_insensitive_header(
request.headers,
_OAUTH_CODE_HEADER,
)
if len(authorization_code.encode("utf-8")) > _OAUTH_CODE_HEADER_MAX_BYTES:
raise WebUISettingsError("OAuth authorization code is too large")
payload = await asyncio.to_thread(
complete_oauth_provider,
query,
authorization_code or None,
)
else:
payload = await asyncio.to_thread(logout_oauth_provider, query)
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
if payload.get("status") in {"authorization_required", "pending"}:
return self._json_response(payload)
return self._json_response(self._with_restart_state(payload))
def _handle_settings_web_search_update(self, request: WsRequest) -> Response: