Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs-site/src/content/docs/guides/codex-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,33 @@ reapplies the configured name. Genuine upstream native names (e.g. `gpt-5.6-sol`
"GPT-5.6-Sol") come from the pinned upstream snapshot and are never overridden by a custom display
name.

### Catalog troubleshooting

If a model is missing from Codex, or the catalog order/visibility looks wrong, check in order:

1. **`selectedModels`** on the provider — a non-empty allowlist exposes only those ids to Codex;
Comment on lines +158 to +160

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include disabled providers in the troubleshooting checklist

A provider-level disabled: true is another direct cause of every model from that provider disappearing: gatherRoutedModels() removes disabled providers before discovery, filtering, and catalog generation. Because this new section tells users with missing models to check these items in order but never mentions the provider's disabled switch, they can exhaust the checklist without finding the actual cause. Add the provider-level switch before selectedModels in the sequence.

Useful? React with 👍 / 👎.

empty or omitted exposes all discovered models. An id not in the allowlist never reaches the
catalog.
2. **`disabledModels`** (top level) — hides models from both the catalog and `/v1/models`, and flips
bare native GPT slugs to `visibility: "hide"`.
3. **`liveModels: false` with empty `models`** — when live discovery is off and `models` is empty or
omitted, opencodex exposes no routed models for that provider.
Comment on lines +165 to +166

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope the empty static-catalog guidance

This is not true for every provider: when a built-in provider such as kiro or moonshot omits models, gatherRoutedModels() calls enrichProviderFromRegistry(), which restores that provider's registry-seeded models before honoring liveModels: false; openai-apikey can also add its trusted registry rows even when models is explicitly empty. Users troubleshooting those providers will therefore be told to expect an empty catalog while routed entries remain visible. Please limit this guidance to providers without registry seeds and document the built-in/OpenAI exceptions.

Useful? React with 👍 / 👎.

4. **Cursor `GetUsableModels`** — the Cursor adapter discovers models through its protobuf
`GetUsableModels` RPC, not `/models`, so a Cursor-side change can alter which ids are visible
independently of other providers.
5. **Cache and `ocx sync`** — live catalogs are cached for about five minutes (`modelCacheTtlMs`,
default `300000`). Run `ocx sync` to force a fresh fetch and rewrite the catalog immediately.
Comment on lines +170 to +171

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 \
  'modelCacheTtlMs|ocx sync|invalidate|force.?fetch|cache' .

Repository: lidge-jun/opencodex

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== docs snippet ==\n'
sed -n '160,180p' docs-site/src/content/docs/guides/codex-integration.md

printf '\n== sync references ==\n'
rg -n -C 3 '\bocx sync\b|/api/sync|modelCacheTtlMs|clearModelCache|invalidate.*cache|stale the Codex model cache|rewrite the catalog' \
  docs-site src tests --glob '!**/dist/**' --glob '!**/build/**'

printf '\n== model cache implementation ==\n'
rg -n -C 4 'modelCacheTtlMs|clearModelCache|getStaleCached|setCached|cache.*ttl|ttl.*cache|invalidate.*model cache' src tests --glob '!**/dist/**' --glob '!**/build/**'

Repository: lidge-jun/opencodex

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== sync command files ==\n'
fd -a 'sync' src docs-site tests | sed -n '1,120p'

printf '\n== direct references to ocx sync command ==\n'
rg -n -C 4 'ocx sync|sync command|command.*sync|/api/sync|stale the Codex model cache' src docs-site tests

printf '\n== model-cache file outline ==\n'
fd -a 'model-cache' src tests

Repository: lidge-jun/opencodex

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== candidate model-cache file ==\n'
fd -a 'model-cache.ts' src

printf '\n== candidate sync implementation files ==\n'
fd -a 'sync' src | sed -n '1,120p'

Repository: lidge-jun/opencodex

Length of output: 315


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 '/api/sync|stale the Codex model cache|clearModelCache\(|modelCacheTtlMs: 0|sync.*model cache|refresh.*catalog' \
  src docs-site tests

