Gemini fixes (#1349)

* Fix gemini yolo

* limit diff unchanged lines context to 3 lines

* fix shell command parsing

* remove the GeminiModel struct
This commit is contained in:
Solomon
2025-11-20 16:38:28 +00:00
committed by GitHub
parent 1933bb463c
commit 37f8f3c74f
10 changed files with 99 additions and 151 deletions

View File

@@ -27,13 +27,18 @@
"GEMINI": {
"DEFAULT": {
"GEMINI": {
"model": "default",
"yolo": true
}
},
"FLASH": {
"GEMINI": {
"model": "flash",
"model": "gemini-2.5-flash",
"yolo": true
}
},
"PRO": {
"GEMINI": {
"model": "gemini-3-pro-preview",
"yolo": true
}
}

View File

@@ -288,35 +288,27 @@ pub fn normalize_logs(msg_store: Arc<MsgStore>, worktree_path: &Path) {
completed,
command
);
let mut result = if let Some(out_val) = tc.raw_output.as_ref() {
serde_json::from_value::<ShellOutput>(out_val.clone())
.ok()
.map(|out| {
let mut exit_status = out
.exit_code
.map(|code| crate::logs::CommandExitStatus::ExitCode { code });
let output = out.stdout.or(out.stderr).unwrap_or_default();
if exit_status.is_none() && completed {
exit_status = Some(crate::logs::CommandExitStatus::Success {
success: true,
});
let tc_exit_status = match tc.status {
agent_client_protocol::ToolCallStatus::Completed => {
Some(crate::logs::CommandExitStatus::Success { success: true })
}
crate::logs::CommandRunResult {
exit_status,
output: Some(output),
agent_client_protocol::ToolCallStatus::Failed => {
Some(crate::logs::CommandExitStatus::Success { success: false })
}
_ => None,
};
let result = if let Some(text) = collect_text_content(&tc.content) {
Some(crate::logs::CommandRunResult {
exit_status: tc_exit_status,
output: Some(text),
})
} else {
None
};
if result.is_none() && completed {
result = Some(crate::logs::CommandRunResult {
exit_status: Some(crate::logs::CommandExitStatus::Success {
success: true,
}),
Some(crate::logs::CommandRunResult {
exit_status: tc_exit_status,
output: None,
});
}
})
};
ActionType::CommandRun { command, result }
}
agent_client_protocol::ToolKind::Delete => ActionType::FileEdit {
@@ -606,7 +598,13 @@ impl AcpEventParser {
/// Parse command from tool title (for execute tools)
pub fn parse_execute_command(title: &str) -> String {
title.split(" (").next().unwrap_or(title).trim().to_string()
if let Some(command) = title.split(" [current working directory ").next() {
command.trim().to_string()
} else if let Some(command) = title.split(" (").next() {
command.trim().to_string()
} else {
title.trim().to_string()
}
}
}
@@ -652,16 +650,6 @@ struct FetchArgs {
url: String,
}
#[derive(Debug, Clone, Deserialize)]
struct ShellOutput {
#[serde(default)]
exit_code: Option<i32>,
#[serde(default)]
stdout: Option<String>,
#[serde(default)]
stderr: Option<String>,
}
#[derive(Debug, Clone, Default)]
struct StreamingState {
assistant_text: Option<StreamingText>,

View File

@@ -13,10 +13,7 @@ use serde::{Deserialize, Serialize};
use tokio::process::Command;
use ts_rs::TS;
use workspace_utils::{
approvals::ApprovalStatus,
diff::{concatenate_diff_hunks, create_unified_diff, create_unified_diff_hunk},
log_msg::LogMsg,
msg_store::MsgStore,
approvals::ApprovalStatus, diff::create_unified_diff, log_msg::LogMsg, msg_store::MsgStore,
path::make_path_relative,
};
@@ -568,25 +565,21 @@ impl ClaudeLogProcessor {
}
}
ClaudeToolData::MultiEdit { file_path, edits } => {
let hunks: Vec<String> = edits
let changes: Vec<FileChange> = edits
.iter()
.filter_map(|edit| {
if edit.old_string.is_some() || edit.new_string.is_some() {
Some(create_unified_diff_hunk(
.filter(|edit| edit.old_string.is_some() || edit.new_string.is_some())
.map(|edit| FileChange::Edit {
unified_diff: create_unified_diff(
file_path,
&edit.old_string.clone().unwrap_or_default(),
&edit.new_string.clone().unwrap_or_default(),
))
} else {
None
}
),
has_line_numbers: false,
})
.collect();
ActionType::FileEdit {
path: make_path_relative(file_path, worktree_path),
changes: vec![FileChange::Edit {
unified_diff: concatenate_diff_hunks(file_path, &hunks),
has_line_numbers: false,
}],
changes,
}
}
ClaudeToolData::Write { file_path, content } => {

View File

@@ -9,10 +9,7 @@ use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, process::Command};
use ts_rs::TS;
use workspace_utils::{
diff::{
concatenate_diff_hunks, create_unified_diff, create_unified_diff_hunk,
extract_unified_diff_hunks,
},
diff::{concatenate_diff_hunks, create_unified_diff, extract_unified_diff_hunks},
msg_store::MsgStore,
path::make_path_relative,
shell::resolve_executable_path,
@@ -727,15 +724,19 @@ impl CursorToolCall {
}
if let Some(multi_str_replace) = &args.multi_str_replace {
let hunks: Vec<String> = multi_str_replace
let edits: Vec<FileChange> = multi_str_replace
.edits
.iter()
.map(|edit| create_unified_diff_hunk(&edit.old_text, &edit.new_text))
.collect();
changes.push(FileChange::Edit {
unified_diff: concatenate_diff_hunks(&path, &hunks),
.map(|edit| FileChange::Edit {
unified_diff: create_unified_diff(
&path,
&edit.old_text,
&edit.new_text,
),
has_line_numbers: false,
});
})
.collect();
changes.extend(edits);
}
if changes.is_empty()

View File

@@ -260,23 +260,30 @@ pub fn normalize_logs(
DroidToolData::MultiEdit { file_path, edits } => {
let path = make_path_relative(&file_path, &worktree_path_str);
let hunks: Vec<String> = edits
let changes: Vec<FileChange> = edits
.iter()
.filter_map(|edit| {
if edit.old_string.is_some() || edit.new_string.is_some() {
Some(workspace_utils::diff::create_unified_diff_hunk(
&edit.old_string.clone().unwrap_or_default(),
&edit.new_string.clone().unwrap_or_default(),
))
Some(FileChange::Edit {
unified_diff:
workspace_utils::diff::create_unified_diff(
&file_path,
&edit
.old_string
.clone()
.unwrap_or_default(),
&edit
.new_string
.clone()
.unwrap_or_default(),
),
has_line_numbers: false,
})
} else {
None
}
})
.collect();
let changes = vec![FileChange::Edit {
unified_diff: concatenate_diff_hunks(&file_path, &hunks),
has_line_numbers: false,
}];
let tool_state = FileEditState {
index: None,

View File

@@ -12,34 +12,12 @@ use crate::{
executors::{AppendPrompt, ExecutorError, SpawnedChild, StandardCodingAgentExecutor},
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GeminiModel {
Default, // no --model flag
Flash, // --model gemini-2.5-flash
}
impl GeminiModel {
fn base_command(&self) -> &'static str {
"npx -y @google/gemini-cli@0.16.0"
}
fn build_command_builder(&self) -> CommandBuilder {
let mut builder = CommandBuilder::new(self.base_command());
if let GeminiModel::Flash = self {
builder = builder.extend_params(["--model", "gemini-2.5-flash"]);
}
builder
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
pub struct Gemini {
#[serde(default)]
pub append_prompt: AppendPrompt,
pub model: GeminiModel,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub yolo: Option<bool>,
#[serde(flatten)]
@@ -48,10 +26,15 @@ pub struct Gemini {
impl Gemini {
fn build_command_builder(&self) -> CommandBuilder {
let mut builder = self.model.build_command_builder();
let mut builder = CommandBuilder::new("npx -y @google/gemini-cli@0.16.0");
if let Some(model) = &self.model {
builder = builder.extend_params(["--model", model.as_str()]);
}
if self.yolo.unwrap_or(false) {
builder = builder.extend_params(["--yolo"]);
builder = builder.extend_params(["--allowed-tools", "run_shell_command"]);
}
builder = builder.extend_params(["--experimental-acp"]);

View File

@@ -113,7 +113,6 @@ fn generate_types_content() -> String {
executors::executors::BaseAgentCapability::decl(),
executors::executors::claude::ClaudeCode::decl(),
executors::executors::gemini::Gemini::decl(),
executors::executors::gemini::GeminiModel::decl(),
executors::executors::amp::Amp::decl(),
executors::executors::codex::Codex::decl(),
executors::executors::codex::SandboxMode::decl(),

View File

@@ -45,50 +45,28 @@ pub enum DiffChangeKind {
// Unified diff utility functions
// ==============================
/// Converts a replace diff to a unified diff hunk without the hunk header.
/// The hunk returned will have valid hunk, and diff lines.
pub fn create_unified_diff_hunk(old: &str, new: &str) -> String {
// normalize ending line feed to optimize diff output
let mut old = old.to_string();
let mut new = new.to_string();
if !old.ends_with('\n') {
old.push('\n');
}
if !new.ends_with('\n') {
new.push('\n');
}
/// Converts a replace diff to a list of unified diff hunks.
/// Uses a context limit of 3 lines.
fn create_unified_diff_hunks(old: &str, new: &str) -> Vec<String> {
let old = ensure_newline(old);
let new = ensure_newline(new);
let diff = TextDiff::from_lines(&old, &new);
let mut out = String::new();
// Generate unified diff with context
let unified_diff = diff
.unified_diff()
.context_radius(3)
.header("a", "b")
.to_string();
// We need a valud hunk header. assume lines are 0. but - + count will be correct.
let old_count = diff.old_slices().len();
let new_count = diff.new_slices().len();
out.push_str(&format!("@@ -1,{old_count} +1,{new_count} @@\n"));
for change in diff.iter_all_changes() {
let sign = match change.tag() {
ChangeTag::Equal => ' ',
ChangeTag::Delete => '-',
ChangeTag::Insert => '+',
};
let val = change.value();
out.push(sign);
out.push_str(val);
}
out
extract_unified_diff_hunks(&unified_diff)
}
/// Creates a full unified diff with the file path in the header.
pub fn create_unified_diff(file_path: &str, old: &str, new: &str) -> String {
let mut out = String::new();
out.push_str(format!("--- a/{file_path}\n+++ b/{file_path}\n").as_str());
out.push_str(&create_unified_diff_hunk(old, new));
out
let hunks = create_unified_diff_hunks(old, new);
concatenate_diff_hunks(file_path, &hunks)
}
/// Compute addition/deletion counts between two text snapshots.

View File

@@ -1,9 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"model"
],
"type": "object",
"properties": {
"append_prompt": {
"title": "Append Prompt",
@@ -16,10 +12,9 @@
"default": null
},
"model": {
"type": "string",
"enum": [
"default",
"flash"
"type": [
"string",
"null"
]
},
"yolo": {
@@ -47,5 +42,6 @@
"type": "string"
}
}
}
},
"type": "object"
}

View File

@@ -236,9 +236,7 @@ export enum BaseAgentCapability { SESSION_FORK = "SESSION_FORK", SETUP_HELPER =
export type ClaudeCode = { append_prompt: AppendPrompt, claude_code_router?: boolean | null, plan?: boolean | null, approvals?: boolean | null, model?: string | null, dangerously_skip_permissions?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
export type Gemini = { append_prompt: AppendPrompt, model: GeminiModel, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
export type GeminiModel = "default" | "flash";
export type Gemini = { append_prompt: AppendPrompt, model?: string | null, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
export type Amp = { append_prompt: AppendPrompt, dangerously_allow_all?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };