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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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 } => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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, };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user