Repository: lidge-jun/opencodex

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'clearModelCache|setCached\(|getStaleCached\(|modelCacheTtlMs|sync' src/codex src/cli src | head -n 200

Repository: lidge-jun/opencodex

Length of output: 12880


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== src/codex/sync.ts ==\n'
sed -n '1,140p' src/codex/sync.ts

printf '\n== src/codex/model-cache.ts ==\n'
sed -n '1,180p' src/codex/model-cache.ts

printf '\n== refreshCodexModelCatalog references ==\n'
rg -n -C 6 'refreshCodexModelCatalog|syncModelsToCodex|clearModelCache\(' src/codex src/server src/cli

Repository: lidge-jun/opencodex

Length of output: 31145


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== src/codex/refresh.ts ==\n'
sed -n '1,220p' src/codex/refresh.ts

printf '\n== sync/catalog call sites ==\n'
rg -n -C 4 'syncCatalogModels|refreshCodexModelCatalog|gatherRoutedModels|modelCacheTtlMs' src/codex src/server src/cli

printf '\n== model-cache usage in refresh path ==\n'
rg -n -C 4 'clearModelCache\(|setCached\(|getFreshCached\(|getStaleCached\(' src/codex src/server

Repository: lidge-jun/opencodex

Length of output: 23832


Clarify ocx sync here. src/codex/sync.ts only rebuilds the on-disk Codex catalog and invalidates Codex’s models_cache.json; it still calls gatherRoutedModels() through the normal per-provider cache path, so a warm modelCacheTtlMs entry can be reused instead of forcing a live fetch. Reword this to say ocx sync refreshes the catalog and Codex cache, while live provider discovery still honors the TTL unless the provider cache is stale or cleared.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/guides/codex-integration.md` around lines 170 -
171, Reword the “Cache and ocx sync” guidance to clarify that ocx sync rebuilds
the on-disk Codex catalog and invalidates Codex’s models_cache.json, but does
not force live provider discovery. State that gatherRoutedModels() continues
using the per-provider modelCacheTtlMs cache, with fresh discovery occurring
only when that cache is stale or cleared.

Source: Path instructions


:::caution[Other local writers]
Catalog writes (`opencodex-catalog.json`, `config.toml`) are atomic **inside** opencodex, which only
prevents half-written files when two opencodex-owned writers race. That does **not** stop another
local process, file watcher, or sync agent from rewriting catalog visibility or order after opencodex
has written. Codex keeps its separate `models_cache.json` and can refresh it independently, changing
the visible list without rewriting `opencodex-catalog.json`. If models flip unexpectedly while the
proxy is running, stop or reconfigure the competing writers, then run `ocx sync` — this is an
external-writer hazard, not a confirmed opencodex defect.
:::

## The subagent picker

Codex's `spawn_agent` advertises the first **5 picker-visible catalog models** after sorting by
Expand Down
5 changes: 3 additions & 2 deletions docs-site/src/content/docs/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,9 @@ advertised effort control on those models as proof of upstream-native reasoning
`cursor/auto-balance`, and `cursor/auto-intelligence` entries. Explicit levels are encoded in
`requested_model.parameters` while the legacy `cursor/auto` entry retains the account/team default.
- Cursor-native local filesystem/shell/network execution is denied by default. Explicit `mcpServers`
and `desktopExecutor` integrations have separate opt-ins; `unsafeAllowNativeLocalExec` enables the
broader built-in executor and bypasses Codex approval/sandbox semantics.
and `desktopExecutor` integrations have separate opt-ins; `nativeLocalExec: "on"` enables the
broader built-in executor and bypasses Codex approval/sandbox semantics, and legacy
`unsafeAllowNativeLocalExec: true` remains equivalent only when `nativeLocalExec` is unset.
Comment on lines +137 to +139

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Make legacy precedence fail closed for invalid selectors.

The documentation says the legacy boolean applies only when nativeLocalExec is unset and that explicit values always win, but the resolver falls back to the legacy opt-in for any unrecognized value. A malformed selector combined with unsafeAllowNativeLocalExec: true can therefore enable the broader executor unexpectedly.

  • docs-site/src/content/docs/reference/adapters.md#L137-L139: keep the documented precedence only after the resolver treats invalid explicit values as fail-closed.
  • docs-site/src/content/docs/reference/configuration.md#L190-L191: align the field contract with the resolver’s actual invalid-value behavior.
  • docs-site/src/content/docs/reference/configuration.md#L269-L271: retain “explicit value always wins” only after invalid values no longer fall through to the legacy flag.
📍 Affects 2 files
  • docs-site/src/content/docs/reference/adapters.md#L137-L139 (this comment)
  • docs-site/src/content/docs/reference/configuration.md#L190-L191
  • docs-site/src/content/docs/reference/configuration.md#L269-L271
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/reference/adapters.md` around lines 137 - 139,
Update the nativeLocalExec resolver so any explicitly provided unrecognized
value disables the broader executor and never falls back to
unsafeAllowNativeLocalExec; retain legacy fallback only when nativeLocalExec is
unset. Align docs-site/src/content/docs/reference/adapters.md lines 137-139,
docs-site/src/content/docs/reference/configuration.md lines 190-191, and
docs-site/src/content/docs/reference/configuration.md lines 269-271 with this
fail-closed behavior, preserving “explicit value always wins” only for valid
explicit values.

