Fix Gemini 3 on OpenRouter (#43416)

Release Notes:

- Gemini 3 now works on OpenRouter in the Agent Panel
This commit is contained in:
Richard Feldman
2025-11-24 13:24:26 -05:00
committed by GitHub
parent a7fff59136
commit 6631d8be4e
22 changed files with 408 additions and 37 deletions
+1
View File
@@ -182,6 +182,7 @@ impl DbThread {
crate::Message::Agent(AgentMessage {
content,
tool_results,
reasoning_details: None,
})
}
language_model::Role::System => {
+1
View File
@@ -703,6 +703,7 @@ impl EditAgent {
role: Role::User,
content: vec![MessageContent::Text(prompt)],
cache: false,
reasoning_details: None,
});
// Include tools in the request so that we can take advantage of
+3
View File
@@ -1081,6 +1081,7 @@ fn message(
role,
content: contents.into_iter().collect(),
cache: false,
reasoning_details: None,
}
}
@@ -1268,6 +1269,7 @@ impl EvalAssertion {
role: Role::User,
content: vec![prompt.into()],
cache: false,
reasoning_details: None,
}],
thinking_allowed: true,
..Default::default()
@@ -1594,6 +1596,7 @@ impl EditAgentTest {
role: Role::System,
content: vec![MessageContent::Text(system_prompt)],
cache: true,
reasoning_details: None,
}]
.into_iter()
.chain(eval.conversation)
+58 -29
View File
@@ -215,7 +215,8 @@ async fn test_prompt_caching(cx: &mut TestAppContext) {
vec![LanguageModelRequestMessage {
role: Role::User,
content: vec!["Message 1".into()],
cache: true
cache: true,
reasoning_details: None,
}]
);
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::Text(
@@ -239,17 +240,20 @@ async fn test_prompt_caching(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Message 1".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec!["Response to Message 1".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Message 2".into()],
cache: true
cache: true,
reasoning_details: None,
}
]
);
@@ -295,37 +299,44 @@ async fn test_prompt_caching(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Message 1".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec!["Response to Message 1".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Message 2".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec!["Response to Message 2".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Use the echo tool".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec![MessageContent::ToolUse(tool_use)],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::ToolResult(tool_result)],
cache: true
cache: true,
reasoning_details: None,
}
]
);
@@ -648,17 +659,20 @@ async fn test_resume_after_tool_use_limit(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["abc".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec![MessageContent::ToolUse(tool_use.clone())],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::ToolResult(tool_result.clone())],
cache: true
cache: true,
reasoning_details: None,
},
]
);
@@ -682,22 +696,26 @@ async fn test_resume_after_tool_use_limit(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["abc".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec![MessageContent::ToolUse(tool_use)],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::ToolResult(tool_result)],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Continue where you left off".into()],
cache: true
cache: true,
reasoning_details: None,
}
]
);
@@ -769,22 +787,26 @@ async fn test_send_after_tool_use_limit(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["abc".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec![MessageContent::ToolUse(tool_use)],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::ToolResult(tool_result)],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
content: vec!["ghi".into()],
cache: true
cache: true,
reasoning_details: None,
}
]
);
@@ -1827,7 +1849,8 @@ async fn test_building_request_with_pending_tools(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Hey!".into()],
cache: true
cache: true,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
@@ -1835,7 +1858,8 @@ async fn test_building_request_with_pending_tools(cx: &mut TestAppContext) {
MessageContent::Text("Hi!".into()),
MessageContent::ToolUse(echo_tool_use.clone())
],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
@@ -1846,7 +1870,8 @@ async fn test_building_request_with_pending_tools(cx: &mut TestAppContext) {
content: "test".into(),
output: Some("test".into())
})],
cache: false
cache: false,
reasoning_details: None,
},
],
);
@@ -2244,12 +2269,14 @@ async fn test_send_retry_finishes_tool_calls_on_error(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Call the echo tool!".into()],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec![language_model::MessageContent::ToolUse(tool_use_1.clone())],
cache: false
cache: false,
reasoning_details: None,
},
LanguageModelRequestMessage {
role: Role::User,
@@ -2262,7 +2289,8 @@ async fn test_send_retry_finishes_tool_calls_on_error(cx: &mut TestAppContext) {
output: Some("test".into())
}
)],
cache: true
cache: true,
reasoning_details: None,
},
]
);
@@ -2276,7 +2304,8 @@ async fn test_send_retry_finishes_tool_calls_on_error(cx: &mut TestAppContext) {
thread.last_message(),
Some(Message::Agent(AgentMessage {
content: vec![AgentMessageContent::Text("Done".into())],
tool_results: IndexMap::default()
tool_results: IndexMap::default(),
reasoning_details: None,
}))
);
})
+20
View File
@@ -113,6 +113,7 @@ impl Message {
role: Role::User,
content: vec!["Continue where you left off".into()],
cache: false,
reasoning_details: None,
}],
}
}
@@ -177,6 +178,7 @@ impl UserMessage {
role: Role::User,
content: Vec::with_capacity(self.content.len()),
cache: false,
reasoning_details: None,
};
const OPEN_CONTEXT: &str = "<context>\n\
@@ -444,6 +446,7 @@ impl AgentMessage {
role: Role::Assistant,
content: Vec::with_capacity(self.content.len()),
cache: false,
reasoning_details: self.reasoning_details.clone(),
};
for chunk in &self.content {
match chunk {
@@ -479,6 +482,7 @@ impl AgentMessage {
role: Role::User,
content: Vec::new(),
cache: false,
reasoning_details: None,
};
for tool_result in self.tool_results.values() {
@@ -508,6 +512,7 @@ impl AgentMessage {
pub struct AgentMessage {
pub content: Vec<AgentMessageContent>,
pub tool_results: IndexMap<LanguageModelToolUseId, LanguageModelToolResult>,
pub reasoning_details: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -1398,6 +1403,18 @@ impl Thread {
self.handle_thinking_event(text, signature, event_stream, cx)
}
RedactedThinking { data } => self.handle_redacted_thinking_event(data, cx),
ReasoningDetails(details) => {
let last_message = self.pending_message();
// Store the last non-empty reasoning_details (overwrites earlier ones)
// This ensures we keep the encrypted reasoning with signatures, not the early text reasoning
if let serde_json::Value::Array(ref arr) = details {
if !arr.is_empty() {
last_message.reasoning_details = Some(details);
}
} else {
last_message.reasoning_details = Some(details);
}
}
ToolUse(tool_use) => {
return Ok(self.handle_tool_use_event(tool_use, event_stream, cx));
}
@@ -1673,6 +1690,7 @@ impl Thread {
role: Role::User,
content: vec![SUMMARIZE_THREAD_DETAILED_PROMPT.into()],
cache: false,
reasoning_details: None,
});
let task = cx
@@ -1737,6 +1755,7 @@ impl Thread {
role: Role::User,
content: vec![SUMMARIZE_THREAD_PROMPT.into()],
cache: false,
reasoning_details: None,
});
self.pending_title_generation = Some(cx.spawn(async move |this, cx| {
let mut title = String::new();
@@ -1984,6 +2003,7 @@ impl Thread {
role: Role::System,
content: vec![system_prompt.into()],
cache: false,
reasoning_details: None,
}];
for message in &self.messages {
messages.extend(message.to_request());