Gemini support
This commit is contained in:
@@ -7,7 +7,8 @@ fn generate_constants() -> String {
|
||||
export const EXECUTOR_TYPES: string[] = [
|
||||
"echo",
|
||||
"claude",
|
||||
"amp"
|
||||
"amp",
|
||||
"gemini"
|
||||
];
|
||||
|
||||
export const EDITOR_TYPES: EditorType[] = [
|
||||
@@ -22,7 +23,8 @@ export const EDITOR_TYPES: EditorType[] = [
|
||||
export const EXECUTOR_LABELS: Record<string, string> = {
|
||||
"echo": "Echo (Test Mode)",
|
||||
"claude": "Claude",
|
||||
"amp": "Amp"
|
||||
"amp": "Amp",
|
||||
"gemini": "Gemini"
|
||||
};
|
||||
|
||||
export const EDITOR_LABELS: Record<string, string> = {
|
||||
@@ -97,6 +99,7 @@ fn main() {
|
||||
vibe_kanban::models::task_attempt::UpdateTaskAttempt::decl(),
|
||||
vibe_kanban::models::task_attempt::CreateFollowUpAttempt::decl(),
|
||||
vibe_kanban::models::task_attempt_activity::TaskAttemptActivity::decl(),
|
||||
vibe_kanban::models::task_attempt_activity::TaskAttemptActivityWithPrompt::decl(),
|
||||
vibe_kanban::models::task_attempt_activity::CreateTaskAttemptActivity::decl(),
|
||||
vibe_kanban::routes::filesystem::DirectoryEntry::decl(),
|
||||
vibe_kanban::models::task_attempt::DiffChunkType::decl(),
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio::{
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::executors::{AmpExecutor, ClaudeExecutor, EchoExecutor};
|
||||
use crate::executors::{AmpExecutor, ClaudeExecutor, EchoExecutor, GeminiExecutor};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecutorError {
|
||||
@@ -110,6 +110,7 @@ pub enum ExecutorConfig {
|
||||
Echo,
|
||||
Claude,
|
||||
Amp,
|
||||
Gemini,
|
||||
// Future executors can be added here
|
||||
// Shell { command: String },
|
||||
// Docker { image: String, command: String },
|
||||
@@ -130,11 +131,13 @@ impl ExecutorConstants {
|
||||
ExecutorConfig::Echo,
|
||||
ExecutorConfig::Claude,
|
||||
ExecutorConfig::Amp,
|
||||
ExecutorConfig::Gemini,
|
||||
],
|
||||
executor_labels: vec![
|
||||
"Echo (Test Mode)".to_string(),
|
||||
"Claude".to_string(),
|
||||
"Amp".to_string(),
|
||||
"Gemini".to_string(),
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -146,6 +149,7 @@ impl ExecutorConfig {
|
||||
ExecutorConfig::Echo => Box::new(EchoExecutor),
|
||||
ExecutorConfig::Claude => Box::new(ClaudeExecutor),
|
||||
ExecutorConfig::Amp => Box::new(AmpExecutor),
|
||||
ExecutorConfig::Gemini => Box::new(GeminiExecutor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
84
backend/src/executors/gemini.rs
Normal file
84
backend/src/executors/gemini.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use async_trait::async_trait;
|
||||
use tokio::process::{Child, Command};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
executor::{Executor, ExecutorError},
|
||||
models::task::Task,
|
||||
};
|
||||
|
||||
/// An executor that uses Gemini CLI to process tasks
|
||||
pub struct GeminiExecutor;
|
||||
|
||||
/// An executor that resumes a Gemini session
|
||||
pub struct GeminiFollowupExecutor {
|
||||
pub session_id: String,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for GeminiExecutor {
|
||||
async fn spawn(
|
||||
&self,
|
||||
pool: &sqlx::SqlitePool,
|
||||
task_id: Uuid,
|
||||
worktree_path: &str,
|
||||
) -> Result<Child, ExecutorError> {
|
||||
// Get the task to fetch its description
|
||||
let task = Task::find_by_id(pool, task_id)
|
||||
.await?
|
||||
.ok_or(ExecutorError::TaskNotFound)?;
|
||||
|
||||
let prompt = format!(
|
||||
"Task title: {}
|
||||
Task description: {}",
|
||||
task.title,
|
||||
task.description
|
||||
.as_deref()
|
||||
.unwrap_or("No description provided")
|
||||
);
|
||||
|
||||
// Use Gemini CLI to process the task
|
||||
let child = Command::new("npx")
|
||||
.kill_on_drop(true)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.current_dir(worktree_path)
|
||||
.arg("@bloopai/gemini-cli-interactive")
|
||||
.arg("-p")
|
||||
.arg(&prompt)
|
||||
.process_group(0) // Create new process group so we can kill entire tree
|
||||
.spawn()
|
||||
.map_err(ExecutorError::SpawnFailed)?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for GeminiFollowupExecutor {
|
||||
async fn spawn(
|
||||
&self,
|
||||
pool: &sqlx::SqlitePool,
|
||||
task_id: Uuid,
|
||||
worktree_path: &str,
|
||||
) -> Result<Child, ExecutorError> {
|
||||
// Use Gemini CLI with session resumption (if supported)
|
||||
let child = Command::new("npx")
|
||||
.kill_on_drop(true)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.current_dir(worktree_path)
|
||||
.arg("@bloopai/gemini-cli-interactive")
|
||||
.arg("-p")
|
||||
.arg(&self.prompt)
|
||||
.arg(format!("--resume={}", self.session_id))
|
||||
.process_group(0) // Create new process group so we can kill entire tree
|
||||
.spawn()
|
||||
.map_err(ExecutorError::SpawnFailed)?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@ pub mod amp;
|
||||
pub mod claude;
|
||||
pub mod dev_server;
|
||||
pub mod echo;
|
||||
pub mod gemini;
|
||||
pub mod setup_script;
|
||||
|
||||
pub use amp::{AmpExecutor, AmpFollowupExecutor};
|
||||
pub use claude::{ClaudeExecutor, ClaudeFollowupExecutor};
|
||||
pub use dev_server::DevServerExecutor;
|
||||
pub use echo::EchoExecutor;
|
||||
pub use gemini::{GeminiExecutor, GeminiFollowupExecutor};
|
||||
pub use setup_script::SetupScriptExecutor;
|
||||
|
||||
@@ -583,6 +583,7 @@ impl TaskAttempt {
|
||||
let executor_config = match most_recent_coding_agent.executor_type.as_deref() {
|
||||
Some("claude") => crate::executor::ExecutorConfig::Claude,
|
||||
Some("amp") => crate::executor::ExecutorConfig::Amp,
|
||||
Some("gemini") => crate::executor::ExecutorConfig::Gemini,
|
||||
Some("echo") => crate::executor::ExecutorConfig::Echo,
|
||||
_ => return Err(TaskAttemptError::TaskNotFound), // Invalid executor type
|
||||
};
|
||||
@@ -613,6 +614,7 @@ impl TaskAttempt {
|
||||
match executor_name.as_ref().map(|s| s.as_str()) {
|
||||
Some("claude") => crate::executor::ExecutorConfig::Claude,
|
||||
Some("amp") => crate::executor::ExecutorConfig::Amp,
|
||||
Some("gemini") => crate::executor::ExecutorConfig::Gemini,
|
||||
_ => crate::executor::ExecutorConfig::Echo, // Default for "echo" or None
|
||||
}
|
||||
}
|
||||
@@ -725,6 +727,7 @@ impl TaskAttempt {
|
||||
crate::executor::ExecutorConfig::Echo => "echo",
|
||||
crate::executor::ExecutorConfig::Claude => "claude",
|
||||
crate::executor::ExecutorConfig::Amp => "amp",
|
||||
crate::executor::ExecutorConfig::Gemini => "gemini",
|
||||
};
|
||||
(
|
||||
"executor".to_string(),
|
||||
@@ -737,6 +740,7 @@ impl TaskAttempt {
|
||||
crate::executor::ExecutorConfig::Echo => "echo",
|
||||
crate::executor::ExecutorConfig::Claude => "claude",
|
||||
crate::executor::ExecutorConfig::Amp => "amp",
|
||||
crate::executor::ExecutorConfig::Gemini => "gemini",
|
||||
};
|
||||
(
|
||||
"followup_executor".to_string(),
|
||||
@@ -856,7 +860,9 @@ impl TaskAttempt {
|
||||
session_id,
|
||||
prompt,
|
||||
} => {
|
||||
use crate::executors::{AmpFollowupExecutor, ClaudeFollowupExecutor};
|
||||
use crate::executors::{
|
||||
AmpFollowupExecutor, ClaudeFollowupExecutor, GeminiFollowupExecutor,
|
||||
};
|
||||
|
||||
let executor: Box<dyn crate::executor::Executor> = match config {
|
||||
crate::executor::ExecutorConfig::Claude => {
|
||||
@@ -879,6 +885,16 @@ impl TaskAttempt {
|
||||
return Err(TaskAttemptError::TaskNotFound); // No thread ID for followup
|
||||
}
|
||||
}
|
||||
crate::executor::ExecutorConfig::Gemini => {
|
||||
if let Some(sid) = session_id {
|
||||
Box::new(GeminiFollowupExecutor {
|
||||
session_id: sid.clone(),
|
||||
prompt: prompt.clone(),
|
||||
})
|
||||
} else {
|
||||
return Err(TaskAttemptError::TaskNotFound); // No session ID for followup
|
||||
}
|
||||
}
|
||||
crate::executor::ExecutorConfig::Echo => {
|
||||
// Echo doesn't support followup, use regular echo
|
||||
config.create_executor()
|
||||
|
||||
@@ -141,6 +141,7 @@ pub async fn create_task_and_start(
|
||||
crate::executor::ExecutorConfig::Echo => "echo".to_string(),
|
||||
crate::executor::ExecutorConfig::Claude => "claude".to_string(),
|
||||
crate::executor::ExecutorConfig::Amp => "amp".to_string(),
|
||||
crate::executor::ExecutorConfig::Gemini => "gemini".to_string(),
|
||||
});
|
||||
let attempt_payload = CreateTaskAttempt {
|
||||
executor: executor_string,
|
||||
|
||||
@@ -99,6 +99,7 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{executor.type === 'claude' && 'Claude Code from Anthropic'}
|
||||
{executor.type === 'amp' && 'From Sourcegraph'}
|
||||
{executor.type === 'gemini' && 'Google Gemini from Bloop'}
|
||||
{executor.type === 'echo' &&
|
||||
'This is just for debugging vibe-kanban itself'}
|
||||
</p>
|
||||
|
||||
@@ -36,12 +36,13 @@ export function ExecutionOutputViewer({
|
||||
|
||||
const isAmpExecutor = executor === 'amp';
|
||||
const isClaudeExecutor = executor === 'claude';
|
||||
const isGeminiExecutor = executor === 'gemini';
|
||||
const hasStdout = !!executionProcess.stdout;
|
||||
const hasStderr = !!executionProcess.stderr;
|
||||
|
||||
// Check if stdout looks like JSONL (for Amp or Claude executor)
|
||||
// Check if stdout looks like JSONL (for Amp, Claude, or Gemini executor)
|
||||
const { isValidJsonl, jsonlFormat } = useMemo(() => {
|
||||
if ((!isAmpExecutor && !isClaudeExecutor) || !executionProcess.stdout) {
|
||||
if ((!isAmpExecutor && !isClaudeExecutor && !isGeminiExecutor) || !executionProcess.stdout) {
|
||||
return { isValidJsonl: false, jsonlFormat: null };
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ export function ExecutionOutputViewer({
|
||||
} catch {
|
||||
return { isValidJsonl: false, jsonlFormat: null };
|
||||
}
|
||||
}, [isAmpExecutor, isClaudeExecutor, executionProcess.stdout]);
|
||||
}, [isAmpExecutor, isClaudeExecutor, isGeminiExecutor, executionProcess.stdout]);
|
||||
|
||||
// Set initial view mode based on JSONL detection
|
||||
useEffect(() => {
|
||||
|
||||
@@ -56,6 +56,7 @@ const availableExecutors = [
|
||||
{ id: 'echo', name: 'Echo' },
|
||||
{ id: 'claude', name: 'Claude' },
|
||||
{ id: 'amp', name: 'Amp' },
|
||||
{ id: 'gemini', name: 'Gemini' },
|
||||
];
|
||||
|
||||
export function TaskDetailsToolbar({
|
||||
|
||||
@@ -20,7 +20,7 @@ export type SoundConstants = { sound_files: Array<SoundFile>, sound_labels: Arra
|
||||
|
||||
export type ConfigConstants = { editor: EditorConstants, sound: SoundConstants, };
|
||||
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" } | { "type": "gemini" };
|
||||
|
||||
export type ExecutorConstants = { executor_types: Array<ExecutorConfig>, executor_labels: Array<string>, };
|
||||
|
||||
@@ -60,10 +60,10 @@ export type CreateFollowUpAttempt = { prompt: string, };
|
||||
|
||||
export type TaskAttemptActivity = { id: string, execution_process_id: string, status: TaskAttemptStatus, note: string | null, created_at: string, };
|
||||
|
||||
export type CreateTaskAttemptActivity = { execution_process_id: string, status: TaskAttemptStatus | null, note: string | null, };
|
||||
|
||||
export type TaskAttemptActivityWithPrompt = { id: string, execution_process_id: string, status: TaskAttemptStatus, note: string | null, created_at: string, prompt: string | null, };
|
||||
|
||||
export type CreateTaskAttemptActivity = { execution_process_id: string, status: TaskAttemptStatus | null, note: string | null, };
|
||||
|
||||
export type DirectoryEntry = { name: string, path: string, is_directory: boolean, is_git_repo: boolean, };
|
||||
|
||||
export type DiffChunkType = "Equal" | "Insert" | "Delete";
|
||||
@@ -98,7 +98,8 @@ export type UpdateExecutorSession = { session_id: string | null, prompt: string
|
||||
export const EXECUTOR_TYPES: string[] = [
|
||||
"echo",
|
||||
"claude",
|
||||
"amp"
|
||||
"amp",
|
||||
"gemini"
|
||||
];
|
||||
|
||||
export const EDITOR_TYPES: EditorType[] = [
|
||||
@@ -113,7 +114,8 @@ export const EDITOR_TYPES: EditorType[] = [
|
||||
export const EXECUTOR_LABELS: Record<string, string> = {
|
||||
"echo": "Echo (Test Mode)",
|
||||
"claude": "Claude",
|
||||
"amp": "Amp"
|
||||
"amp": "Amp",
|
||||
"gemini": "Gemini"
|
||||
};
|
||||
|
||||
export const EDITOR_LABELS: Record<string, string> = {
|
||||
|
||||
Reference in New Issue
Block a user