copilot_chat: Serialize ToolChoice::Any as "required" instead of "any" (#52015)
**Observed behavior:** Inline assistant failed for GPT-4.1, Gemini, and other non-Anthropic models. Claude worked correctly because Anthropic's API accepts `"any"` as a valid value. ## Fix Renamed `ToolChoice::Any` → `ToolChoice::Required` in both `copilot_chat.rs` and `responses.rs`, matching the convention used by other OpenAI-compatible providers (`open_ai`, `lmstudio`, `open_router`). `copilot_chat::ToolChoice` is a wire type only for the `/chat/completions` path — Anthropic models go through `into_anthropic()` and never touch it, so no per-model logic is needed. Also fixes the same serialization bug in `responses::ToolChoice`, which was not covered by the original approach, and adds regression tests for both. ## Affected models - `gpt-4.1` via copilot_chat provider - `gemini-*` via copilot_chat provider - Likely affects all OpenAI-compatible models routed through copilot_chat ## Screenshots **Bug (only Claude works, Gemini and GPT-4.1 fail):** <img width="598" height="209" alt="image" src="https://github.com/user-attachments/assets/bbd418d9-7de3-4191-9ca9-fd1961534e23" /> **Fix:** <img width="532" height="154" alt="image" src="https://github.com/user-attachments/assets/86bb0f8e-67e6-4417-9b78-b1b7ad328e9e" /> **Result:** After the fix, all models work correctly via inline assistant. ## Release Notes - Fix inline assistant 400 errors for GPT-4.1, Gemini, and other non-Anthropic models via the copilot_chat provider (`tool_choice` was sending `"any"` instead of `"required"`)
This commit is contained in:
1 parent
fcbf0a75b2
commit
c5564f9eb0
3 files changed
+45
-4
No files matched your search
@@ -370,7 +370,7 @@ pub enum Tool {
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ToolChoice {
|
||||
Auto,
|
||||
Any,
|
||||
Required,
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -1736,4 +1736,22 @@ mod tests {
|
||||
// Only /v1/messages endpoint -> supports_response = false (doesn't have /responses)
|
||||
assert!(!model_with_messages.supports_response());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_choice_required_serializes_as_required() {
|
||||
// Regression test: ToolChoice::Required must serialize as "required" (not "any")
|
||||
// for OpenAI-compatible APIs. Reverting the rename would break this.
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ToolChoice::Required).unwrap(),
|
||||
"\"required\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ToolChoice::Auto).unwrap(),
|
||||
"\"auto\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ToolChoice::None).unwrap(),
|
||||
"\"none\""
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ pub enum ToolDefinition {
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ToolChoice {
|
||||
Auto,
|
||||
Any,
|
||||
Required,
|
||||
None,
|
||||
#[serde(untagged)]
|
||||
Other(ToolDefinition),
|
||||
@@ -408,3 +408,26 @@ pub async fn stream_response(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tool_choice_required_serializes_as_required() {
|
||||
// Regression test: ToolChoice::Required must serialize as "required" (not "any")
|
||||
// for OpenAI Responses API. Reverting the rename would break this.
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ToolChoice::Required).unwrap(),
|
||||
"\"required\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ToolChoice::Auto).unwrap(),
|
||||
"\"auto\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ToolChoice::None).unwrap(),
|
||||
"\"none\""
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1046,7 +1046,7 @@ fn into_copilot_chat(
|
||||
tools,
|
||||
tool_choice: tool_choice.map(|choice| match choice {
|
||||
LanguageModelToolChoice::Auto => ToolChoice::Auto,
|
||||
LanguageModelToolChoice::Any => ToolChoice::Any,
|
||||
LanguageModelToolChoice::Any => ToolChoice::Required,
|
||||
LanguageModelToolChoice::None => ToolChoice::None,
|
||||
}),
|
||||
thinking_budget: None,
|
||||
@@ -1255,7 +1255,7 @@ fn into_copilot_responses(
|
||||
|
||||
let mapped_tool_choice = tool_choice.map(|choice| match choice {
|
||||
LanguageModelToolChoice::Auto => responses::ToolChoice::Auto,
|
||||
LanguageModelToolChoice::Any => responses::ToolChoice::Any,
|
||||
LanguageModelToolChoice::Any => responses::ToolChoice::Required,
|
||||
LanguageModelToolChoice::None => responses::ToolChoice::None,
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user