Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
740 changes: 94 additions & 646 deletions src-tauri/src/codex_config.rs

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src-tauri/src/codex_desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ pub(crate) fn load_cc_switch_model_catalog_projection(
}

Err(format!(
"No routed models were found in {}, the CCSwitchMulti models cache, or the active provider inline catalog",
"No routed models were found in {}, the CCSwitchMulti models cache, or a legacy active provider inline catalog",
catalog_path.display()
))
}
Expand All @@ -293,11 +293,11 @@ fn codex_model_catalog_projection_from_value(
})
}

/// 从当前活动 provider TOML 内联 `models` 读取最后一道模型菜单回退。
/// 从旧版配置的活动 provider TOML 内联 `models` 读取最后一道模型菜单回退。
///
/// 生成 live config 时,CCSM 会把同一份路由目录同时投射到 JSON catalog、models cache
/// 和活动 provider 内联数组。任一 JSON 文件短暂不可读时,内联数组仍能避免整次 Desktop
/// 会话安装空白名单;这里仅读取活动 provider,不能把未启用 provider 的模型重新注入。
/// 新版 CCSM 不再写入 Codex 已不支持的 provider `models` 字段;这里保留只读兼容,
/// 让尚未完成下一次配置同步的旧安装仍能恢复模型菜单。这里只读取活动 provider,
/// 不能把未启用 provider 的模型重新注入。
fn read_active_codex_provider_inline_model_catalog() -> Option<Value> {
let text = crate::codex_config::read_codex_config_text().ok()?;
let parsed = text.parse::<toml::Value>().ok()?;
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/src/codex_paginated_history_repair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,19 @@ pub struct BlockedRolloutReasonGroup {
pub samples: Vec<String>,
}

#[cfg(any(target_os = "windows", test))]
const BLOCKED_REASON_SAMPLE_LIMIT: usize = 5;
#[cfg(any(target_os = "windows", test))]
const IMMUTABLE_BLOCKED_PREFIX: &str = "codex_paginated_history_immutable: ";
#[cfg(any(target_os = "windows", test))]
const IMMUTABLE_BLOCKED_TRAILER: &str =
"; provider migration cannot safely rewrite byte-addressed history";

/// 把原始 blocked 报文拆成 (原因码, 细分原因, 示例)。
///
/// 保护性跳过来自两组代码:迁移守卫的 `codex_paginated_history_immutable`
/// 报文,以及分页谱系/投影游标检查抛出的 `code: key=value` 报文。
#[cfg(any(target_os = "windows", test))]
fn classify_blocked_reason(message: &str) -> (String, String, Option<String>) {
let trimmed = message.trim();
if let Some(rest) = trimmed.strip_prefix(IMMUTABLE_BLOCKED_PREFIX) {
Expand Down Expand Up @@ -118,6 +122,7 @@ fn classify_blocked_reason(message: &str) -> (String, String, Option<String>) {
(code, String::new(), sample)
}

#[cfg(any(target_os = "windows", test))]
fn group_blocked_reasons(blocked: &[String]) -> Vec<BlockedRolloutReasonGroup> {
let mut groups: Vec<BlockedRolloutReasonGroup> = Vec::new();
for message in blocked {
Expand Down Expand Up @@ -1561,6 +1566,7 @@ fn repaired_projection_status_at(
Ok(status)
}

#[allow(dead_code)]
pub(crate) fn repaired_projections_caught_up(
outcome: &PaginatedHistoryRepairOutcome,
) -> Result<bool, String> {
Expand Down
47 changes: 35 additions & 12 deletions src-tauri/src/codex_subagent_profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3726,23 +3726,47 @@ mod tests {
Some("deepseek-v4-pro")
);
// *-vision* 是独立视觉模型,不属于文本角色族。
assert_eq!(deepseek_role_identity_for_model("deepseek-v4-flash-vision-exp"), None);
assert_eq!(deepseek_role_identity_for_model("deepseek-flash-vision"), None);
assert_eq!(
deepseek_role_identity_for_model("deepseek-v4-flash-vision-exp"),
None
);
assert_eq!(
deepseek_role_identity_for_model("deepseek-flash-vision"),
None
);
// 其它 DeepSeek 模型不属于 flash/pro 角色族。
assert_eq!(deepseek_role_identity_for_model("deepseek-chat"), None);
assert_eq!(deepseek_role_identity_for_model("gpt-5.6-sol"), None);
}

#[test]
fn deepseek_role_models_match_treats_aliases_as_same_model() {
assert!(deepseek_role_models_match("deepseek-flash", "deepseek-v4-flash"));
assert!(deepseek_role_models_match("deepseek-v4-flash", "deepseek-flash"));
assert!(deepseek_role_models_match("DEEPSEEK-FLASH", "deepseek-v4-flash"));
assert!(deepseek_role_models_match("deepseek-pro", "deepseek-v4-pro"));
assert!(deepseek_role_models_match(
"deepseek-flash",
"deepseek-v4-flash"
));
assert!(deepseek_role_models_match(
"deepseek-v4-flash",
"deepseek-flash"
));
assert!(deepseek_role_models_match(
"DEEPSEEK-FLASH",
"deepseek-v4-flash"
));
assert!(deepseek_role_models_match(
"deepseek-pro",
"deepseek-v4-pro"
));
assert!(deepseek_role_models_match("qwen3.8", "QWEN3.8"));
// 同族不跨角色,不同模型不匹配。
assert!(!deepseek_role_models_match("deepseek-flash", "deepseek-v4-pro"));
assert!(!deepseek_role_models_match("deepseek-v4-flash-vision-exp", "deepseek-flash"));
assert!(!deepseek_role_models_match(
"deepseek-flash",
"deepseek-v4-pro"
));
assert!(!deepseek_role_models_match(
"deepseek-v4-flash-vision-exp",
"deepseek-flash"
));
assert!(!deepseek_role_models_match("qwen3.8", "qwen3.6"));
}

Expand Down Expand Up @@ -3814,10 +3838,9 @@ mod tests {
vec![valid(profile("deepseek-v4-flash", "deepseek-v4-flash"))],
)));
// 只保留视觉模型:文本 flash 角色不得被视觉型号顶替。
compile_request.catalog_models =
vec![catalog("deepseek-v4-flash-vision-exp", true)];
let output = compile_subagent_v2_profiles(&compile_request)
.expect("compile vision-only catalog");
compile_request.catalog_models = vec![catalog("deepseek-v4-flash-vision-exp", true)];
let output =
compile_subagent_v2_profiles(&compile_request).expect("compile vision-only catalog");
let flash_status = output
.profile_statuses
.iter()
Expand Down
5 changes: 5 additions & 0 deletions src-tauri/src/process_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ impl ProcessIdentity {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProcessIdentityError {
NotFound,
#[cfg_attr(target_os = "macos", allow(dead_code))]
AccessDenied,
Unavailable(u32),
}

impl ProcessIdentityError {
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) fn from_win32(code: u32) -> Self {
const ERROR_ACCESS_DENIED: u32 = 5;
const ERROR_INVALID_PARAMETER: u32 = 87;
Expand Down Expand Up @@ -96,6 +98,7 @@ pub(crate) struct TcpPortRow {
pub pid: u32,
}

#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
fn tcp_state_label(state: u32) -> &'static str {
match state {
1 => "CLOSED",
Expand Down Expand Up @@ -439,6 +442,7 @@ pub(crate) fn harden_socket_handle_not_inheritable(raw_socket: usize) {
}

#[cfg(not(target_os = "windows"))]
#[allow(dead_code)]
pub(crate) fn harden_socket_handle_not_inheritable(_raw_socket: usize) {}

pub(crate) fn current_executable_path() -> Option<PathBuf> {
Expand Down Expand Up @@ -938,6 +942,7 @@ mod tests {
}

#[test]
#[cfg(target_os = "windows")]
fn port_rows_are_attributed_to_the_listening_process() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind listener");
let port = listener.local_addr().expect("addr").port();
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/proxy/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3605,6 +3605,7 @@ fn create_codex_chat_sse_stream_from_verified_profile<E: std::error::Error + Sen
)
}

#[allow(clippy::too_many_arguments)]
fn create_codex_chat_sse_stream_from_verified_profile_for_client<
E: std::error::Error + Send + 'static,
>(
Expand Down Expand Up @@ -3664,6 +3665,7 @@ fn chat_completion_to_response_from_verified_profile(
)
}

#[allow(clippy::too_many_arguments)]
fn chat_completion_to_response_from_verified_profile_for_client(
body: Value,
tool_context: &transform_codex_chat::CodexToolContext,
Expand Down
18 changes: 4 additions & 14 deletions src-tauri/src/services/provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3484,16 +3484,10 @@ experimental_bearer_token = "PROXY_MANAGED"
Some("PROXY_MANAGED"),
"{label}: CCSM-managed routes must use the local placeholder"
);
let provider_models = live_toml
.get("model_providers")
.and_then(|providers| providers.get("codex_model_router_v2"))
.and_then(|provider| provider.get("models"))
.and_then(|models| models.as_array())
.expect("router provider should expose inline models for Desktop custom picker");
let provider_model_ids: Vec<&str> = provider_models
.iter()
.filter_map(|model| model.get("model").and_then(|value| value.as_str()))
.collect();
assert!(
router_facade.get("models").is_none(),
"{label}: router provider must not expose the unsupported legacy models field"
);

let catalog_text =
std::fs::read_to_string(crate::codex_config::get_codex_model_catalog_path())
Expand Down Expand Up @@ -3559,10 +3553,6 @@ experimental_bearer_token = "PROXY_MANAGED"
cache_model_fields.contains(&expected),
"{label}: cache should include model field {expected}, got: {cache_model_fields:?}"
);
assert!(
provider_model_ids.contains(&expected),
"{label}: provider inline models should include {expected}, got: {provider_model_ids:?}"
);
}
};

Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/services/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5626,6 +5626,7 @@ mod tests {
assert_eq!(ProxyService::stale_listener_owner_pid(port), None);
}

#[cfg(target_os = "windows")]
#[tokio::test]
#[serial]
async fn release_stale_listener_holders_never_touches_foreign_children() {
Expand Down
11 changes: 8 additions & 3 deletions src-tauri/src/services/session_usage_codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ fn take_codex_append_bytes_read() -> u64 {
struct CodexCheckpoint {
last_byte_offset: i64,
file_size: i64,
file_modified: i64,
head_window_len: Option<i64>,
head_fingerprint: Option<i64>,
tail_fingerprint: Option<i64>,
Expand Down Expand Up @@ -362,6 +361,7 @@ struct CodexParseSnapshot {
/// 只读的、已完成 parent replay 剥离的 rollout token 事件。
///
/// 状态页必须复用此解析器,而不能把 `total_token_usage` 当成可直接相加的请求用量。
#[cfg(test)]
#[derive(Debug, Clone)]
pub(crate) struct VerifiedCodexRolloutUsageEvent {
pub model: String,
Expand All @@ -373,6 +373,7 @@ pub(crate) struct VerifiedCodexRolloutUsageEvent {

/// 解析失败、父 replay 无法验证、或某个可计费用量缺少时间戳时返回 `None`。
/// 这让读侧把它表示成未知,而不是把累计快照猜成真实用量。
#[cfg(test)]
pub(crate) fn read_verified_codex_rollout_usage(
file_path: &Path,
rollout_index: &RolloutIndex,
Expand Down Expand Up @@ -759,7 +760,7 @@ fn load_codex_checkpoint(
Ok((
last_byte_offset,
file_size,
file_modified,
_file_modified,
head_window_len,
head_fingerprint,
tail_fingerprint,
Expand All @@ -771,7 +772,6 @@ fn load_codex_checkpoint(
.map(|state| CodexCheckpoint {
last_byte_offset,
file_size,
file_modified,
head_window_len,
head_fingerprint,
tail_fingerprint,
Expand All @@ -785,6 +785,7 @@ fn load_codex_checkpoint(
}
}

#[allow(clippy::too_many_arguments)]
fn save_codex_checkpoint_on_conn(
conn: &rusqlite::Connection,
file_path: &Path,
Expand Down Expand Up @@ -1046,6 +1047,7 @@ pub fn sync_codex_usage(db: &Database) -> Result<SessionSyncResult, AppError> {
}

/// 收集所有 Codex 会话 JSONL 文件
#[cfg(test)]
fn collect_codex_session_files(codex_dir: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();

Expand Down Expand Up @@ -1086,6 +1088,7 @@ pub(crate) fn build_rollout_index(files: &[PathBuf]) -> RolloutIndex {
}

/// 递归扫描目录下的 .jsonl 文件(限制最大深度)
#[cfg(test)]
fn collect_jsonl_recursive(dir: &Path, files: &mut Vec<PathBuf>, depth: u32, max_depth: u32) {
let entries = match fs::read_dir(dir) {
Ok(e) => e,
Expand All @@ -1102,6 +1105,7 @@ fn collect_jsonl_recursive(dir: &Path, files: &mut Vec<PathBuf>, depth: u32, max
}
}

#[cfg(test)]
fn parse_codex_file(
file_path: &Path,
root_thread_id: Option<String>,
Expand Down Expand Up @@ -1783,6 +1787,7 @@ fn save_codex_session_metadata_on_conn(
Ok(())
}

#[allow(clippy::too_many_arguments)]
fn sync_codex_append(
db: &Database,
file_path: &Path,
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/services/session_usage_codex_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ pub struct CodexDiscoveryRoot {
#[derive(Debug, Clone, Default)]
pub struct CodexDiscoveryBatch {
pub paths: Vec<PathBuf>,
#[allow(dead_code)]
pub full_scan: bool,
#[allow(dead_code)]
pub next_full_scan_at: Option<i64>,
}

Expand Down
Loading
Loading