Files
codex-mmo/docs/PROVIDER_DRIVERS.md
2026-08-24 08:11:59 -07:00

246 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Route drivers
Codex MMO is model-maker neutral, but protocols and access products are not interchangeable. In MMO generation 8, an executable `route` selects one driver and records its API operator, access product, wire protocol, billing mode, transport capabilities, credential names, and resource group.
The filename remains `PROVIDER_DRIVERS.md` for documentation organization only; the public catalog object is `routes`, not the old provider-shaped schema.
## Driver matrix
| Driver | Execution path | Typical use | Managed gateway |
|---|---|---|:---:|
| `codex_builtin` | Codex reserved provider | ChatGPT-backed Codex models | no |
| `codex_custom` | Direct Codex Responses endpoint | Conforming private/third-party Responses API | no |
| `codex_oss` | Codex native local integration | Ollama or LM Studio | no |
| `switchyard` | Immutable snapshot-specific route | Chat, Responses, Anthropic Messages, compatible local APIs | yes |
| `catalog_only` | Metadata only | Media/service endpoint or unsupported transport | no execution |
## Common route identity
```toml
[routes.example_openai_chat]
name = "Example API"
api_operator = "example"
access_product = "example_api"
wire_protocol = "openai_chat"
billing_mode = "api"
driver = "switchyard"
base_url = "https://models.example.test/v1"
credential_envs = ["EXAMPLE_API_KEY"]
resource_group = "example_pool"
transport_modalities = ["text"]
transport_output_modalities = ["text"]
tool_calling = true
parallel_tool_calls = false
max_retries = 1
```
Required identity fields are never inferred from a route key. A recognized field that does not apply to the selected driver is rejected instead of retained inertly.
## `codex_builtin`
```toml
[routes.codex_chatgpt_builtin]
name = "Built-in Codex with ChatGPT authentication"
api_operator = "openai"
access_product = "chatgpt_codex"
wire_protocol = "codex_builtin"
billing_mode = "chatgpt_subscription"
driver = "codex_builtin"
provider_id = "openai"
auth = "chatgpt"
resource_group = "chatgpt_subscription"
transport_modalities = ["text", "image"]
tool_calling = true
parallel_tool_calls = true
```
This preserves Codexs native authentication, entitlement, tools, images, and active model catalog. A generated home uses the configured base homes file-backed `auth.json` according to `auth_link_mode`; Codex 0.149 keyring-only state cannot be transferred to another `CODEX_HOME`.
Account-visible models and capabilities remain authoritative. The bundled catalog is a release baseline, not an entitlement claim.
## `codex_custom`
```toml
[routes.private_responses]
name = "Private Responses"
api_operator = "example"
access_product = "private_responses"
wire_protocol = "openai_responses"
billing_mode = "api"
driver = "codex_custom"
base_url = "https://llm.example.internal/v1"
wire_api = "responses"
credential_envs = ["PRIVATE_LLM_API_KEY"]
transport_modalities = ["text", "image"]
preserves_tool_media = true
tool_result_modalities = ["text", "image"]
[routes.private_responses.http_headers]
"X-Static-Tenant" = "engineering"
[routes.private_responses.env_http_headers]
"X-Private-Token" = "PRIVATE_HEADER_VALUE"
```
Use direct mode only when the endpoint faithfully implements Responses and direct transport is preferable to translation. The Codex process necessarily receives declared credential/header variables, so place sensitive work in a suitable OS boundary.
`wire_api` is currently `responses`. Direct retry fields are `request_max_retries`, `stream_max_retries`, and `stream_idle_timeout_ms`. Codex interprets zero as immediate expiry, not “disabled,” so route validation rejects it. Generated homes raise the effective idle floor to at least one hour and twice the role's warning-only stall interval. That value detects a genuinely broken stream; it is not a task deadline, goal budget, or instruction for the model to keep time.
## `codex_oss`
```toml
[routes.ollama_codex_oss]
name = "Codex native Ollama OSS mode"
api_operator = "local"
access_product = "ollama"
wire_protocol = "codex_oss"
billing_mode = "local"
driver = "codex_oss"
provider_id = "ollama"
resource_group = "local_gpu_0"
transport_modalities = ["text"]
```
Supported `provider_id` values are `ollama` and `lmstudio`. Switchyard variants also exist for compatible local Chat endpoints when common routing and telemetry matter more than native transport.
## `switchyard`
Switchyard 0.2.0 supports these route protocols:
- `openai_chat`;
- `openai_responses`;
- `anthropic_messages`.
The compiler emits only routes used by one immutable snapshot. A profile with no Switchyard binding starts no gateway. Managed ingress is restricted to a loopback IP literal because the generated gateway has no client-auth layer.
Bundled Switchyard routes include:
- OpenCode Go Chat, Responses, and Anthropic Messages through `OPENCODE_API_KEY`;
- OpenCode Zen Chat, Responses, and Anthropic Messages through the same `OPENCODE_API_KEY`;
- OpenRouter Chat through `OPENROUTER_API_KEY`;
- Z.AI Coding Plan Chat and Responses through `ZAI_CODING_API_KEY`;
- Z.AI General API through the separately explicit `ZAI_API_KEY`;
- a bundled llama.cpp model binding;
- direct OpenAI/Anthropic and LM Studio/Ollama route definitions for explicit
user catalog extensions. A route without a model record is not an executable
bundled model binding.
Switchyard receives `extra_headers`, not environment-backed per-header mappings. API authentication uses `credential_envs`. MMO emits `max_retries = 1` by default, meaning one additional attempt; the accepted range is 010.
### Temporary Codex MCP namespace bridge
Codex 0.149 represents MCP servers as Responses `namespace` tools. Switchyard 0.2.0's Responses decoder retains top-level function and ID tools but does not retain that namespace shape. For a gateway whose recorded executable version is exactly `0.2.0`, MMO therefore asks the same Codex app-server for its granted MCP tool schemas, registers equivalent flat `mmo_mcp__SERVER__TOOL` dynamic functions when a thread is first created, and routes each dynamic call back through Codex's `mcpServer/tool/call`. The MMO-owned prefix is deliberate because Codex rejects dynamic declarations using its reserved native `mcp__` prefix. Codex remains the MCP connection owner and enforces its configured server/tool allowlists; the bridge does not read supervisor files or connect to MCP servers independently. MCP text, image, audio, error state, and non-redundant structured content are preserved; a structured value is omitted only when an existing text block parses to that exact value (including the common single-key result wrapper), avoiding duplicate context without discarding distinct information. Durable Codex threads retain the dynamic tool declarations, while every replacement host reconstructs only the in-memory routing map.
This is a release-scoped compatibility shim, not a second permanent MCP architecture. It is disabled for every Switchyard version other than `0.2.0`. The baseline/bridge guard test deliberately fails when the reviewed Switchyard baseline changes. Before accepting a newer baseline:
1. prove with the exact candidate binary that Codex 0.149 namespace tools reach both the Go and Zen model routes and that a real MCP call completes;
2. prove new-thread, resumed-thread, worker, allowlist, error, and cancellation behavior without `dynamicTools`;
3. remove the bridge constants, dynamic schema discovery/call routing, root/worker injection, and their compatibility tests;
4. update this section, the architecture/veracity records, changelog, package manifest, and installed runtime.
An operator-supplied newer Switchyard is never assigned the 0.2.0 shim merely because the profile uses the `switchyard` driver. Its native namespace path must pass live qualification before production use.
#### Open removal tracker
Upstream [Switchyard PR #384](https://github.com/NVIDIA-NeMo/Switchyard/pull/384), merged as commit [`c7beccd4891fa5cfe3a3b94fdd376f5765864507`](https://github.com/NVIDIA-NeMo/Switchyard/commit/c7beccd4891fa5cfe3a3b94fdd376f5765864507) on 2026-08-20, implements native Codex namespace preservation. The current `v0.2.0` tag predates that commit, and no published tag contains it as of 2026-08-23. This tracker remains open until the first official `switchyard-server` release whose tag contains that commit is available.
The release containing that commit is an upgrade/removal trigger, not permission to retain both paths. The baseline bump must be one atomic change: run the qualification matrix above against the exact released binary, delete the dynamic bridge and its version constant, then update the pin. The existing `test_temporary_switchyard_bridge_must_be_removed_when_baseline_advances` test blocks a baseline-only update. If native qualification fails, keep `0.2.0` pinned and record the failure; do not extend the bridge to the newer release.
Z.AI Coding Plan uses distinct documented bases by wire protocol: Responses/Codex uses `https://api.z.ai/api/v1`, while OpenAI-compatible Chat uses `https://api.z.ai/api/coding/paas/v4`. Switchyard appends the protocol endpoint suffix. MMO does not retry one route through the other and does not fall back to the separately credentialed General API.
The Coding Plan Anthropic-compatible endpoint remains catalog-only: its documented Bearer authentication cannot be represented by Switchyard 0.2.0s fixed Anthropic `x-api-key` behavior. No hidden transport fallback is attempted.
## `catalog_only`
Catalog-only records preserve complete inventory information without pretending the endpoint can run a Codex agent. Examples include OCR, image/video generation, audio transcription, hosted application agents, Google-native Zen models without an installed driver, and the incompatible Coding Plan Anthropic route.
```toml
[routes.zai_general_catalog]
name = "Z.AI media and specialist API catalog"
api_operator = "zai"
access_product = "zai_general_api"
wire_protocol = "catalog_only"
billing_mode = "catalog_only"
driver = "catalog_only"
transport_modalities = ["text", "image", "video", "audio", "file"]
[models.zai_general_catalog__glm_image]
maker = "zai"
route = "zai_general_catalog"
upstream_id = "glm-image"
kind = "image_generation"
agent_compatible = false
```
## Driver-specific fields
| Field | Drivers | Meaning |
|---|---|---|
| `api_operator` | all | API operator identity |
| `access_product` | all | Plan/service identity |
| `wire_protocol` | all | Exact protocol enum |
| `billing_mode` | all | `api`, `subscription`, `chatgpt_subscription`, `local`, or `catalog_only` |
| `provider_id` | built-in/OSS | Codex-owned provider identifier |
| `base_url` | custom/Switchyard/catalog-only | Absolute HTTP(S) API root |
| `credential_envs` | custom/Switchyard/catalog-only | Ordered valid environment names |
| `wire_api` | custom | Direct Codex API; `responses` |
| `http_headers` | custom | Static Codex headers |
| `env_http_headers` | custom | Header name to environment name |
| `extra_headers` | Switchyard | Static Switchyard headers |
| `max_retries` | Switchyard | Additional attempts, 010 |
| `request_max_retries` | custom | Direct request retries |
| `stream_max_retries` | custom | Stream reconnections |
| `stream_idle_timeout_ms` | custom | Provider stream-idle floor (at least 1,000 ms); generated app-server configs raise it through the role's execution window plus finalization grace when that is longer |
| `transport_modalities` | all | Inputs the complete path preserves |
| `transport_output_modalities` | all | Outputs the complete path preserves |
| `preserves_tool_media` | all | Whether tool media survives transport |
| `tool_result_modalities` | all | Preserved tool-result media types |
| `supports_documents` | all | Complete file/document path |
| `openrouter_policy` | OpenRouter only | Route-wide typed serving policy |
There are no singular credential aliases or legacy header-name aliases in the v2 contract. Use exactly `credential_envs`, `http_headers`, `env_http_headers`, and `extra_headers` for their documented drivers.
`base_url` must be an absolute HTTP(S) root with a nonempty host. Userinfo, control/space characters, backslashes, queries, and fragments are rejected. Header names and values are validated before downstream configuration is generated.
## Model transport fields
```toml
[models.example_openai_chat__example_coder]
maker = "example"
route = "example_openai_chat"
upstream_id = "example-coder"
kind = "chat"
agent_compatible = true
modalities = ["text"]
output_modalities = ["text"]
tool_calling = true
parallel_tool_calls = false
structured_output = true
context_window = 200000
max_output_tokens = 32000
reasoning_levels = ["none", "high"]
default_reasoning = "high"
resource_group = "example_pool"
```
The compiler checks model capabilities against route capabilities. A maker/model name alone is never evidence that a particular transport preserves tools, images, documents, reasoning selectors, or structured output. `extra_body` is supported only for Switchyard-backed model targets and must contain finite JSON-compatible values.
## Credentials
Credential assignments belong in `~/.config/codex-mmo/credentials.env` or the launch environment. Profiles, catalogs, snapshots, and generated static configuration store names, never values. Worker/gateway environments filter conventional unrelated credential names and explicitly re-admit only the selected route and Tool MCP variables.
That filtering is name-based, not an OS sandbox or secret oracle. Use conventional secret names and sanitize unusual variables before launching untrusted work.
## Adding a route
Add a generation-8 fragment under `~/.config/codex-mmo/catalog.d/`, then run:
```bash
codex-mmo catalog verify
codex-mmo catalog routes
codex-mmo catalog models --route ROUTE_KEY
codex-mmo profile validate PROFILE
```
No runtime plugin is needed when an existing driver fits. Executable driver plugins are intentionally outside profile packs.