😏
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
# 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 Codex’s native authentication, entitlement, tools, images, and active model catalog. A generated home uses the configured base home’s 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 0–10.
|
||||
|
||||
### 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.0’s 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, 0–10 |
|
||||
| `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.
|
||||
Reference in New Issue
Block a user