Skip to content

feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check#3818

Merged
farion1231 merged 6 commits into
farion1231:mainfrom
CSberlin:feat/v1-models-endpoint
Jun 7, 2026
Merged

feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check#3818
farion1231 merged 6 commits into
farion1231:mainfrom
CSberlin:feat/v1-models-endpoint

Conversation

@CSberlin

@CSberlin CSberlin commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a GET /v1/models endpoint. Codex CLI probes this endpoint at startup — without it cc-switch returns 404, causing Codex to fail before any request reaches the upstream LLM.

The handler returns the cc-switch–managed model catalog file directly (~/.codex/cc-switch-model-catalog.json), gated on config.toml still referencing the catalog via model_catalog_json. This prevents serving stale models after switching to a provider without a custom catalog.

Verification

Codex doctor sends exactly one request — GET /v1/models — and only checks the HTTP status code (200 vs 404). Response body format does not affect the reachability check.

Setup /v1/models codex doctor
cc-switch directly (127.0.0.1:15721) 404 (route missing) fail
With this PR 200 + model catalog pass

Related Issue

Fixes #3812

Changes

  • src-tauri/src/proxy/server.rs: register GET /v1/models and GET /models routes (+3 lines)
  • src-tauri/src/proxy/handlers.rs: new handle_models() handler with stale-catalog guard (+20 lines)

Checklist

  • cargo fmt --check passes
  • cargo clippy --no-deps passes (zero warnings)
  • cargo test -p cc-switch -- proxy — 837 passed, 5 pre-existing failures (port 15721 in use by running cc-switch)
  • No frontend changes (pnpm typecheck / pnpm format:check not applicable)
  • No user-facing text changed (no i18n needed)

…heck

Codex CLI probes GET /v1/models at startup. Without this endpoint the proxy
returns 404, causing Codex to fail before any request reaches the upstream
LLM.

Return an OpenAI-compatible model list derived from the cc-switch–managed
model catalog file.

Fixes farion1231#3812
@farion1231

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1fb0cbd542

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/proxy/handlers.rs Outdated
Codex deserializes the response as a catalog with a top-level `models`
field, not the OpenAI `{"object":"list","data":[...]}` envelope.
Return the catalog file content directly so the format matches what
Codex expects.

Co-authored-by: Codex review bot
@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

实测验证

Codex doctor 探测的路径

用 probe-logger 抓包确认:codex doctor 启动时发送的唯一请求GET /v1/models。Codex 仅检查 HTTP 状态码(200 vs 404),不校验响应体格式。

测试结果

配置 /v1/models codex doctor
直接连 cc-switch (127.0.0.1:15721) 404 (路由缺失) ✗ fail
bridge 返回 OpenAI 格式 {"object":"list","data":[...]} 200 ✓ pass
bridge 返回 catalog 格式 {"models": [...]} 200 ✓ pass

结论

  1. /v1/models 端点必须存在,否则 Codex 启动检查失败
  2. 响应格式不重要,Codex 只看 HTTP 状态码
  3. 当前实现(直接返回 catalog 文件)代码最简单(3 行),且与 Codex bot 的建议一致

@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

复现验证

测试环境

  • macOS 26.4.0,Codex CLI v0.137.0,cc-switch v3.16.1
  • DeepSeek API (api.deepseek.com),Codex config.toml 配置 wire_api = "responses"
  • QingyunLite 代理开启(7892),no_proxy 设为 localhost,127.0.0.1,localaddress,.localdomain.com

复现步骤与结果

第一步:按官方指南配置,直接连 cc-switch

# ~/.codex/config.toml
base_url = "http://127.0.0.1:15721/v1"
$ codex doctor
✗ reachability provider base URL route returned 404
  custom API route probe  http://127.0.0.1:15721/v1/<redacted> returned HTTP 404 (required)

第二步:用 probe-logger 抓包确认 Codex 实际请求的路径

GET /v1/models

Codex doctor 只发送这一个请求。当前 cc-switch build_router() 中未注册该路由,返回 404。

第三步:模拟 PR 合入后的效果(test server 返回 catalog 格式)

$ codex doctor
✓ reachability active provider endpoints are reachable over HTTP
  custom API route probe  route exists (HTTP 200)

格式测试

两种响应格式均通过 codex doctor(Codex 仅检查 HTTP 状态码,不校验 body),但选择直接返回 cc-switch 已有的 model catalog 文件内容({"models": [...]}),原因:

  • cc-switch 已生成此文件(~/.codex/cc-switch-model-catalog.json),零额外维护
  • 格式与 Codex 原生 model catalog 一致,display_namecontext_window 等元数据完整保留
  • 代码最简(3 行),无字段转换逻辑

改动效果

改动前 改动后
GET /v1/models 404 200 + model catalog
GET /models 404 200 + model catalog
codex doctor ✗ fail ✓ pass
其他端点 不受影响 不受影响

@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231 comment done

@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f49c8323c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/proxy/handlers.rs Outdated
Only return the model catalog when config.toml still references it via
`model_catalog_json`.  After switching to a provider without a custom
catalog, the old file lingers on disk — serving it unconditionally
would advertise the previous provider's models to Codex.

Co-authored-by: Codex review bot
@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd02f22234

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/proxy/handlers.rs Outdated
cc-switch writes `model_catalog_json = "cc-switch-model-catalog.json"`
(relative) via set_codex_model_catalog_json_field.  Match on the
filename constant rather than the absolute path so the guard works
with both relative and absolute paths.

