Trim of the salvaged openai-native marker provider (#107377): - `hermes auth --provider openai-codex` does not exist; the login command is `hermes auth add openai-codex` (hermes_cli/subcommands/auth.py). Fixed in plugin.yaml, provider.py docstring and the web-search docs (2 spots). - `hermes tools` picker tag was Chinese; now English like every other provider. - tests/plugins/web/test_web_search_provider_plugins.py enumerates the bundled registry exactly, so the new plugin made it fail; it now lists openai-native with capability flags (search=True, extract=False) — the search-only invariant that keeps web_extract on its own backend. - contributors/emails mapping for the salvaged author.
89 lines
3.9 KiB
Python
89 lines
3.9 KiB
Python
"""OpenAI native web search — declares the Responses API server-side ``web_search`` built-in.
|
|
|
|
Config: ``web.search_backend: openai-native`` (or ``web.backend``).
|
|
Auth: openai-codex OAuth (``hermes auth add openai-codex``); no API key of its own.
|
|
|
|
Unlike every other provider here, this one never executes a search itself. Selecting it
|
|
tells the Codex Responses transport to declare the provider-executed ``web_search`` tool
|
|
(``{"type": "web_search"}``) in place of the client-side ``web_search`` function, so the
|
|
model drives search server-side. The transport performs that swap; this class exists so
|
|
``web.search_backend`` has a real provider name to point at.
|
|
|
|
Auth gating lives here rather than in the transport because the transport must stay
|
|
reachable for a user who configured this backend but has not signed in yet — they get the
|
|
clear "sign in" error from :meth:`search` rather than a silently different backend.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any, Dict
|
|
|
|
from plugins.web._common import BaseWebSearchProvider, search_fail
|
|
|
|
_UNSUPPORTED_MSG = (
|
|
"openai-native declares OpenAI's server-side web_search tool; it cannot run as a "
|
|
"client-side search and requires the Codex Responses transport (provider "
|
|
"openai-codex). For client-side search use firecrawl (default) or another backend."
|
|
)
|
|
|
|
|
|
def _dget(obj: Any, key: str) -> Any:
|
|
return obj.get(key) if isinstance(obj, dict) else None
|
|
|
|
|
|
def has_codex_credentials() -> bool:
|
|
"""Cheap probe: True when openai-codex OAuth tokens are *likely* usable.
|
|
|
|
Mirrors ``tools/xai_http.has_xai_credentials`` — deliberately avoids
|
|
``resolve_codex_runtime_credentials`` (disk locks, OAuth network refresh), because
|
|
this runs on every ``hermes tools`` repaint. Checks, fast-to-slow:
|
|
``providers.openai-codex.tokens.access_token`` in ``auth.json``, then any
|
|
``credential_pool.openai-codex`` entry carrying an ``access_token`` (pool-only
|
|
multi-account grants never write the providers singleton). Returns False on any
|
|
exception so a corrupted auth store cannot block other availability scans.
|
|
"""
|
|
try:
|
|
from hermes_constants import get_hermes_home
|
|
|
|
auth_path = get_hermes_home() / "auth.json"
|
|
if not auth_path.exists():
|
|
return False
|
|
store = json.loads(auth_path.read_text(encoding="utf-8-sig"))
|
|
tokens = _dget(_dget(_dget(store, "providers"), "openai-codex"), "tokens")
|
|
if str(_dget(tokens, "access_token") or "").strip():
|
|
return True
|
|
entries = _dget(_dget(store, "credential_pool"), "openai-codex")
|
|
return isinstance(entries, list) and any(
|
|
isinstance(e, dict) and str(e.get("access_token", "") or "").strip() for e in entries
|
|
)
|
|
except Exception: # noqa: BLE001 — availability must never raise
|
|
return False
|
|
|
|
|
|
class OpenAINativeWebSearchProvider(BaseWebSearchProvider):
|
|
"""Marker provider: the Codex Responses transport swaps the client ``web_search``
|
|
function for the server-executed built-in when this backend is active."""
|
|
|
|
NAME = "openai-native"
|
|
DISPLAY_NAME = "OpenAI Native Web Search (Codex Responses)"
|
|
|
|
def is_available(self) -> bool:
|
|
return has_codex_credentials()
|
|
|
|
def search(self, query: str, limit: int = 5) -> Dict[str, Any]:
|
|
"""Never called on a successful native turn — the transport replaces the tool
|
|
before the request goes out. Reached only when the active transport cannot host
|
|
the built-in, so fail loudly instead of returning an empty result set."""
|
|
return search_fail(_UNSUPPORTED_MSG)
|
|
|
|
def get_setup_schema(self) -> Dict[str, Any]:
|
|
from plugins.web._common import setup_schema
|
|
|
|
return setup_schema(
|
|
self.DISPLAY_NAME,
|
|
"native",
|
|
"Search runs on the provider side (needs the Codex Responses transport + an openai-codex login); search only, extraction still uses another backend",
|
|
"",
|
|
)
|