Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
31f4c09
feat(MOC-236): 池化模型路由后端引擎(exposeAllProviderModels,默认关)
Cmochance Jun 14, 2026
0add728
Merge remote-tracking branch 'origin/main' into worktree-feat+pool-mo…
Cmochance Jun 14, 2026
2047775
fix(MOC-236): 池化 catalog 修两处 bot review P2
Cmochance Jun 14, 2026
1a63e39
fix(MOC-236): 池模式 provider CRUD/autofill/reorder 后重建池 catalog + resolver
Cmochance Jun 14, 2026
fa1d281
fix(MOC-236): 池化开关 re-apply 失败改后端 loud log(去前端未消费的响应字段)
Cmochance Jun 14, 2026
234cd27
fix(MOC-236): 池模式 CRUD re-sync 失败 loud log(不再静默丢弃)
Cmochance Jun 14, 2026
8c85a67
feat(MOC-236): pooledModels 持久化 —— 池子从「获取模型」拉取的完整列表生成
Cmochance Jun 14, 2026
f66f1da
feat(MOC-236): 池化前端 UX —— 编辑流解耦 + 开关文案 + set-default 语义切换
Cmochance Jun 14, 2026
b4eda60
fix(MOC-236): #477 bot review 2 条 P2
Cmochance Jun 14, 2026
cbfbc5b
fix(MOC-236): #477 bot review round-2(pooledModels chat 过滤 + 并入手输 map…
Cmochance Jun 14, 2026
c3bf82b
fix(MOC-236): #477 bot review round-3(池合并永不收缩 + 不推荐 embedding default)
Cmochance Jun 14, 2026
9aa0bcd
fix(MOC-236): #477 bot review round-4(映射页保存也并入 pooledModels)
Cmochance Jun 14, 2026
8876ee1
fix(MOC-236): #477 bot review round-5(upstream 身份变更清空 stale 池)
Cmochance Jun 14, 2026
9316956
fix(MOC-236): #477 bot review round-6(apiKey 变更也清空 stale 池)
Cmochance Jun 14, 2026
0591917
feat(MOC-236): 模型池子集化 + 整合 curation 后端
Cmochance Jun 14, 2026
5545e60
feat(MOC-236): 整合提供商页双卡池 UI(子集选择 + 模型 curation)
Cmochance Jun 14, 2026
c4805b0
style(MOC-236): cargo fmt 新增池子集测试
Cmochance Jun 14, 2026
2b981ea
fix(MOC-236): #477 bot review(显式空池不回退槽位映射)
Cmochance Jun 14, 2026
6b57352
fix(MOC-236): #477 bot review round-2(整合获取不改映射 + 保留缺省/空之分)
Cmochance Jun 14, 2026
9a1e18f
fix(MOC-236): #477 bot review round-3(pooledModels 单写者 = 整合页)
Cmochance Jun 14, 2026
f06ef0a
fix(MOC-236): #477 bot review round-4(加入整合保映射 + 显式空池不复活)
Cmochance Jun 14, 2026
80e85ec
fix(MOC-236): 整合页标题+开关不显示(删旧 #page-providers .page-title hide)
Cmochance Jun 14, 2026
ab88ca6
fix(MOC-236): #477 bot review round-5(身份变更清空 stale 池 + 空池一致回退)
Cmochance Jun 14, 2026
fe4d5be
fix(MOC-236): #477 bot review round-6(池化模式禁用 legacy slug 路由到已排除 provi…
Cmochance Jun 14, 2026
3c4f412
fix(MOC-236): #477 bot review round-7(身份变更置显式空池,不回退陈旧映射)
Cmochance Jun 14, 2026
10bcaa8
fix(MOC-236): #477 bot review round-8(pool-miss 不退回被排除的 active provider)
Cmochance Jun 14, 2026
bd3fa90
fix(MOC-236): #477 bot review round-9(vision/audio 多模态 chat 模型保留进池)
Cmochance Jun 14, 2026
8a74289
feat(MOC-236): 整合改用标准档映射(provider/model 不进 Codex,中间映射区)
Cmochance Jun 15, 2026
8c7e5e3
fix(MOC-236): #477 bot review(槽位映射须指向当前池模型 + 导入保留 poolSlotMappings)
Cmochance Jun 15, 2026
9a08fc4
fix(MOC-236): #477 bot review(池模式 catalog miss 路由到池默认档,不走单 provider 映射)
Cmochance Jun 15, 2026
737daf4
fix(MOC-236): #477 bot review(串行化 poolSlotMappings 保存,防并发乱序覆盖)
Cmochance Jun 15, 2026
1126c4a
fix(MOC-236): 整合模式补「应用」入口 + 强制走代理(用户反馈:整合没生效)
Cmochance Jun 15, 2026
ee8d2c8
Merge remote-tracking branch 'origin/main' into worktree-feat+pool-fr…
Cmochance Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 168 additions & 15 deletions crates/codex_integration/src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::auth::{read_auth, write_auth};
use crate::model_catalog::{
catalog_models_for_provider_with_display_names, clear_catalog_models, upsert_catalog_models,
CODEX_MODEL_CATALOG_KEY,
CatalogModel, CODEX_MODEL_CATALOG_KEY,
};
use crate::paths::CodexPaths;
use crate::snapshot::{
Expand Down Expand Up @@ -97,6 +97,16 @@ pub struct ApplyConfig<'a> {
/// 改走现有 `openai_base_url` 根键路径。
#[serde(default)]
pub preserve_chatgpt_auth: bool,
/// **池化模式**:全 provider 的 catalog 模型列表(由 src-tauri 用
/// `unique_pool_slugs` + `catalog_models_for_pool` 构建)。`Some` → catalog 写池
/// (Codex picker 显示所有 provider 的所有模型);`None` → 走单 active provider 路径
/// (与池化前**字节一致**)。仅 local_proxy 模式可能为 `Some`(direct 不写 catalog)。
#[serde(skip)]
pub pool: Option<&'a [CatalogModel]>,
/// 池模式下 root `model` 锚定目标 slug(一般是 active provider 的池默认 slug)。
/// 仅当当前 config.toml 的 `model` 不在新 catalog 时用它重置(单模式锚 `gpt-5.5`)。
#[serde(skip)]
pub pool_default_slug: Option<&'a str>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -208,15 +218,21 @@ pub fn apply_provider(paths: &CodexPaths, cfg: &ApplyConfig) -> Result<ApplyResu
// 选择器里 fallback 到内置 GPT 系列名("GPT-5.5"等),用户看不到真实
// provider/model。现在每条 provider 都通过 catalog 把 display_name 设成
// "<provider> / <real-model>",`model_context_window` 仍只在 1M 时设。
let models = catalog_models_for_provider_with_display_names(
cfg.provider_name,
cfg.default_model,
cfg.supports_1m,
cfg.model_mappings,
cfg.model_capabilities,
cfg.model_display_names,
cfg.review_model_slot,
);
// 池化(整合)模式:catalog = src-tauri 构建好的池条目(标准档映射);否则单 active
// provider catalog(行为不变)。后续 `if models.is_empty()` 的留空 strip 逻辑两种都适用。
let models = if let Some(pool) = cfg.pool {
pool.to_vec()
} else {
catalog_models_for_provider_with_display_names(
cfg.provider_name,
cfg.default_model,
cfg.supports_1m,
cfg.model_mappings,
cfg.model_capabilities,
cfg.model_display_names,
cfg.review_model_slot,
)
};
if models.is_empty() {
// [MOC-234] responses passthrough provider 允许**留空默认模型**(UI 如此 ——
// model 原样透传给原生上游),此时 catalog models 为空。**绝不写 `models:[]`
Expand Down Expand Up @@ -261,11 +277,35 @@ pub fn apply_provider(paths: &CodexPaths, cfg: &ApplyConfig) -> Result<ApplyResu
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
Err(e) => return Err(e.into()),
};
let model_needs_reset = current_model
.as_deref()
.is_some_and(|m| !m.is_empty() && !models.iter().any(|cm| cm.slug == m));
if model_needs_reset && models.iter().any(|cm| cm.slug == "gpt-5.5") {
sync_root_value(&paths.config_toml, "model", Some("\"gpt-5.5\""))?;
let model_needs_reset = match current_model.as_deref() {
Some(m) => !m.is_empty() && !models.iter().any(|cm| cm.slug == m),
// 池模式下 config 无 root `model` key:Codex 隐式默认 gpt-5.5,但池 catalog 不含
// 该 slug(全是 `<provider>/<model>`)→ 必须主动锚到池默认 slug,否则新会话起在
// 一个不在 catalog 的隐式模型上(bot review P2)。单模式 catalog 含 gpt-5.5,隐式
// 默认即可、不强写(保持原行为)。
None => cfg.pool.is_some(),
};
if model_needs_reset {
// 锚定目标:池模式 → active provider 的池默认 slug(回退到首条池条目);
// 单模式 → gpt-5.5。只在目标确实在刚写入的 catalog 里时才重置(守
// no-silent-destructive:绝不把 model 设成 catalog 没有的 slug)。
let anchor: Option<&str> = if cfg.pool.is_some() {
match cfg.pool_default_slug {
Some(s) if !s.is_empty() && models.iter().any(|cm| cm.slug == s) => Some(s),
_ => models.first().map(|m| m.slug.as_str()),
}
} else if models.iter().any(|cm| cm.slug == "gpt-5.5") {
Some("gpt-5.5")
} else {
None
};
if let Some(anchor) = anchor {
sync_root_value(
&paths.config_toml,
"model",
Some(&toml_string_literal(anchor)),
)?;
}
}
if cfg.supports_1m {
sync_root_value(&paths.config_toml, "model_context_window", Some("1000000"))?;
Expand Down Expand Up @@ -609,6 +649,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -661,6 +703,8 @@ mod tests {
app_version: "v2.0.0-stage2.5",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -717,6 +761,8 @@ mod tests {
app_version: "v2.0.0-stage2.5",
codex_network_access: true,
preserve_chatgpt_auth: true,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -764,6 +810,8 @@ mod tests {
app_version: "v",
codex_network_access: false,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -810,6 +858,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -878,6 +928,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -945,6 +997,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1009,6 +1063,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1049,6 +1105,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand All @@ -1069,6 +1127,67 @@ mod tests {
.any(|m| m["slug"] == "gpt-5.5"));
}

#[test]
fn apply_pool_anchors_model_when_config_has_no_model_key() {
// bot review P2:全新 config(无 root `model`)+ 池模式 → 必须锚到 pool_default_slug,
// 否则 Codex 隐式默认 gpt-5.5,而池 catalog 全是 `<provider>/<model>` slug、不含它。
let (_t, paths) = setup();
let pool = vec![
CatalogModel {
slug: "deepseek/deepseek-v4-pro".into(),
display_name: "DeepSeek / deepseek-v4-pro".into(),
provider_name: "DeepSeek".into(),
context_window: 1_000_000,
effective_context_window_percent: 95,
auto_review_model_override: None,
},
CatalogModel {
slug: "kimi/kimi-k2.6".into(),
display_name: "Kimi / kimi-k2.6".into(),
provider_name: "Kimi".into(),
context_window: 262_144,
effective_context_window_percent: 95,
auto_review_model_override: None,
},
];
apply_provider(
&paths,
&ApplyConfig {
base_url: "http://127.0.0.1:18080",
gateway_api_key: "k",
supports_1m: false,
provider_name: "DeepSeek",
default_model: "deepseek-v4-pro",
model_mappings: None,
model_capabilities: None,
model_display_names: None,
review_model_slot: None,
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: Some(&pool),
pool_default_slug: Some("deepseek/deepseek-v4-pro"),
},
)
.unwrap();
let toml = read_toml(&paths);
assert!(
toml.contains(r#"model = "deepseek/deepseek-v4-pro""#),
"全新 pool config 应锚到 pool_default_slug:\n{toml}"
);
let catalog: serde_json::Value =
serde_json::from_slice(&std::fs::read(&paths.model_catalog_json).unwrap()).unwrap();
let slugs: Vec<&str> = catalog["models"]
.as_array()
.unwrap()
.iter()
.filter_map(|m| m["slug"].as_str())
.collect();
assert!(slugs.contains(&"deepseek/deepseek-v4-pro"));
assert!(slugs.contains(&"kimi/kimi-k2.6"));
assert!(!slugs.contains(&"gpt-5.5"), "池 catalog 不应含 gpt-5.5");
}

#[test]
fn apply_with_supports_1m_uses_provider_slot_mapping() {
let (_t, paths) = setup();
Expand Down Expand Up @@ -1097,6 +1216,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1137,6 +1258,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand All @@ -1157,6 +1280,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1208,6 +1333,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1253,6 +1380,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1317,6 +1446,8 @@ mod tests {
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1500,6 +1631,8 @@ model = \"gpt-5.5\"
app_version: "v-active",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1568,6 +1701,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand All @@ -1588,6 +1723,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1622,6 +1759,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand All @@ -1648,6 +1787,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1681,6 +1822,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1720,6 +1863,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1771,6 +1916,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1825,6 +1972,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -1879,6 +2028,8 @@ model = \"gpt-5.5\"
app_version: "v",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down Expand Up @@ -2186,6 +2337,8 @@ model = \"gpt-5.5\"
app_version: "v-test",
codex_network_access: true,
preserve_chatgpt_auth: false,
pool: None,
pool_default_slug: None,
},
)
.unwrap();
Expand Down
Loading
Loading