Co-authored-by: Codex review bot
@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f155a230c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/proxy/handlers.rs Outdated
Replace raw config_text.contains() with proper TOML field parsing so
commented-out lines and stray mentions of the filename in other fields
don't defeat the stale guard.  Also switch from contains() to exact
filename match (Path::new(val).file_name() == Some(...)) to stay
consistent with resolve_cc_switch_catalog_path in codex_config.rs.

Add log::debug! when the guard blocks serving so the operator can
distinguish "no models configured" from "guard blocked stale catalog".
@CSberlin

CSberlin commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Replace the inline config.toml parsing and filename match in
handle_models with the existing resolve_cc_switch_catalog_path helper
(now pub(crate)). This removes the duplicated stale-guard logic, keeps
a single source of truth for catalog-path ownership, and makes the
handler honor absolute model_catalog_json paths the same way Codex
live-setting import does.

@farion1231 farion1231 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for your contribution!

@farion1231
farion1231 merged commit 27c41f7 into farion1231:main Jun 7, 2026
3 checks passed
AnXiYiZhi pushed a commit to AnXiYiZhi/DevCLaw-old that referenced this pull request Jun 9, 2026
…heck (farion1231#3818)

* feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check

Codex CLI probes GET /v1/models at startup. Without this endpoint the proxy
returns 404, causing Codex to fail before any request reaches the upstream
LLM.

Return an OpenAI-compatible model list derived from the cc-switch–managed
model catalog file.

Fixes farion1231#3812

* fix(proxy): return Codex catalog schema from /v1/models

Codex deserializes the response as a catalog with a top-level `models`
field, not the OpenAI `{"object":"list","data":[...]}` envelope.
Return the catalog file content directly so the format matches what
Codex expects.

Co-authored-by: Codex review bot

* fix(proxy): guard /v1/models against serving stale catalog

Only return the model catalog when config.toml still references it via
`model_catalog_json`.  After switching to a provider without a custom
catalog, the old file lingers on disk — serving it unconditionally
would advertise the previous provider's models to Codex.

Co-authored-by: Codex review bot

* fix(proxy): match relative model_catalog_json in stale-guard

cc-switch writes `model_catalog_json = "cc-switch-model-catalog.json"`
(relative) via set_codex_model_catalog_json_field.  Match on the
filename constant rather than the absolute path so the guard works
with both relative and absolute paths.

Co-authored-by: Codex review bot

* fix(proxy): parse model_catalog_json field instead of substring match

Replace raw config_text.contains() with proper TOML field parsing so
commented-out lines and stray mentions of the filename in other fields
don't defeat the stale guard.  Also switch from contains() to exact
filename match (Path::new(val).file_name() == Some(...)) to stay
consistent with resolve_cc_switch_catalog_path in codex_config.rs.

Add log::debug! when the guard blocks serving so the operator can
distinguish "no models configured" from "guard blocked stale catalog".

* refactor(proxy): reuse resolve_cc_switch_catalog_path in handle_models

Replace the inline config.toml parsing and filename match in
handle_models with the existing resolve_cc_switch_catalog_path helper
(now pub(crate)). This removes the duplicated stale-guard logic, keeps
a single source of truth for catalog-path ownership, and makes the
handler honor absolute model_catalog_json paths the same way Codex
live-setting import does.

---------

Co-authored-by: Jason <[email protected]>
gfunc pushed a commit to gfunc/cc-switch that referenced this pull request Jun 19, 2026
…heck (farion1231#3818)

* feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check

Codex CLI probes GET /v1/models at startup. Without this endpoint the proxy
returns 404, causing Codex to fail before any request reaches the upstream
LLM.

Return an OpenAI-compatible model list derived from the cc-switch–managed
model catalog file.

Fixes farion1231#3812

* fix(proxy): return Codex catalog schema from /v1/models

Codex deserializes the response as a catalog with a top-level `models`
field, not the OpenAI `{"object":"list","data":[...]}` envelope.
Return the catalog file content directly so the format matches what
Codex expects.

Co-authored-by: Codex review bot

* fix(proxy): guard /v1/models against serving stale catalog

Only return the model catalog when config.toml still references it via
`model_catalog_json`.  After switching to a provider without a custom
catalog, the old file lingers on disk — serving it unconditionally
would advertise the previous provider's models to Codex.

Co-authored-by: Codex review bot

* fix(proxy): match relative model_catalog_json in stale-guard

cc-switch writes `model_catalog_json = "cc-switch-model-catalog.json"`
(relative) via set_codex_model_catalog_json_field.  Match on the
filename constant rather than the absolute path so the guard works
with both relative and absolute paths.

Co-authored-by: Codex review bot

* fix(proxy): parse model_catalog_json field instead of substring match

Replace raw config_text.contains() with proper TOML field parsing so
commented-out lines and stray mentions of the filename in other fields
don't defeat the stale guard.  Also switch from contains() to exact
filename match (Path::new(val).file_name() == Some(...)) to stay
consistent with resolve_cc_switch_catalog_path in codex_config.rs.

Add log::debug! when the guard blocks serving so the operator can
distinguish "no models configured" from "guard blocked stale catalog".

* refactor(proxy): reuse resolve_cc_switch_catalog_path in handle_models

Replace the inline config.toml parsing and filename match in
handle_models with the existing resolve_cc_switch_catalog_path helper
(now pub(crate)). This removes the duplicated stale-guard logic, keeps
a single source of truth for catalog-path ownership, and makes the
handler honor absolute model_catalog_json paths the same way Codex
live-setting import does.

---------

Co-authored-by: Jason <[email protected]>
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.

feat: 支持 GET /v1/models 端点以兼容 Codex CLI API 可达性检测

2 participants