Skip to content

Fix nested model selectors in Pi exports - #8287

Draft
lidge-jun wants to merge 4 commits into
devfrom
codex/8172-pi-selector
Draft

lidge-jun wants to merge 4 commits into
devfrom
codex/8172-pi-selector

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Pi exports routed models under the proxy's encoded key, so OpenRouter's nested openrouter/free is exported as openrouter/openrouter-free. That key routes only while the router's inventory can decode it; with a cold inventory the proxy forwards openrouter-free and the upstream rejects it (#8172).

Following the owner decision on #8172 (the serving proxy is authoritative; ambiguous, colliding, wrong-model and cold-inventory routes are refused), Pi now gets the raw selector only where the serving proxy proves it:

  • src/clients/config-export/nested-selectors.ts runs inside loadExportModels against the admitted configuration and router inventory of that load. The raw selector, and its --fast form for a Fast row, must reach the encoded key's provider, model and account, and no other row of the full roster may claim it. The proof is the alias proof, factored out of alias-selectors.ts into proveExportSelector and uniqueExportCandidates.
  • Cold inventory, a self-namespaced model that owns the raw spelling (acme/org/model next to org/model), a policy or combo result, or a collision keeps the encoded key, which is today's output.
  • ocx export --client pi fetches its document from /api/client-config, the route the dashboard download already uses, so the CLI's saved configuration never proves a selector.
  • A managed Pi Apply or overwrite re-proves captured selectors against the live configuration (model-name-guard.ts) and refuses with conflict if one changed.
  • Explicit aliases, native rows and other Pi-format clients (omo, Gentle Shell) keep their keys. The Pi guide (English and seven locales) and structure/clients/export-model-names.md describe the rule.

Carries the approach from #8171; the first commit includes the original author's Co-authored-by trailer.

Closes #8172

Verification

All runs used Bun 1.4.4 with env -i and temporary HOME, OPENCODEX_HOME, CODEX_HOME and CLAUDE_CONFIG_DIR.

  • Failing-first: tests/cli/cli-export-command.test.ts "the serving proxy proves the raw selector…" (serving config knows org/free, saved config does not) fails with either the CLI routing change or the loadExportModels attachment reverted, and passes with both. The guard case in tests/clients/client-export-model-names.test.ts fails with model-name-guard.ts reverted.
  • Proof cases in tests/config/client-config-export.test.ts: proven with Fast, cold inventory, self-namespaced collision, roster collision, explicit alias and a stale candidate on reload.
  • One file at a time: client-config-export 100/0, cli-export-command 33/0, client-export-model-names 12/0, management-client-config-route 43/0, api-catalog-route 9/0, loopback-listener-integration 38/0.
  • tests/clients plus the export and management route files in one process: 3029 pass, 9 fail. All nine pass when their files run alone (cross-file state in a single process).
  • bun run typecheck, bun run structure:check, bun run privacy:scan and git diff --check pass.
  • Cost: proving 400 nested rows takes about 110 ms per roster load, on the export and integration path only.
  • NOT RUN: the full bun run test, the docs-site build, and a live Pi client against OpenRouter.
  • Pushed with --no-verify; the repository has no pre-push hook.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. Selector serialization and a write guard only; no credential or admission change.

Co-authored-by: Juan Fernando Huenchun Barra <jhuenchunbarra@gmail.com>
@coderabbitai

coderabbitai Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Nov 7, 2026
Pi now receives a raw nested selector only when the serving proxy's own
configuration and inventory prove it reaches the encoded key's model and
account, Fast row included, with no other row claiming it. Cold, ambiguous,
colliding and wrong-model routes keep the encoded key. ocx export --client pi
takes its document from /api/client-config, and a managed Pi write refuses
when a captured selector stopped proving.
@lidge-jun

Copy link
Copy Markdown
Owner Author

Maintainer judgment: hold (not merging)

The rework at 4be22db follows the owner decision on #8172 for the surfaces the proxy serves: proof runs in loadExportModels against the serving configuration and inventory, cold, ambiguous, colliding and wrong-model routes keep the encoded key, and Fast rows are proven with their base. An independent review confirmed those parts (alias output unchanged, all new tests fail on the pre-fix source). It also found two problems that keep this from merging:

  1. ocx sync still proves Pi selectors from the CLI's saved configuration. src/cli/sync-command.ts (~L166-171) loads the CLI config and calls loadExportModels(config) in the CLI process, then refreshes the managed Pi integration through buildPiContribution. The write guard re-proves against that same config, so it cannot catch a difference from the live proxy. That is the case the owner decision rules out, and it contradicts the new structure note.
  2. ocx export --client pi changes output in Codex Direct mode. The CLI's local build drops native rows under Codex Direct (omitNative in opencodeCatalogFromProxyRows); the server roster does not, so routing the CLI through /api/client-config adds the native GPT rows, which Pi cannot use there (its loopback placeholder is not a ChatGPT bearer). The dashboard download already behaves this way today, so the two surfaces already disagree in that mode.

Fixing either one means choosing who owns Pi's catalog, and each choice changes something users see:

  • (a) Route ocx sync's Pi refresh through the proxy (as /api/sync does) and apply the Codex Direct native-row rule in the Pi builder, so the CLI export, dashboard download and managed refresh all agree. This removes unusable native rows from managed Pi files in Direct mode.
  • (b) Keep raw selectors server-only: the CLI export and ocx sync keep encoded keys (today's output); the dashboard download and dashboard-driven refresh get proven raw selectors. A managed Pi file could then flip between spellings depending on which path refreshed it last.
  • (c) Close this without merging. Since fix(router): decode routed slugs via modelDisplayNames ids #7979 (v2.95.0) the encoded key decodes whenever the router knows the model, and the owner decision refuses cold inventories, so the raw selector only helps in the window where the proxy has restarted but not yet rediscovered the model. That benefit may not justify (a) or (b).

The branch is left as a draft at 4be22db; the review findings are recorded here so the next owner does not repeat them.

…ws in Codex Direct

Pi may carry raw nested selectors that only the serving proxy can prove (#8172), but ocx sync rebuilt the Pi file in the CLI process from the CLI's saved configuration and router cache, and the write guard re-proved against the same snapshot. ocx sync now leaves Pi out of its local refresh and asks the running proxy to refresh an owned Pi file through POST /api/client-integrations/pi/sync, which builds it from that process's own configuration, router and roster; a refusal or an older proxy is reported as a Pi outcome with no local fallback.

The Pi export moved onto the serving proxy's roster, which kept native OpenAI rows in Codex Direct mode; Direct forwards the caller's own bearer and Pi sends a placeholder key, so those rows only answered 401. The Pi builder now drops them in Direct mode, as the CLI-built export did.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pi export emits invalid IDs for nested provider model slugs

1 participant