diff --git a/crates/executors/default_profiles.json b/crates/executors/default_profiles.json index 2c2026a567..8227d85ba8 100644 --- a/crates/executors/default_profiles.json +++ b/crates/executors/default_profiles.json @@ -294,6 +294,24 @@ "model": "glm-4.6" } } + }, + "MISTRAL_VIBE": { + "DEFAULT": { + "MISTRAL_VIBE": { + "auto_approve": true + } + }, + "DEVSTRAL_SMALL": { + "MISTRAL_VIBE": { + "model": "devstral-small-2", + "auto_approve": true + } + }, + "APPROVALS": { + "MISTRAL_VIBE": { + "auto_approve": false + } + } } } } diff --git a/crates/executors/src/command.rs b/crates/executors/src/command.rs index f4c66973b6..cdc6d34f44 100644 --- a/crates/executors/src/command.rs +++ b/crates/executors/src/command.rs @@ -20,8 +20,8 @@ pub enum CommandBuildError { #[derive(Debug, Clone)] pub struct CommandParts { - program: String, - args: Vec, + pub program: String, + pub args: Vec, } impl CommandParts { diff --git a/crates/executors/src/executors/mistral_vibe.rs b/crates/executors/src/executors/mistral_vibe.rs new file mode 100644 index 0000000000..be4188378d --- /dev/null +++ b/crates/executors/src/executors/mistral_vibe.rs @@ -0,0 +1,141 @@ +use std::{path::Path, sync::Arc}; + +use async_trait::async_trait; +use derivative::Derivative; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use workspace_utils::msg_store::MsgStore; + +pub use super::acp::AcpAgentHarness; +use crate::{ + approvals::ExecutorApprovalService, + command::{CmdOverrides, CommandBuilder, apply_overrides}, + env::ExecutionEnv, + executors::{ + AppendPrompt, AvailabilityInfo, ExecutorError, SpawnedChild, StandardCodingAgentExecutor, + }, +}; + +#[derive(Derivative, Clone, Serialize, Deserialize, TS, JsonSchema)] +#[derivative(Debug, PartialEq)] +pub struct MistralVibe { + #[serde(default)] + pub append_prompt: AppendPrompt, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub model: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_approve: Option, + #[serde(flatten)] + pub cmd: CmdOverrides, + #[serde(skip)] + #[ts(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub approvals: Option>, +} + +impl MistralVibe { + fn build_command_builder(&self) -> CommandBuilder { + let builder = CommandBuilder::new("vibe-acp"); + apply_overrides(builder, &self.cmd) + } +} + +#[async_trait] +impl StandardCodingAgentExecutor for MistralVibe { + fn use_approvals(&mut self, approvals: Arc) { + self.approvals = Some(approvals); + } + + async fn spawn( + &self, + current_dir: &Path, + prompt: &str, + env: &ExecutionEnv, + ) -> Result { + let mut harness = AcpAgentHarness::with_session_namespace("vibe_sessions"); + if let Some(ref model) = self.model { + harness = harness.with_model(model); + } + let combined_prompt = self.append_prompt.combine_prompt(prompt); + let vibe_command = self.build_command_builder().build_initial()?; + let approvals = if self.auto_approve.unwrap_or(false) { + None + } else { + self.approvals.clone() + }; + harness + .spawn_with_command( + current_dir, + combined_prompt, + vibe_command, + env, + &self.cmd, + approvals, + ) + .await + } + + async fn spawn_follow_up( + &self, + current_dir: &Path, + prompt: &str, + session_id: &str, + env: &ExecutionEnv, + ) -> Result { + let mut harness = AcpAgentHarness::with_session_namespace("vibe_sessions"); + if let Some(ref model) = self.model { + harness = harness.with_model(model); + } + let combined_prompt = self.append_prompt.combine_prompt(prompt); + let vibe_command = self.build_command_builder().build_follow_up(&[])?; + let approvals = if self.auto_approve.unwrap_or(false) { + None + } else { + self.approvals.clone() + }; + harness + .spawn_follow_up_with_command( + current_dir, + combined_prompt, + session_id, + vibe_command, + env, + &self.cmd, + approvals, + ) + .await + } + + fn normalize_logs(&self, msg_store: Arc, worktree_path: &Path) { + super::acp::normalize_logs(msg_store, worktree_path); + } + + fn default_mcp_config_path(&self) -> Option { + dirs::home_dir().map(|home| home.join(".vibe").join("config.toml")) + } + + fn get_availability_info(&self) -> AvailabilityInfo { + if let Some(timestamp) = dirs::home_dir() + .and_then(|home| std::fs::metadata(home.join(".vibe").join(".env")).ok()) + .and_then(|m| m.modified().ok()) + .and_then(|modified| modified.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_secs() as i64) + { + return AvailabilityInfo::LoginDetected { + last_auth_timestamp: timestamp, + }; + } + + let mcp_config_found = self + .default_mcp_config_path() + .map(|p| p.exists()) + .unwrap_or(false); + + if mcp_config_found { + AvailabilityInfo::InstallationFound + } else { + AvailabilityInfo::NotFound + } + } +} diff --git a/crates/executors/src/executors/mod.rs b/crates/executors/src/executors/mod.rs index 6aababc7fa..b0442ed562 100644 --- a/crates/executors/src/executors/mod.rs +++ b/crates/executors/src/executors/mod.rs @@ -19,7 +19,8 @@ use crate::{ env::ExecutionEnv, executors::{ amp::Amp, claude::ClaudeCode, codex::Codex, copilot::Copilot, cursor::CursorAgent, - droid::Droid, gemini::Gemini, opencode::Opencode, qwen::QwenCode, + droid::Droid, gemini::Gemini, mistral_vibe::MistralVibe, opencode::Opencode, + qwen::QwenCode, }, mcp_config::McpConfig, }; @@ -32,6 +33,7 @@ pub mod copilot; pub mod cursor; pub mod droid; pub mod gemini; +pub mod mistral_vibe; pub mod opencode; pub mod qwen; @@ -100,6 +102,7 @@ pub enum CodingAgent { QwenCode, Copilot, Droid, + MistralVibe, } impl CodingAgent { @@ -160,7 +163,8 @@ impl CodingAgent { | Self::Gemini(_) | Self::QwenCode(_) | Self::Droid(_) - | Self::Opencode(_) => vec![BaseAgentCapability::SessionFork], + | Self::Opencode(_) + | Self::MistralVibe(_) => vec![BaseAgentCapability::SessionFork], Self::Codex(_) => vec![ BaseAgentCapability::SessionFork, BaseAgentCapability::SetupHelper, diff --git a/crates/executors/src/mcp_config.rs b/crates/executors/src/mcp_config.rs index c3f8881573..fbcc7cfebc 100644 --- a/crates/executors/src/mcp_config.rs +++ b/crates/executors/src/mcp_config.rs @@ -293,7 +293,10 @@ impl CodingAgent { use Adapter::*; let adapter = match self { - CodingAgent::ClaudeCode(_) | CodingAgent::Amp(_) | CodingAgent::Droid(_) => Passthrough, + CodingAgent::ClaudeCode(_) + | CodingAgent::Amp(_) + | CodingAgent::Droid(_) + | CodingAgent::MistralVibe(_) => Passthrough, CodingAgent::QwenCode(_) | CodingAgent::Gemini(_) => Gemini, CodingAgent::CursorAgent(_) => Cursor, CodingAgent::Codex(_) => Codex, diff --git a/crates/server/src/bin/generate_types.rs b/crates/server/src/bin/generate_types.rs index c80934a6cd..770cb7b66e 100644 --- a/crates/server/src/bin/generate_types.rs +++ b/crates/server/src/bin/generate_types.rs @@ -193,6 +193,7 @@ fn generate_types_content() -> String { executors::executors::droid::Droid::decl(), executors::executors::droid::Autonomy::decl(), executors::executors::droid::ReasoningEffortLevel::decl(), + executors::executors::mistral_vibe::MistralVibe::decl(), executors::executors::AppendPrompt::decl(), executors::actions::coding_agent_initial::CodingAgentInitialRequest::decl(), executors::actions::coding_agent_follow_up::CodingAgentFollowUpRequest::decl(), @@ -294,6 +295,10 @@ fn generate_schemas() -> Result, serde_json::Error "droid", generate_json_schema::()?, ), + ( + "mistral_vibe", + generate_json_schema::()?, + ), ]); println!( "✅ JSON schemas generated. {} schemas created.", diff --git a/frontend/public/agents/mistral-vibe-dark.svg b/frontend/public/agents/mistral-vibe-dark.svg new file mode 100644 index 0000000000..77fbe174f0 --- /dev/null +++ b/frontend/public/agents/mistral-vibe-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/public/agents/mistral-vibe-light.svg b/frontend/public/agents/mistral-vibe-light.svg new file mode 100644 index 0000000000..e5bdc41ab6 --- /dev/null +++ b/frontend/public/agents/mistral-vibe-light.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/components/agents/AgentIcon.tsx b/frontend/src/components/agents/AgentIcon.tsx index 65a71aa6b6..ca69dc0f81 100644 --- a/frontend/src/components/agents/AgentIcon.tsx +++ b/frontend/src/components/agents/AgentIcon.tsx @@ -38,6 +38,8 @@ export function getAgentName( return 'Copilot'; case BaseCodingAgent.DROID: return 'Droid'; + case BaseCodingAgent.MISTRAL_VIBE: + return 'Mistral Vibe'; } } @@ -82,6 +84,9 @@ export function AgentIcon({ agent, className = 'h-4 w-4' }: AgentIconProps) { case BaseCodingAgent.DROID: iconPath = `/agents/droid${suffix}.svg`; break; + case BaseCodingAgent.MISTRAL_VIBE: + iconPath = `/agents/mistral-vibe${suffix}.svg`; + break; default: return null; } diff --git a/shared/schemas/mistral_vibe.json b/shared/schemas/mistral_vibe.json new file mode 100644 index 0000000000..220a709a71 --- /dev/null +++ b/shared/schemas/mistral_vibe.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "append_prompt": { + "title": "Append Prompt", + "description": "Extra text appended to the prompt", + "type": [ + "string", + "null" + ], + "format": "textarea", + "default": null + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "auto_approve": { + "type": [ + "boolean", + "null" + ] + }, + "base_command_override": { + "title": "Base Command Override", + "description": "Override the base command with a custom command", + "type": [ + "string", + "null" + ] + }, + "additional_params": { + "title": "Additional Parameters", + "description": "Additional parameters to append to the base command", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "env": { + "title": "Environment Variables", + "description": "Environment variables to set when running the executor", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + } + }, + "type": "object" +} \ No newline at end of file diff --git a/shared/types.ts b/shared/types.ts index e21fb7784d..8a8611c202 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -419,9 +419,9 @@ working_dir: string | null, }; export type ScriptRequestLanguage = "Bash"; -export enum BaseCodingAgent { CLAUDE_CODE = "CLAUDE_CODE", AMP = "AMP", GEMINI = "GEMINI", CODEX = "CODEX", OPENCODE = "OPENCODE", CURSOR_AGENT = "CURSOR_AGENT", QWEN_CODE = "QWEN_CODE", COPILOT = "COPILOT", DROID = "DROID" } +export enum BaseCodingAgent { CLAUDE_CODE = "CLAUDE_CODE", AMP = "AMP", GEMINI = "GEMINI", CODEX = "CODEX", OPENCODE = "OPENCODE", CURSOR_AGENT = "CURSOR_AGENT", QWEN_CODE = "QWEN_CODE", COPILOT = "COPILOT", DROID = "DROID", MISTRAL_VIBE = "MISTRAL_VIBE" } -export type CodingAgent = { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid }; +export type CodingAgent = { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid } | { "MISTRAL_VIBE": MistralVibe }; export type AvailabilityInfo = { "type": "LOGIN_DETECTED", last_auth_timestamp: bigint, } | { "type": "INSTALLATION_FOUND" } | { "type": "NOT_FOUND" }; @@ -445,7 +445,7 @@ executor: BaseCodingAgent, */ variant: string | null, }; -export type ExecutorConfig = { [key in string]?: { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid } }; +export type ExecutorConfig = { [key in string]?: { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid } | { "MISTRAL_VIBE": MistralVibe } }; export type ExecutorConfigs = { executors: { [key in BaseCodingAgent]?: ExecutorConfig }, }; @@ -487,6 +487,8 @@ export type Autonomy = "normal" | "low" | "medium" | "high" | "skip-permissions- export type DroidReasoningEffort = "none" | "dynamic" | "off" | "low" | "medium" | "high"; +export type MistralVibe = { append_prompt: AppendPrompt, model?: string | null, auto_approve?: boolean | null, base_command_override?: string | null, additional_params?: Array | null, env?: { [key in string]?: string } | null, }; + export type AppendPrompt = string | null; export type CodingAgentInitialRequest = { prompt: string,