From 1d7bad3909a70e0ebb86a34134481a820c6fa5ff Mon Sep 17 00:00:00 2001 From: chengyongru Date: Tue, 21 Jul 2026 17:45:55 +0800 Subject: [PATCH] feat(providers): support Codex fast mode --- docs/configuration.md | 19 +++++++++++ nanobot/providers/factory.py | 1 + nanobot/providers/openai_codex_provider.py | 5 +++ tests/providers/test_openai_codex_provider.py | 34 +++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 150e90e0..2bf123c9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -676,6 +676,25 @@ Then run: nanobot agent -m "Hello!" ``` +To opt in to Codex Fast mode, merge this provider setting into `config.json`: + +```json +{ + "providers": { + "openaiCodex": { + "extraBody": { + "service_tier": "priority" + } + } + } +} +``` + +`priority` is the Responses API request value used by Codex Fast mode. The setting only works +for models and accounts that support Fast mode; remove `service_tier` to return to standard +processing. Fast mode consumes Codex credits at a higher rate. See the +[OpenAI Codex rate card](https://help.openai.com/en/articles/20001106) for current details. + For proxy, remote/headless login, model-name, or config-key errors, see [`troubleshooting.md`](./troubleshooting.md#provider-and-model-problems). diff --git a/nanobot/providers/factory.py b/nanobot/providers/factory.py index 8e2543d9..4b56f054 100644 --- a/nanobot/providers/factory.py +++ b/nanobot/providers/factory.py @@ -88,6 +88,7 @@ def _make_provider_core( provider = OpenAICodexProvider( default_model=model, proxy=getattr(p, "proxy", None) if p else None, + extra_body=p.extra_body if p else None, ) elif backend == "azure_openai": from nanobot.providers.azure_openai_provider import AzureOpenAIProvider diff --git a/nanobot/providers/openai_codex_provider.py b/nanobot/providers/openai_codex_provider.py index 1db8fa79..0d7eac0b 100644 --- a/nanobot/providers/openai_codex_provider.py +++ b/nanobot/providers/openai_codex_provider.py @@ -37,10 +37,12 @@ class OpenAICodexProvider(LLMProvider): self, default_model: str = "openai-codex/gpt-5.6-sol", proxy: str | None = None, + extra_body: dict[str, Any] | None = None, ): super().__init__(api_key=None, api_base=None) self.default_model = default_model self.proxy = proxy or None + self._extra_body = dict(extra_body or {}) async def _call_codex( self, @@ -74,6 +76,9 @@ class OpenAICodexProvider(LLMProvider): body["reasoning"] = reasoning_options if tools: body["tools"] = convert_tools(tools) + if self._extra_body: + # Apply explicit provider overrides last, matching other provider backends. + body.update(self._extra_body) stage = "oauth_token" try: diff --git a/tests/providers/test_openai_codex_provider.py b/tests/providers/test_openai_codex_provider.py index 6c80ba61..ea5d0c6e 100644 --- a/tests/providers/test_openai_codex_provider.py +++ b/tests/providers/test_openai_codex_provider.py @@ -9,6 +9,8 @@ import pytest from loguru import logger import nanobot.providers.base as provider_base +from nanobot.config.schema import Config +from nanobot.providers.factory import make_provider from nanobot.providers.openai_codex_provider import ( OpenAICodexProvider, _build_reasoning_options, @@ -220,6 +222,38 @@ async def test_codex_prompt_cache_key_uses_stable_conversation_prefix(monkeypatc assert bodies[0]["prompt_cache_key"] == bodies[1]["prompt_cache_key"] assert bodies[0]["prompt_cache_key"] != bodies[2]["prompt_cache_key"] + assert all("service_tier" not in body for body in bodies) + + +@pytest.mark.asyncio +async def test_codex_provider_applies_extra_body_from_config(monkeypatch) -> None: + bodies: list[dict[str, Any]] = [] + _mock_codex_token(monkeypatch) + + async def fake_request(_url, _headers, body, **_kwargs): + bodies.append(body) + return "ok", [], "stop", {}, None + + monkeypatch.setattr("nanobot.providers.openai_codex_provider._request_codex", fake_request) + config = Config.model_validate({ + "agents": { + "defaults": { + "model": "openai-codex/gpt-5.6-sol", + "provider": "openai_codex", + }, + }, + "providers": { + "openaiCodex": { + "extraBody": {"service_tier": "priority"}, + }, + }, + }) + + provider = make_provider(config) + response = await provider.chat([{"role": "user", "content": "hello"}]) + + assert response.content == "ok" + assert bodies[0]["service_tier"] == "priority" @pytest.mark.asyncio