Skip to content

Merge upstream/main (Auto Router, host guard, doctor, passthroughs) into fork#10

Merged
luckeyfaraday merged 20 commits into
mainfrom
merge-upstream
Jun 20, 2026
Merged

Merge upstream/main (Auto Router, host guard, doctor, passthroughs) into fork#10
luckeyfaraday merged 20 commits into
mainfrom
merge-upstream

Conversation

@luckeyfaraday

Copy link
Copy Markdown
Owner

Brings the fork up to date with 0xSero/codex-shim — was 19 commits behind.

What this pulls in

  • Auto Router — per-task model selection (router.py, docs/AUTO_ROUTER.md, demo + integration tests)
  • Host-header guard — DNS-rebinding protection (hostguard.py) — security fix
  • doctor command — read-only local diagnostics
  • Cursor Composer subscription passthrough (cursor_passthrough.py)
  • OpenCode Go on-ramp (opencode_go.py)
  • Anthropic-Messages bridge for OpenAI-compatible chat providers
  • Web /picker UI, native tool-type mapping, streaming-usage normalization, and assorted server.py/translate.py fixes

Conflict resolution (codex_shim/cli.py only)

  • Imports — unioned both sides: kept the fork's getpass (provider-setup prompts) alongside upstream's collections.Counter and importlib.util (used by doctor).
  • Command dispatch — kept upstream's new doctor subcommand, but adapted its call to the fork's local port variable (status(port) / doctor(args.settings, port)) to match the surrounding dispatch, instead of upstream's args.port.

Fork-only code preserved

The provider-setup shortcuts (setup / openrouter / minimax) and tests/test_provider_workflows.py are fork-only and coexist with upstream's doctor command. Verified setup --help, doctor --help, and a live doctor run.

Verification

  • python3 -m compileall codex_shim/ clean
  • 187 passed (was 45 pre-merge; upstream adds router/hostguard/cursor/native-tool/doctor suites)

Notes / follow-ups

🤖 Generated with Claude Code

aaronjmars and others added 20 commits May 27, 2026 11:49
…utions#14)

Adds a Host-header allowlist middleware so a page the user visits cannot use DNS rebinding to drive the loopback shim with their BYOK credentials. Loopback names, the configured bind host, and CODEX_SHIM_ALLOWED_HOSTS are accepted; everything else is rejected with 403 before any upstream call. 53 tests pass locally.

Co-authored-by: aaronjmars <aaronjmars@users.noreply.github.com>
Patches the Codex Desktop sidebar recent-thread loader alongside the existing model-picker ASAR patch so native openai chats stay visible while Desktop is routed through codex_shim. Also recomputes ElectronAsarIntegrity in Info.plist after repacking app.asar and restores/recomputes it during restore-app. Tested on Codex Desktop 26.519.41501 / codex-cli 0.133.0-alpha.1 (macOS arm64). 40 tests pass locally.

Fixes #10.

Co-authored-by: tharuxpert <tharuxpert@users.noreply.github.com>
)

Re-do of #9 against post-sybil-solutions#13/sybil-solutions#14 main. Adds /picker, /api/models, /api/switch behind the existing Host-header allowlist; switching rewrites model and provider name in ~/.codex/config.toml so Codex Desktop shows the selected model's display name. Cross-platform Codex restart (Windows/macOS). Best-effort last_request.json dump + upstream-error slug logging on the chat-completions path. Translate.py hardening from the original #9 already landed via 0da7b74/215d32e and is intentionally not re-applied. 68 tests pass locally.

Closes #9.