Source: Path instructions


## `azure-openai` (alias: `azure`)

Expand Down
38 changes: 27 additions & 11 deletions docs-site/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ network. Only do this on trusted networks, and always set a strong `OPENCODEX_AP
| `location?` | `string` | Vertex location; environment fallback is `GOOGLE_CLOUD_LOCATION`. |
| `mcpServers?` | `Record<string,CursorMcpServerConfig>` | **Cursor only.** MCP servers started over stdio or reached over Streamable HTTP; fields are listed below. |
| `desktopExecutor?` | `DesktopExecutorConfig` | **Cursor only.** External computer-use/record-screen commands; fields are listed below. |
| `unsafeAllowNativeLocalExec?` | `boolean` | **Cursor adapter only.** Opt-in escape hatch for Cursor server-driven local `read` / `write` / `delete` / `ls` / `grep` / `shell` / `fetch` execution. Defaults to `false` so remote Cursor messages cannot bypass Codex approval and sandbox enforcement. See [Cursor provider](#cursor-provider-adapter-cursor) below. |
| `unsafeAllowNativeLocalExec?` | `boolean` | **Cursor adapter only.** Legacy compatibility boolean for the Cursor server-driven local `read` / `write` / `delete` / `ls` / `grep` / `shell` / `fetch` executor. Equivalent to `nativeLocalExec: "on"` when `nativeLocalExec` is unset; an explicit `nativeLocalExec` value always wins. Defaults to `false`. Prefer `nativeLocalExec` for new configs. See [Cursor provider](#cursor-provider-adapter-cursor) below. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | **Cursor adapter only.** Native local exec policy for the Cursor server-driven executor. `"off"` (default) rejects it; `"on"` is the trusted-local opt-in; `"codex-sandbox"` is accepted for backwards compatibility but is fail-closed like `"off"`. See [Cursor provider](#cursor-provider-adapter-cursor) below. |

For broken `openai-responses` compatibility gateways, `responsesItemIdRepair` belongs on the
provider object itself, for example:
Expand Down Expand Up @@ -232,9 +233,17 @@ the selection is preserved on every request. They remain available when live mod
`default`, just like the original `cursor/auto` entry.

By default, Cursor's server-driven native local tools stay **disabled**. Codex keeps using its own
tools (`apply_patch`, `exec_command`, and so on) with approval and sandbox policy. Set
`unsafeAllowNativeLocalExec` only for trusted local experiments where you accept that Cursor may
read, write, delete, list, grep, shell, or fetch on your machine **without** Codex's approval path.
tools (`apply_patch`, `exec_command`, and so on) with approval and sandbox policy. Use the
`nativeLocalExec` field to choose a policy:

- **`"off"` (default, safest)** — rejects all Cursor server-driven local `read`, `write`, `delete`,
`ls`, `grep`, `shell`, and `fetch` execution. Use this unless you have deliberately opted in.
- **`"on"` (trusted-local opt-in)** — always allows Cursor-native local execution for this provider.
Use it only for a trusted local experiment on a host where every data-plane caller is trusted.
Comment on lines +241 to +242

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the apply_patch mutation exception

In normal Codex requests that advertise apply_patch, this mode does not always allow every listed native operation: LiveCursorTransport.run() sets rejectNativeFileMutations, and handleCursorNativeExec() then rejects Cursor-native write and delete calls even when the exec policy resolves to "on". The unconditional wording can make operators expect a fully enabled native executor while mutations continue to fail; document that write and delete remain delegated to apply_patch whenever that client tool is available.

Useful? React with 👍 / 👎.

- **`"codex-sandbox"` (accepted, fail-closed)** — recognized for backwards compatibility, but
currently behaves like `"off"`. Responses `instructions` / `system` / `developer` text is
caller-controlled prose, and opencodex has no trustworthy per-request attestation that it reflects
a real Codex sandbox state, so it never authorizes native local exec.

```json
{
Expand All @@ -244,21 +253,25 @@ read, write, delete, list, grep, shell, or fetch on your machine **without** Cod
"baseUrl": "https://api2.cursor.sh",
"authMode": "oauth",
"defaultModel": "auto",
"unsafeAllowNativeLocalExec": true
"nativeLocalExec": "off"
}
}
}
```

The flag belongs on the **provider object** (`providers.cursor`), not at the top level of
The field belongs on the **provider object** (`providers.cursor`), not at the top level of
`config.json`.

You can also set it from the [web dashboard](/opencodex/guides/web-dashboard/): **Providers →
Cursor → Edit JSON**, add `"unsafeAllowNativeLocalExec": true`, save, then restart the proxy
(`ocx restart` or `ocx stop` + `ocx start`).
Cursor → Edit JSON**, set `"nativeLocalExec"` to `"off"`, `"on"`, or `"codex-sandbox"`, save, then
restart the proxy (`ocx restart` or `ocx stop` + `ocx start`).

The legacy `unsafeAllowNativeLocalExec: true` boolean is still accepted and is equivalent to
`nativeLocalExec: "on"` when `nativeLocalExec` is unset; an explicit `nativeLocalExec` value always
wins. Prefer `nativeLocalExec` for new configs.
Comment on lines +269 to +271

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the legacy enablement instructions together

The newly documented precedence makes the existing provider guide incorrect for users who first copy this page's example with nativeLocalExec: "off": guides/providers.md still tells them to enable execution by adding only unsafeAllowNativeLocalExec: true, but the explicit "off" wins and execution remains disabled. The Japanese, Korean, Russian, and Chinese provider/reference pages repeat the same unconditional legacy instruction and omit the new modes, so update those pages to use nativeLocalExec: "on" or clearly explain the precedence.

AGENTS.md reference: AGENTS.md:L78-L79

Useful? React with 👍 / 👎.


MCP, screen recording, and computer-use use separate `mcpServers` / `desktopExecutor` config and are
not controlled by this flag.
not controlled by this field.

### Cursor integration records

Expand All @@ -272,8 +285,11 @@ accept `headers?: Record<string,string>`. Both forms support `enabled?: boolean`
one JSON request from stdin, and must write one JSON result to stdout.

:::caution[Security]
Leave `unsafeAllowNativeLocalExec` unset or `false` unless you explicitly want Cursor-native local
execution that bypasses Codex approval and sandbox semantics.
`"off"` is the safest default. The default loopback bind admits **any** local process without auth
(including other local users on multi-user machines), and request text such as Codex sandbox
markers never authorizes native local exec. Leave `nativeLocalExec` unset or set it to `"off"`
unless you explicitly want Cursor-native local execution that bypasses Codex approval and sandbox
semantics.
:::

## OpenRouter provider routing
Expand Down
Loading