mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 18:05:37 +08:00
refactor(proxy): replace panic-prone unwrap/expect with safe patterns
Harden the proxy module against panics by removing optimistic
unwrap()/expect() calls in favor of pattern matching and graceful
fallbacks:
- copilot_optimizer/cache_injector: bind Value::Array/String directly
instead of is_array()+as_array().unwrap(); use is_none_or and in-place
string mutation
- hyper_client: gate the raw-write path with if-let + filter instead of
has_cases + unwrap()
- gemini_shadow: recover poisoned RwLock via into_inner() rather than
panicking, with warn logging
- streaming_codex_chat: replace expect("tool state exists") with
let-else (return/continue)
- merge_tool_results: early-return when messages is absent
- sse: fall back to from_utf8_lossy on the UTF-8 boundary slice
No behavior change on the happy path; all proxy tests pass.
This commit is contained in:
@@ -87,14 +87,13 @@ pub fn classify_request(
|
||||
// copilot-api 通过先 merge(text 吸收进 tool_result)再 classify 实现同等效果;
|
||||
// 直接在分类层处理更稳健,不依赖 merge 启用状态和执行顺序。
|
||||
let is_user_initiated = match last_msg.get("content") {
|
||||
Some(content) if content.is_array() => {
|
||||
let blocks = content.as_array().unwrap();
|
||||
Some(Value::Array(blocks)) => {
|
||||
// 含有 tool_result → 工具续写(agent),否则 → 用户发起(user)
|
||||
!blocks
|
||||
.iter()
|
||||
.any(|block| block.get("type").and_then(|t| t.as_str()) == Some("tool_result"))
|
||||
}
|
||||
Some(content) if content.is_string() => true,
|
||||
Some(Value::String(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -124,7 +123,9 @@ fn is_warmup_request(body: &Value, has_anthropic_beta: bool, is_compact: bool) -
|
||||
return false;
|
||||
}
|
||||
// 无工具定义
|
||||
!matches!(body.get("tools"), Some(tools) if tools.is_array() && !tools.as_array().unwrap().is_empty())
|
||||
body.get("tools")
|
||||
.and_then(|tools| tools.as_array())
|
||||
.is_none_or(|tools| tools.is_empty())
|
||||
}
|
||||
|
||||
/// 检测是否为 Claude Code 上下文压缩/compact 请求。
|
||||
@@ -228,7 +229,10 @@ pub fn merge_tool_results(mut body: Value) -> Value {
|
||||
}
|
||||
|
||||
// Phase 2: 跨消息合并 — 连续的 tool_result-only 用户消息合并
|
||||
let messages = body["messages"].as_array().unwrap().clone();
|
||||
let messages = match body.get("messages").and_then(|m| m.as_array()) {
|
||||
Some(messages) => messages.clone(),
|
||||
None => return body,
|
||||
};
|
||||
if messages.len() <= 1 {
|
||||
return body;
|
||||
}
|
||||
@@ -422,10 +426,8 @@ pub fn sanitize_orphan_tool_results(mut body: Value) -> Value {
|
||||
// 空 tool_use_id 或不在紧邻 assistant 的 tool_use 中 → orphan
|
||||
if tool_use_id.is_empty() || !prev_tool_use_ids.contains(tool_use_id) {
|
||||
let content_text = match block.get("content") {
|
||||
Some(c) if c.is_string() => c.as_str().unwrap_or("").to_string(),
|
||||
Some(c) if c.is_array() => c
|
||||
.as_array()
|
||||
.unwrap()
|
||||
Some(Value::String(text)) => text.clone(),
|
||||
Some(Value::Array(blocks)) => blocks
|
||||
.iter()
|
||||
.filter_map(|b| b.get("text").and_then(|t| t.as_str()))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -483,10 +485,8 @@ pub fn strip_thinking_blocks(mut body: Value) -> Value {
|
||||
/// 从请求体的 `system` 字段提取文本(处理 string/array 两种格式)。
|
||||
fn extract_system_text(body: &Value) -> String {
|
||||
match body.get("system") {
|
||||
Some(s) if s.is_string() => s.as_str().unwrap_or("").to_string(),
|
||||
Some(arr) if arr.is_array() => arr
|
||||
.as_array()
|
||||
.unwrap()
|
||||
Some(Value::String(text)) => text.clone(),
|
||||
Some(Value::Array(blocks)) => blocks
|
||||
.iter()
|
||||
.filter_map(|b| b.get("text").and_then(|t| t.as_str()))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -573,13 +573,12 @@ fn append_text_to_tool_result(tool_result: &mut Value, text_block: &Value) {
|
||||
}
|
||||
|
||||
// tool_result 的 content 可以是字符串或数组
|
||||
match tool_result.get("content") {
|
||||
Some(c) if c.is_string() => {
|
||||
let existing = c.as_str().unwrap_or("");
|
||||
tool_result["content"] = Value::String(format!("{existing}\n{text}"));
|
||||
match tool_result.get_mut("content") {
|
||||
Some(Value::String(existing)) => {
|
||||
existing.push('\n');
|
||||
existing.push_str(text);
|
||||
}
|
||||
Some(c) if c.is_array() => {
|
||||
let arr = tool_result["content"].as_array_mut().unwrap();
|
||||
Some(Value::Array(arr)) => {
|
||||
arr.push(serde_json::json!({"type": "text", "text": text}));
|
||||
}
|
||||
_ => {
|
||||
@@ -592,21 +591,18 @@ fn append_text_to_tool_result(tool_result: &mut Value, text_block: &Value) {
|
||||
/// 从消息中提取文本内容
|
||||
fn extract_text_from_message(msg: &Value) -> String {
|
||||
match msg.get("content") {
|
||||
Some(content) if content.is_string() => content.as_str().unwrap_or("").to_string(),
|
||||
Some(content) if content.is_array() => {
|
||||
let blocks = content.as_array().unwrap();
|
||||
blocks
|
||||
.iter()
|
||||
.filter_map(|block| {
|
||||
if block.get("type").and_then(|t| t.as_str()) == Some("text") {
|
||||
block.get("text").and_then(|t| t.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
Some(Value::String(text)) => text.clone(),
|
||||
Some(Value::Array(blocks)) => blocks
|
||||
.iter()
|
||||
.filter_map(|block| {
|
||||
if block.get("type").and_then(|t| t.as_str()) == Some("text") {
|
||||
block.get("text").and_then(|t| t.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user