Co-authored-by: MarioCodarin <MarioCodarin@users.noreply.github.com>
Add /v1/responses/compact support. ChatGPT passthrough forwards to the native ChatGPT compact endpoint; BYOK OpenAI/chat and Anthropic routes run a non-streaming compaction prompt and return a Responses-shaped compacted window. Translate native Responses-only tools into deterministic BYOK function fallbacks for computer_use, web_search, apply_patch, and local_shell while keeping MCP function tools passed through. Tests: git diff --check; python3 -m pytest tests -q (72 passed); python3 -m compileall codex_shim. Generated with [Devin](https://cli.devin.ai/docs)
Preserve upstream usage from OpenAI chat-completion stream chunks and Anthropic message_delta stream events, then include it on the final response.completed event so Codex can track token counts and trigger auto-compaction after streamed BYOK turns. Adds regression tests for both stream shapes. Tests: git diff --check; python3 -m pytest tests -q (74 passed); python3 -m compileall codex_shim. Generated with [Devin](https://cli.devin.ai/docs)
Preserve Responses input_image content, computer_call_output screenshots, and visual function_call_output payloads through BYOK translation. OpenAI-chat providers receive image_url parts; Anthropic providers receive image blocks. Keeps text-only Anthropic messages as strings for strict compatibility. Tests: git diff --check; python3 -m pytest tests -q (78 passed); python3 -m compileall codex_shim. Generated with [Devin](https://cli.devin.ai/docs)
Adds auth-gated Cursor Composer subscription passthrough via cursor-agent CLI and fixes pre-merge subprocess cleanup/docs issues.\n\nVerified:\n- git diff --check\n- python3 -m pytest tests/test_cursor_passthrough.py -q\n- python3 -m pytest tests/ -q\n- python3 -m compileall codex_shim/ -q\n\nGenerated with [Devin](https://cli.devin.ai/docs)
## Summary
- Add a Windows launcher for live quota/cost-enriched model display names.
- Harden launcher path discovery so it works outside the contributor's local machine.
- Preserve quota-enriched GPT-5.5 catalog names across model switch and app launch flows.

## Verification
- PYTHONPATH="/home/terp/repos/codex-shim" python3 -m pytest "/home/terp/repos/codex-shim/tests" -q
- PYTHONPATH="/home/terp/repos/codex-shim" python3 -m compileall "/home/terp/repos/codex-shim/codex_shim" -q
- git diff --check

Generated with [Devin](https://cli.devin.ai/docs)
Anthropic route headers now send only x-api-key (plus anthropic-version)
and no longer also attach Authorization: Bearer <apiKey>. Some
Anthropic-compatible gateways reject requests that carry both headers.
Providers that genuinely require a bearer token can still supply one via
extraHeaders.

Co-authored-by: OnlyTerp <121772140+OnlyTerp@users.noreply.github.com>
Adds an optional "Auto (smart routing)" picker entry (slug codex-auto) that
routes each Codex task to the cheapest configured model that can handle it. A
cheap classifier model scores every candidate 0.0-1.0 from a capability card;
the shim picks the cheapest candidate whose score clears a threshold (default
0.7), caches the decision per task, and falls back safely on any error so a
request never breaks. The classifier never sees price, so it can't be biased
toward expensive models.

- codex_shim/router.py: config loading, task-signal extraction, classifier
  prompt, score parsing, cheapest-among-viable selection, per-task cache.
- server.py: applies the router on /v1/responses, /v1/responses/compact, and
  /v1/chat/completions; runs the classifier over the configured backend; gates
  the virtual model in /v1/models, /api/models, /health, and the picker.
- catalog.py + cli.py: catalog entry and `codex-shim list`/`model use` support.
- Configured via an optional `router` block in ~/.codex-shim/models.json. Env
  knobs: CODEX_SHIM_DISABLE_ROUTER, CODEX_SHIM_ROUTER_TIMEOUT,
  CODEX_SHIM_ROUTER_MAX_TOKENS, CODEX_SHIM_ROUTER_LOG.
- docs/AUTO_ROUTER.md, README section, and a runnable offline proof at
  examples/auto_router_demo.py.

Verification:
- python3 -m pytest tests/ -q  (113 passed)
- python3 -m compileall codex_shim/ examples/ -q
- python3 examples/auto_router_demo.py  (RESULT: PASS)

Generated with [Devin](https://cli.devin.ai/docs)
… loop, concurrency)

Expands Auto Router coverage from 27 to 48 offline tests with a real-ShimServer
integration suite proving the production paths:

- streaming /v1/responses through the router (response.completed + usage)
- /v1/responses/compact through the router
- /v1/chat/completions through the router
- the agent tool-call loop: one classification per task is reused across
  follow-up turns (cache), while distinct tasks each get classified
- OpenAI-shaped AND Anthropic-shaped classifiers, plus an Anthropic candidate
- the exact HTTP the shim sends to the classifier (payload + auth headers,
  capability cards and task text present)
- threshold tuning flows through from config (low keeps cheap, high escalates)
- graceful fallback when the classifier returns non-JSON
- discovery gating: health count excludes the virtual model, disable-env and
  unavailable candidates hide it, no-credential candidates are skipped
- the picker switch accepts the auto slug
- concurrency: many simultaneous requests route correctly under the shared cache

Verification:
- python3 -m pytest tests/ -q  (134 passed)
- python3 -m compileall codex_shim/ examples/ -q
- python3 examples/auto_router_demo.py  (RESULT: PASS)

Generated with [Devin](https://cli.devin.ai/docs)
…lutions#30)

_join_url previously special-cased only `/v1` as a known version
segment, so bases such as Volces Ark's coding endpoint at
`https://ark.cn-beijing.volces.com/api/coding/v3` ended up with a
redundant `/v1/` injected (`/api/coding/v3/v1/chat/completions`) and
returned 404.

Generalise the check with a regex (`(?:^|/)v\d+$`) so anything ending
in `/v1`, `/v3`, `/v4`, etc. is recognised and the endpoint is
appended as-is. Order is preserved so the `/messages` fallback still
kicks in for bare Anthropic-style hosts.

This makes the existing test_join_url_handles_versioned_bases pass
(it had been failing on upstream/main since merge — see the merge
commit's verification note); no test changes.
…text-only (sybil-solutions#31)

Reproduction (from production shim.log): an Auto Router request with an
image went through the path where the classifier returned all-zero scores,
which triggered pick_candidate -> None -> fallback_slug. The old
fallback_slug honoured the configured `default` unconditionally, returning
`deepseek-v4-flash` (text-only) for an image task. The shim forwarded the
request to api.deepseek.com which rejected it with a confusing 400:

    Failed to deserialize the JSON body into the target type:
    messages[116]: unknown variant `image_url`, expected `text`

That breaks the Auto Router's contract that routing must never break a
request.

Fix
===
`fallback_slug` now takes a new keyword-only flag `has_image_task` (default
False, so old call sites and tests keep their behaviour). When True, the
pool is restricted to candidates with `supports_images=True` *before* the
default/cheapest selection runs. If no vision-capable candidate exists,
fallback returns None so the caller can surface the failure explicitly
rather than silently producing a 400 from a text-only upstream.

The three internal call sites in router.resolve_auto (no-classifier,
classifier-error, empty-scores) and the one final-fallback site in
server.ShimServer._resolve_auto_model now thread the existing image
signal (signal["has_images"] / router.has_images(body)) through.

Tests
=====
Three new tests in test_router.py encode the user-visible contract:

  test_fallback_slug_skips_image_incapable_when_task_has_image
  test_fallback_slug_image_task_with_no_vision_candidate_returns_none
  test_fallback_slug_default_kwarg_preserves_old_call_sites

Full suite: 124 passed (was 121 + 3 new).
* feat: add OpenCode Go model refresh

* fix: tighten display_name pattern, remove slug heuristic, add generated_by to test

- Replace fragile v/k/m prefix checks in display_name_from_model_id
  with generic isalpha+isdigit pattern
- Remove slug+URL heuristic from _is_opencode_go_row (was matching
  stale rows by URL alone); only check generated_by field
- Add missing generated_by field to test fixture dict

* fix: preserve user's existing settings key when writing OpenCode Go rows

write_opencode_go_models used to pop customModels/launchModels/launch_models
and force-write to 'models', silently migrating the user's schema. A user
with only legacy customModels would see their config key replaced and
their existing rows re-mapped without warning.

Pick the first key the user already has; default to 'models' for new
files. The preserved-row logic is unchanged.
…ybil-solutions#26)

* feat: add Anthropic Messages bridge

* fix: empty string for tool-only assistant content, deduplicate helpers, add bridge tests

- Fix content: None → empty string in _anthropic_assistant_message_to_chat
  for tool-only assistant messages (some providers reject null content)
- Deduplicate _chat_stream_finish_to_anthropic_stop and
  _anthropic_usage_from_normalized from server.py; import canonical
  versions from translate.py instead
- Add 4 new tests: tool call streaming, reasoning streaming,
  Anthropic-shaped error responses, and Anthropic pass-through streaming
- Add 2 new translate tests: tool-only content and reasoning blocks

* fix: uuid message ids, model slug in passthrough SSE, preserve tool names

- Use uuid4() for Anthropic message_id instead of timestamp (collision risk)
- Rewrite model field in pass-through SSE message_start events
- Preserve original tool names in _anthropic_tools_to_chat_tools and
  _anthropic_tool_choice_to_chat (remove _sanitize_tool_name mangling)

---------

Co-authored-by: codex <codex@openai.com>
Co-authored-by: test <test@local>
…etail, subscription docs

- Add codex-shim doctor with OK/WARN/FAIL/INFO diagnostics
- Protect /api/switch with per-process X-Codex-Shim-Picker-Token
- Replace literal patch-app needles with regex + APPLIED markers
- Normalize image detail original→high for chat-completions providers
- Add docs/subscription-integration.md and CHANGELOG/README updates

Co-authored-by: Cursor <cursoragent@cursor.com>
…open-backlog

Integrate open backlog: doctor, CSRF, patch-app, image detail, docs
fix: native tool type mapping for freeform apply_patch and web_search

- Maps original Responses tool types through streaming and non-streaming paths
- Emit custom_tool_call for apply_patch (freeform) instead of generic function_call
- Emit web_search_call for web_search tools so Codex Desktop handles them correctly
- Add server-side DuckDuckGo web search for non-streaming BYOK responses
- Add comprehensive tests for all tool type mapping scenarios
Brings the fork up to date with 0xSero/codex-shim (19 commits behind):
Auto Router, host-header guard (DNS-rebind protection), Cursor Composer and
OpenCode passthrough, Anthropic-Messages bridge for OpenAI-compatible chat
providers, web /picker UI, native tool-type mapping, streaming-usage fixes,
and a read-only `doctor` diagnostics command.

Conflicts (codex_shim/cli.py only):
- Imports: unioned both sides — kept our `getpass` (provider setup prompts)
  alongside upstream's `collections.Counter` and `importlib.util` (doctor).
- Command dispatch: kept upstream's new `doctor` subcommand, adapted its call
  to the fork's local `port` variable (`status(port)` / `doctor(args.settings,
  port)`) instead of upstream's `args.port`, matching the surrounding dispatch.

Fork-only provider-setup shortcuts (`setup`/`openrouter`/`minimax` and
tests/test_provider_workflows.py) are preserved and coexist with upstream's
doctor command. Full suite: 187 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@luckeyfaraday luckeyfaraday merged commit 57627ab into main Jun 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants