11"""Runtime helpers for Oh My Pi (omp) integrations.
22
3- omp resolves its Anthropic chat endpoint from the model registry
4- (``providers.anthropic .baseUrl`` in ``~/.omp/agent/models.yml``), not from
3+ omp resolves its Anthropic and OpenAI provider endpoints from the model registry
4+ (``providers.<id> .baseUrl`` in ``~/.omp/agent/models.yml``), not from
55``ANTHROPIC_BASE_URL`` — that env var only feeds omp's web-search helper.
66Verified empirically: with ``ANTHROPIC_BASE_URL`` pointed at a local probe
77server, omp's chat traffic still went to the real Anthropic endpoint; with a
88``models.yml`` same-ID override, every ``/v1/messages`` request arrived at the
9- probe. A same -ID override keeps omp's bundled Anthropic model catalog and
10- stored credentials (both keyed by provider id ``anthropic``) , so only the
11- endpoint moves.
12-
13- The wrap therefore injects a marker-fenced ``providers.anthropic .baseUrl``
14- override into ``models.yml``, snapshotting the pre-wrap file byte-for-byte
15- first — the same durable-wrap + backup + ``headroom unwrap`` contract the
16- Codex wrap uses for ``config.toml`` .
9+ probe. Same -ID overrides keep omp's bundled model catalog and stored
10+ credentials, so only the endpoints move.
11+
12+ The wrap injects marker-fenced ``providers.anthropic.baseUrl``,
13+ ``providers.openai.baseUrl``, and ``providers.openai-codex .baseUrl`` overrides
14+ into ``models.yml``, snapshotting the pre-wrap file byte-for-byte first. OMP's
15+ Codex provider appends ``/codex/responses``; Headroom already aliases that
16+ path. Gemini and other providers remain untouched .
1717"""
1818
1919from __future__ import annotations
2323from pathlib import Path
2424
2525from headroom .providers .claude import proxy_base_url as claude_proxy_base_url
26+ from headroom .providers .codex import proxy_base_url as codex_proxy_base_url
2627from headroom .proxy .project_context import with_project_prefix
2728
2829MANAGED_MARKER = "# managed by `headroom wrap omp`"
@@ -62,6 +63,11 @@ def proxy_anthropic_base_url(port: int, project: str | None = None) -> str:
6263 return with_project_prefix (claude_proxy_base_url (port ), project )
6364
6465
66+ def proxy_openai_base_url (port : int , project : str | None = None ) -> str :
67+ """Proxy base URL omp's OpenAI and Codex providers are pointed at."""
68+ return with_project_prefix (codex_proxy_base_url (port ), project )
69+
70+
6571def is_managed (models_file : Path ) -> bool :
6672 """Whether ``models_file`` is currently a wrap-managed override."""
6773 if not models_file .exists ():
@@ -74,25 +80,26 @@ def is_managed(models_file: Path) -> bool:
7480
7581
7682def inject_models_override (port : int , project : str | None = None ) -> tuple [Path , str ]:
77- """Point ``providers.anthropic.baseUrl`` at the local proxy .
83+ """Point omp's Anthropic, OpenAI, and Codex provider endpoints at Headroom .
7884
79- Returns ``(models_file, base_url )``.
85+ Returns ``(models_file, anthropic_base_url )`` for backward compatibility .
8086
8187 * First injection snapshots the user's pre-wrap file byte-for-byte to
82- ``models.yml.headroom-backup`` so unwrap can restore it exactly. A file
88+ ``models.yml.headroom-backup`` so unwrap can restore it exactly. A file
8389 that is already wrap-managed is never re-snapshotted (that would clobber
8490 the pristine backup — same guard as the Codex config snapshot).
8591 * The managed file is regenerated from the backup (or from scratch when
8692 the user had no ``models.yml``) on every call, so re-running with a
87- different ``--port`` updates the override idempotently.
93+ different ``--port`` updates the overrides idempotently.
8894 * Any user-defined providers/models from the pre-wrap file are preserved:
89- the override only deep-sets ``providers.anthropic. baseUrl``.
95+ the override only deep-sets each supported provider's `` baseUrl``.
9096 """
9197 import yaml # type: ignore[import-untyped] # PyYAML ships no stubs; lint env installs no deps
9298
9399 models_file = models_yml_path ()
94100 backup = backup_path (models_file )
95101 base_url = proxy_anthropic_base_url (port , project )
102+ openai_base_url = proxy_openai_base_url (port , project )
96103
97104 original_bytes : bytes | None = None
98105 if backup .exists ():
@@ -119,11 +126,16 @@ def inject_models_override(port: int, project: str | None = None) -> tuple[Path,
119126 if not isinstance (providers , dict ): # malformed user value: keep it in backup only
120127 providers = {}
121128 data ["providers" ] = providers
122- anthropic = providers .setdefault ("anthropic" , {})
123- if not isinstance (anthropic , dict ):
124- anthropic = {}
125- providers ["anthropic" ] = anthropic
126- anthropic ["baseUrl" ] = base_url
129+ for provider_name , provider_base_url in (
130+ ("anthropic" , base_url ),
131+ ("openai" , openai_base_url ),
132+ ("openai-codex" , openai_base_url ),
133+ ):
134+ provider = providers .setdefault (provider_name , {})
135+ if not isinstance (provider , dict ):
136+ provider = {}
137+ providers [provider_name ] = provider
138+ provider ["baseUrl" ] = provider_base_url
127139
128140 models_file .parent .mkdir (parents = True , exist_ok = True )
129141 rendered = yaml .safe_dump (data , sort_keys = False , default_flow_style = False )
@@ -157,10 +169,15 @@ def build_launch_env(
157169) -> tuple [dict [str , str ], list [str ]]:
158170 """Build the launch environment and display lines for the omp wrap.
159171
160- The endpoint redirect itself lives in ``models.yml`` (see module
172+ The endpoint redirects themselves live in ``models.yml`` (see module
161173 docstring), so the environment passes through unchanged; the display
162- lines surface where the override went.
174+ lines surface where the overrides went.
163175 """
164176 env = dict (environ or os .environ )
165177 base_url = proxy_anthropic_base_url (port , project )
166- return env , [f"models.yml: providers.anthropic.baseUrl={ base_url } " ]
178+ openai_base_url = proxy_openai_base_url (port , project )
179+ return env , [
180+ f"models.yml: providers.anthropic.baseUrl={ base_url } " ,
181+ f"models.yml: providers.openai.baseUrl={ openai_base_url } " ,
182+ f"models.yml: providers.openai-codex.baseUrl={ openai_base_url } " ,
183+ ]
0 commit comments