feat: route Codex Chat providers through Stream Check

Codex Chat providers (apiFormat=openai_chat, e.g. DeepSeek, MiniMax, Kimi)
were incorrectly probed via /v1/responses by Stream Check, which the upstream
rejects. Detect chat routing via codex_provider_uses_chat_completions and
issue the probe against /chat/completions with a Chat-shaped body.

Also align URL fallback order with the production CodexAdapter::build_url:
origin-only base URLs (https://api.deepseek.com) must hit /v1/<endpoint>
first; bare /<endpoint> only as a fallback. The previous order made any
non-404 error on the bare path (401/400/405) flag the provider as down even
though /v1/<endpoint> would have succeeded.

- Lift origin-only detection into proxy::providers::is_origin_only_url so
  both build_url and Stream Check share a single source of truth.
- Collapse resolve_codex_stream_urls and resolve_codex_chat_stream_urls into
  a generic resolve_codex_endpoint_urls(base_url, is_full_url, endpoint),
  which also gives the Responses path a symmetric "full endpoint without
  is_full_url flag" fallback for free.
- Restrict reasoning_effort propagation in the Chat branch to OpenAI o-series
  models, mirroring transform_codex_chat's runtime check.
This commit is contained in:
Jason
2026-05-21 09:14:52 +08:00
parent 955904f719
commit 72bc912e0d
3 changed files with 181 additions and 19 deletions
+11 -6
View File
@@ -166,6 +166,16 @@ fn is_chat_completions_url(value: &str) -> bool {
.ends_with("/chat/completions")
}
/// `scheme://host` 之后没有路径段的纯 origin 形式。`build_url` 在这种情况下
/// 会自动补 `/v1`Stream Check 等同步生产路径的代码也需要同一判定。
pub fn is_origin_only_url(value: &str) -> bool {
let trimmed = value.trim_end_matches('/');
match trimmed.split_once("://") {
Some((_scheme, rest)) => !rest.contains('/'),
None => !trimmed.contains('/'),
}
}
fn extract_codex_wire_api_from_toml(config_text: &str) -> Option<String> {
let doc = config_text.parse::<TomlValue>().ok()?;
@@ -342,12 +352,7 @@ impl ProviderAdapter for CodexAdapter {
// 检查 base_url 是否已经包含 /v1
let already_has_v1 = base_trimmed.ends_with("/v1");
// 检查是否是纯 origin(没有路径部分)
let origin_only = match base_trimmed.split_once("://") {
Some((_scheme, rest)) => !rest.contains('/'),
None => !base_trimmed.contains('/'),
};
let origin_only = is_origin_only_url(base_trimmed);
let mut url = if already_has_v1 {
// 已经有 /v1,直接拼接