Skip to content
190 changes: 175 additions & 15 deletions crates/codex_integration/src/apply.rs

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions crates/codex_integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ pub use mcp_credentials::{
restore_mcp_credentials_from_mirror, sync_mcp_credentials, SyncReport,
};
pub use model_catalog::{
catalog_models_for_provider, catalog_models_for_provider_with_display_names,
strip_model_suffix, upsert_catalog_models,
catalog_models_for_pool, catalog_models_for_provider,
catalog_models_for_provider_with_display_names, strip_model_suffix, upsert_catalog_models,
CatalogModel, PoolProviderMeta,
};
pub use paths::CodexPaths;
pub use residual::{
Expand Down
159 changes: 157 additions & 2 deletions crates/codex_integration/src/model_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

use codex_app_transfer_registry::{
documented_context_window, load_raw_config, model_supports_1m, normalize_model_mappings,
save_raw_config, strip_internal_model_suffix, CAS_BASE_INSTRUCTIONS, MODEL_SLOTS,
save_raw_config, strip_internal_model_suffix, PoolEntry, CAS_BASE_INSTRUCTIONS, MODEL_SLOTS,
};
use serde_json::{json, Value};

Expand Down Expand Up @@ -211,11 +211,70 @@ pub fn catalog_models_for_provider_with_display_names(
models
}

/// 池化模式下单个 provider 的元数据,按 [`PoolEntry::provider_idx`] 索引。
///
/// 拥有自己的数据(非借用)以避开 caller 端 `Value` 生命周期纠缠。空 object `{}` /
/// `Null` 表示"无该信息"(context_window / display 反查都会安全回退)。
#[derive(Debug, Clone)]
pub struct PoolProviderMeta {
pub provider_name: String,
/// `modelCapabilities`(model id → {context_window, supports1m, ...});`{}` = 无。
pub model_capabilities: Value,
/// model id → 人类可读 displayName(仅 antigravity 非空);`Null` = 回退 raw id。
pub display_names: Value,
}

/// 池化 catalog:对 [`unique_pool_slugs`](codex_app_transfer_registry::unique_pool_slugs)
/// 产出的每条 [`PoolEntry`] 生成一条 [`CatalogModel`]。
///
/// - `slug` 直接用 entry 的 `<provider_slug>/<model>`(已去碰撞);
/// - `display_name` 池模式下 **provider-qualified**(`"<provider> / <model>"`)以消歧
/// 多 provider 同名模型 —— Codex 原生 picker 是扁平列表,只能靠这个区分(单 provider
/// 模式去前缀的旧决策不适用池模式);
/// - `context_window` 复用 [`context_window_for_model`] 的优先级链(显式 caps → documented
/// → 1M/258K 二档),按**每个模型**独立计算;
/// - `auto_review_model_override = None`(slot 机制对斜杠 slug 无意义)。
///
/// `providers_meta` 必须与传给 `unique_pool_slugs` 的 provider 切片**同序**(entry 的
/// `provider_idx` 即下标)。下标越界的 entry 被跳过(防御,正常不发生)。
pub fn catalog_models_for_pool(
entries: &[PoolEntry],
providers_meta: &[PoolProviderMeta],
) -> Vec<CatalogModel> {
entries
.iter()
.filter_map(|entry| {
let meta = providers_meta.get(entry.provider_idx)?;
let caps = Some(&meta.model_capabilities);
let real = entry.real_model.as_str();
// 1M 信号:显式 modelCapabilities/documented(model_supports_1m)**或** 被 strip 的
// 内部 `[1m]` 标记(entry.supports_one_m)。后者保证无显式声明的自定义模型在池模式
// 也拿 1M 窗口,与单 provider 模式一致(bot review P2)。
let supports_1m = entry.supports_one_m || model_supports_1m(real, caps);
let context_window = context_window_for_model(real, real, real, supports_1m, caps);
Comment thread
Cmochance marked this conversation as resolved.
let label = resolve_display_label(real, Some(&meta.display_names));
let display_name = if meta.provider_name.trim().is_empty() {
label
} else {
format!("{} / {}", meta.provider_name.trim(), label)
};
Some(CatalogModel {
slug: entry.slug.clone(),
display_name,
provider_name: meta.provider_name.clone(),
context_window,
effective_context_window_percent: DEFAULT_EFFECTIVE_CONTEXT_WINDOW_PERCENT,
auto_review_model_override: None,
})
})
.collect()
}

pub fn strip_model_suffix(model: &str) -> String {
strip_internal_model_suffix(model)
}

fn context_window_for_model(
pub(crate) fn context_window_for_model(
original_model: &str,
clean_model: &str,
default_model: &str,
Expand Down Expand Up @@ -1164,4 +1223,100 @@ mod tests {
let _typed: codex_app_transfer_registry::Config =
serde_json::from_value(v).expect("top-level models must not break registry config");
}

// ── 池化 catalog(catalog_models_for_pool)──

#[test]
fn catalog_models_for_pool_builds_provider_qualified_entries() {
use codex_app_transfer_registry::PoolEntry;
let entries = vec![
PoolEntry {
provider_idx: 0,
slug: "deepseek/deepseek-v4-pro".into(),
real_model: "deepseek-v4-pro".into(),
supports_one_m: false,
},
PoolEntry {
provider_idx: 1,
slug: "ag/gemini-3.5-flash-low".into(),
real_model: "gemini-3.5-flash-low".into(),
supports_one_m: false,
},
];
let meta = vec![
PoolProviderMeta {
provider_name: "DeepSeek".into(),
model_capabilities: json!({"deepseek-v4-pro": {"context_window": 512000}}),
display_names: Value::Null,
},
PoolProviderMeta {
provider_name: "Antigravity".into(),
model_capabilities: json!({}),
display_names: json!({"gemini-3.5-flash-low": "Gemini 3.5 Flash (Medium)"}),
},
];
let models = catalog_models_for_pool(&entries, &meta);
assert_eq!(models.len(), 2);

// provider-qualified display + slug 透传 + 显式 caps 窗口胜出
assert_eq!(models[0].slug, "deepseek/deepseek-v4-pro");
assert_eq!(models[0].display_name, "DeepSeek / deepseek-v4-pro");
assert_eq!(models[0].context_window, 512_000);
assert!(models[0].auto_review_model_override.is_none());

// display_names 反查命中 → 人类名,仍 provider-qualified
assert_eq!(models[1].slug, "ag/gemini-3.5-flash-low");
assert_eq!(
models[1].display_name,
"Antigravity / Gemini 3.5 Flash (Medium)"
);

// model_to_json:斜杠 slug 原样保留 + visibility=list + supported_in_api(会进 picker)
let json0 = model_to_json(&models[0]);
assert_eq!(json0["slug"], "deepseek/deepseek-v4-pro");
assert_eq!(json0["visibility"], "list");
assert_eq!(json0["supported_in_api"], true);
assert_eq!(json0["context_window"], 512_000);
assert_eq!(json0["apply_patch_tool_type"], "freeform");
}

#[test]
fn catalog_models_for_pool_skips_out_of_range_provider_idx() {
use codex_app_transfer_registry::PoolEntry;
let entries = vec![PoolEntry {
provider_idx: 5,
slug: "x/y".into(),
real_model: "y".into(),
supports_one_m: false,
}];
let meta = vec![PoolProviderMeta {
provider_name: "P".into(),
model_capabilities: json!({}),
display_names: Value::Null,
}];
assert!(catalog_models_for_pool(&entries, &meta).is_empty());
}

#[test]
fn catalog_models_for_pool_honors_supports_one_m_marker_without_caps() {
// bot review P2:仅靠 `[1m]` 标 1M 的自定义模型(无 modelCapabilities / 非 documented),
// 池模式也要拿 1M 窗口(与单 provider 模式一致),靠 PoolEntry.supports_one_m 传递。
use codex_app_transfer_registry::PoolEntry;
let entries = vec![PoolEntry {
provider_idx: 0,
slug: "x/custom-undocumented".into(),
real_model: "custom-undocumented".into(),
supports_one_m: true,
}];
let meta = vec![PoolProviderMeta {
provider_name: "X".into(),
model_capabilities: json!({}), // 无显式声明
display_names: Value::Null,
}];
let models = catalog_models_for_pool(&entries, &meta);
assert_eq!(
models[0].context_window, 1_000_000,
"supports_one_m → 1M 窗口"
);
}
}
122 changes: 109 additions & 13 deletions crates/proxy/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! `registry::Config` 的内存实现;Stage 4 接入 UI / 文件监听后,可换成
//! `ConfigWatcher` 持有实时 config 的版本.

use std::collections::HashMap;
use std::sync::Arc;

use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
Expand Down Expand Up @@ -120,6 +121,13 @@ pub struct StaticResolver {
/// 当 incoming 请求里没法决定 provider 时,fallback 用的 id.
/// 一般等于 `Config::active_provider`.
pub default_provider_id: Option<String>,
/// 池化模式反查表:Codex catalog slug → (`providers` 下标, 上游真实 model)。
///
/// 空 = 未开池化(或无可池模型)→ `decide_provider` 退回 slug-split / 默认
/// provider,行为与池化前**完全一致**。非空时由 `proxy_runner` 在启动时按
/// `settings.exposeAllProviderModels` gate 后用 `registry::unique_pool_slugs`
/// 构建(与 catalog 生成端**同一 helper**,保证 slug 逐字一致、不错路由)。
pub catalog_slug_map: HashMap<String, (usize, String)>,
}

impl StaticResolver {
Expand All @@ -132,9 +140,16 @@ impl StaticResolver {
gateway_key,
providers,
default_provider_id,
catalog_slug_map: HashMap::new(),
}
}

/// 装配池化反查表(builder 形式,保持 `new` 三参签名不变 → 既有调用 / 测试不破)。
pub fn with_catalog_slug_map(mut self, map: HashMap<String, (usize, String)>) -> Self {
self.catalog_slug_map = map;
self
}

fn find_by_id(&self, id: &str) -> Option<&Provider> {
self.providers.iter().find(|p| p.id == id)
}
Expand Down Expand Up @@ -309,25 +324,46 @@ fn decide_provider<'a>(
res: &'a StaticResolver,
body: &[u8],
) -> Option<(&'a Provider, Option<String>)> {
// 试着从 body JSON 里抠 "model".
if let Ok(v) = serde_json::from_slice::<serde_json::Value>(body) {
if let Some(model) = v.get("model").and_then(|m| m.as_str()) {
if let Some((slug, real)) = model.split_once('/') {
if let Some(p) = res.find_by_slug(slug) {
return Some((p, Some(strip_internal_model_suffix(real))));
}
// body JSON 里的 "model"(可能没有);只解析一次。
let model = serde_json::from_slice::<serde_json::Value>(body)
.ok()
.and_then(|v| v.get("model").and_then(|m| m.as_str()).map(str::to_owned));

if let Some(model) = model.as_deref() {
// 0. 池化反查表:catalog slug 精确命中 → 路由到该 provider + 改写成真实 model。
// 放最前:池化 slug 形如 "<base>/<real>",base 可能带碰撞后缀(-2),靠精确表
// 命中而非 find_by_slug 的裸 slug,避免碰撞误路由(把 prompt 发错上游)。
if let Some((idx, real)) = res.catalog_slug_map.get(model) {
if let Some(p) = res.providers.get(*idx) {
return Some((p, Some(real.clone())));
}
// 反查表命中但 idx 越界 = catalog↔map 单一真源约定被破坏(正常不可能:两端都用
// unique_pool_slugs 对同一 config 构建)。loud log 而非静默 fall through —— 否则会
// 落到默认 provider + 默认 model,把 prompt 发错上游。
crate::telemetry::proxy_telemetry().logs.add(
"ERROR",
format!(
"POOL_SLUG_MAP_IDX_OUT_OF_RANGE: slug {model:?} → idx {idx} 越界(providers={}),fall through",
res.providers.len()
),
);
}
// 1. "<slug>/<model>" 约定:按 provider slug 路由(手动调用 / 池化前兼容)。
if let Some((slug, real)) = model.split_once('/') {
if let Some(p) = res.find_by_slug(slug) {
return Some((p, Some(strip_internal_model_suffix(real))));
}
}
}

// 2. 默认 provider + 槽位映射改写。
let provider = res.default_provider()?;
if let Ok(v) = serde_json::from_slice::<serde_json::Value>(body) {
if let Some(model) = v.get("model").and_then(|m| m.as_str()) {
if let Some(mapped) = res.map_model_for_provider(provider, model) {
return Some((provider, Some(mapped)));
}
if let Some(model) = model.as_deref() {
if let Some(mapped) = res.map_model_for_provider(provider, model) {
return Some((provider, Some(mapped)));
}
}
// 没 / 或没可映射 model → 走默认 provider.
// 没 / 或没可映射 model → 走默认 provider 不改写。
Some((provider, None))
}

Expand Down Expand Up @@ -779,4 +815,64 @@ mod tests {
assert_eq!(res.extra_headers.get("x-api-key").unwrap(), "sk-real-key");
assert_eq!(res.extra_headers.get("x-plain").unwrap(), "no-template");
}

// ── 池化反查表路由(catalog_slug_map)──

#[test]
fn catalog_slug_map_routes_pool_slug_and_rewrites_model() {
let providers = vec![
provider("openai", "https://up-1", "sk-1"),
provider("deepseek", "https://up-2", "sk-2"),
];
let mut map = HashMap::new();
map.insert(
"deepseek/deepseek-v4-pro".to_owned(),
(1usize, "deepseek-v4-pro".to_owned()),
);
let r =
StaticResolver::new(None, providers, Some("openai".into())).with_catalog_slug_map(map);
let p = parts_with(&[]);
let res = r
.resolve(&p, br#"{"model":"deepseek/deepseek-v4-pro"}"#)
.unwrap();
assert_eq!(res.provider_id, "deepseek");
assert_eq!(res.api_key, "sk-2");
assert_eq!(res.rewritten_model.as_deref(), Some("deepseek-v4-pro"));
}

#[test]
fn catalog_slug_map_resolves_collision_suffix_slug_that_find_by_slug_cannot() {
// 碰撞后缀 slug "x-2/m":没有任何 provider 的 provider_slug == "x-2",
// find_by_slug 永远命中不了 → 必须靠精确反查表路由到正确 provider。
let providers = vec![
provider("a", "https://up-a", "sk-a"),
provider("b", "https://up-b", "sk-b"),
];
let mut map = HashMap::new();
map.insert("x-2/m".to_owned(), (1usize, "m".to_owned()));
let r = StaticResolver::new(None, providers, Some("a".into())).with_catalog_slug_map(map);
let p = parts_with(&[]);
let res = r.resolve(&p, br#"{"model":"x-2/m"}"#).unwrap();
assert_eq!(res.provider_id, "b");
assert_eq!(res.rewritten_model.as_deref(), Some("m"));
}

#[test]
fn empty_catalog_slug_map_preserves_legacy_slug_split_routing() {
// 空表(未开池化)→ decide_provider 退回 "<slug>/<model>" split,行为不变。
let r = StaticResolver::new(
None,
vec![
provider("openai", "https://up-1", "sk-1"),
provider("deepseek", "https://up-2", "sk-2"),
],
Some("openai".into()),
);
let p = parts_with(&[]);
let res = r
.resolve(&p, br#"{"model":"deepseek/deepseek-v4-pro"}"#)
.unwrap();
assert_eq!(res.provider_id, "deepseek");
assert_eq!(res.rewritten_model.as_deref(), Some("deepseek-v4-pro"));
}
}
6 changes: 4 additions & 2 deletions crates/registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ pub use healing::heal_builtin_extra_headers;
pub use healing::heal_builtin_provider_fields;
pub use healing::heal_legacy_update_url;
pub use model_alias::{
empty_model_mappings, has_internal_one_m_suffix, normalize_model_mappings, openai_model_slot,
provider_slug, strip_internal_model_suffix, MODEL_ORDER, MODEL_SLOTS,
build_catalog_slug_map, empty_model_mappings, has_internal_one_m_suffix,
normalize_model_mappings, openai_model_slot, pooled_model_ids, pooled_models_with_one_m,
provider_slug, strip_internal_model_suffix, unique_pool_slugs, PoolEntry, MODEL_ORDER,
MODEL_SLOTS, POOL_SLUG_SEPARATOR,
};
pub use model_context_policy::{
documented_context_window, model_supports_1m, ONE_M_CONTEXT_WINDOW,
Expand Down
Loading
Loading