From 7a17025a8ab271e3798038fe65124627b0f49953 Mon Sep 17 00:00:00 2001 From: Anthony Date: Fri, 6 Mar 2026 17:44:24 +0800 Subject: [PATCH] fix(ai): improve third-party API compatibility - fix: don't send 'thinking' parameter when disabled to prevent 400 errors from APIs that don't recognize this field - fix: make tool_calls.index optional with serde(default) for APIs that omit this field in streaming responses - fix: add serde alias 'output_tokens' for completion_tokens and defaults for usage fields to support APIs using different field names Fixes #75 Co-Authored-By: Claude Opus 4.6 --- .../ai/ai_stream_handlers/src/types/openai.rs | 3 +- .../core/src/infrastructure/ai/client.rs | 33 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/openai.rs b/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/openai.rs index e584b074..f7197974 100644 --- a/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/openai.rs +++ b/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/openai.rs @@ -62,7 +62,8 @@ struct Delta { #[derive(Debug, Deserialize, Clone)] struct OpenAIToolCall { #[allow(dead_code)] - index: usize, + #[serde(default)] + index: Option, #[allow(dead_code)] id: Option, #[allow(dead_code)] diff --git a/src/crates/core/src/infrastructure/ai/client.rs b/src/crates/core/src/infrastructure/ai/client.rs index c0c25d84..fe3e820f 100644 --- a/src/crates/core/src/infrastructure/ai/client.rs +++ b/src/crates/core/src/infrastructure/ai/client.rs @@ -215,25 +215,28 @@ impl AIClient { } return; } - let thinking_value = if enable { - if api_format.eq_ignore_ascii_case("anthropic") && model_name.starts_with("claude") { - let mut obj = serde_json::map::Map::new(); + + // Only add thinking field when enabled. + // Third-party APIs may not recognize the "thinking" parameter and return 400 errors. + if !enable { + return; + } + + let thinking_value = if api_format.eq_ignore_ascii_case("anthropic") && model_name.starts_with("claude") { + let mut obj = serde_json::map::Map::new(); + obj.insert( + "type".to_string(), + serde_json::Value::String("enabled".to_string()), + ); + if let Some(m) = max_tokens { obj.insert( - "type".to_string(), - serde_json::Value::String("enabled".to_string()), + "budget_tokens".to_string(), + serde_json::json!(10000u32.min(m * 3 / 4)), ); - if let Some(m) = max_tokens { - obj.insert( - "budget_tokens".to_string(), - serde_json::json!(10000u32.min(m * 3 / 4)), - ); - } - serde_json::Value::Object(obj) - } else { - serde_json::json!({ "type": "enabled" }) } + serde_json::Value::Object(obj) } else { - serde_json::json!({ "type": "disabled" }) + serde_json::json!({ "type": "enabled" }) }; request_body["thinking"] = thinking_value; }