From 0f325c87dce1f06a4292610b6e7293fc58bb7aa3 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 6 Feb 2026 23:21:52 -0800 Subject: [PATCH] fix: show agent mode list or fallback label in gigacode --- .../packages/agent-management/src/agents.rs | 41 +++++++++-- .../sandbox-agent/src/opencode_compat.rs | 71 ++++++++++++++++--- server/packages/sandbox-agent/src/router.rs | 56 +++++++++------ ..._endpoints_snapshots@agent_models_amp.snap | 9 +-- 4 files changed, 132 insertions(+), 45 deletions(-) diff --git a/server/packages/agent-management/src/agents.rs b/server/packages/agent-management/src/agents.rs index c5622f8..1251487 100644 --- a/server/packages/agent-management/src/agents.rs +++ b/server/packages/agent-management/src/agents.rs @@ -1020,12 +1020,28 @@ fn extract_result_text(agent: AgentId, events: &[Value]) -> Option { } } +fn amp_mode_from_agent_mode(agent_mode: Option<&str>) -> Option { + let mode = agent_mode + .map(|value| value.trim()) + .filter(|value| !value.is_empty())?; + let normalized = match mode { + "build" | "default" => "smart", + other => other, + }; + Some(normalized.to_string()) +} + fn spawn_amp( path: &Path, working_dir: &Path, options: &SpawnOptions, ) -> Result { let flags = detect_amp_flags(path, working_dir).unwrap_or_default(); + let mode = if flags.mode { + amp_mode_from_agent_mode(options.agent_mode.as_deref()) + } else { + None + }; let mut args: Vec<&str> = Vec::new(); if flags.execute { args.push("--execute"); @@ -1042,8 +1058,8 @@ fn spawn_amp( let mut command = Command::new(path); command.current_dir(working_dir); - if let Some(model) = options.model.as_deref() { - command.arg("--model").arg(model); + if let Some(mode) = mode.as_deref() { + command.arg("--mode").arg(mode); } if let Some(session_id) = options.session_id.as_deref() { command.arg("--continue").arg(session_id); @@ -1070,10 +1086,15 @@ fn spawn_amp( fn build_amp_command(path: &Path, working_dir: &Path, options: &SpawnOptions) -> Command { let flags = detect_amp_flags(path, working_dir).unwrap_or_default(); + let mode = if flags.mode { + amp_mode_from_agent_mode(options.agent_mode.as_deref()) + } else { + None + }; let mut command = Command::new(path); command.current_dir(working_dir); - if let Some(model) = options.model.as_deref() { - command.arg("--model").arg(model); + if let Some(mode) = mode.as_deref() { + command.arg("--mode").arg(mode); } if let Some(session_id) = options.session_id.as_deref() { command.arg("--continue").arg(session_id); @@ -1102,6 +1123,7 @@ struct AmpFlags { print: bool, output_format: bool, dangerously_skip_permissions: bool, + mode: bool, } fn detect_amp_flags(path: &Path, working_dir: &Path) -> Option { @@ -1120,6 +1142,7 @@ fn detect_amp_flags(path: &Path, working_dir: &Path) -> Option { print: text.contains("--print"), output_format: text.contains("--output-format"), dangerously_skip_permissions: text.contains("--dangerously-skip-permissions"), + mode: text.contains("--mode"), }) } @@ -1128,6 +1151,12 @@ fn spawn_amp_fallback( working_dir: &Path, options: &SpawnOptions, ) -> Result { + let flags = detect_amp_flags(path, working_dir).unwrap_or_default(); + let mode = if flags.mode { + amp_mode_from_agent_mode(options.agent_mode.as_deref()) + } else { + None + }; let mut attempts = vec![ vec!["--execute"], vec!["--print", "--output-format", "stream-json"], @@ -1142,8 +1171,8 @@ fn spawn_amp_fallback( for args in attempts { let mut command = Command::new(path); command.current_dir(working_dir); - if let Some(model) = options.model.as_deref() { - command.arg("--model").arg(model); + if let Some(mode) = mode.as_deref() { + command.arg("--mode").arg(mode); } if let Some(session_id) = options.session_id.as_deref() { command.arg("--continue").arg(session_id); diff --git a/server/packages/sandbox-agent/src/opencode_compat.rs b/server/packages/sandbox-agent/src/opencode_compat.rs index db292bb..e7fbcbb 100644 --- a/server/packages/sandbox-agent/src/opencode_compat.rs +++ b/server/packages/sandbox-agent/src/opencode_compat.rs @@ -27,7 +27,8 @@ use tracing::{info, warn}; use utoipa::{IntoParams, OpenApi, ToSchema}; use crate::router::{ - is_question_tool_action, AgentModelInfo, AppState, CreateSessionRequest, PermissionReply, + is_question_tool_action, AgentModeInfo, AgentModelInfo, AppState, CreateSessionRequest, + PermissionReply, }; use sandbox_agent_agent_management::agents::AgentId; use sandbox_agent_agent_management::credentials::{ @@ -2834,16 +2835,64 @@ pub fn build_opencode_router(state: Arc) -> Router { )] async fn oc_agent_list(State(state): State>) -> impl IntoResponse { let name = state.inner.branding.product_name(); - let agent = json!({ - "name": name, - "description": format!("{name} compatibility layer"), - "mode": "all", - "native": false, - "hidden": false, - "permission": [], - "options": {}, - }); - (StatusCode::OK, Json(json!([agent]))) + let mut modes = match state + .inner + .session_manager() + .agent_modes(AgentId::Opencode) + .await + { + Ok(modes) if !modes.is_empty() => modes, + _ => fallback_opencode_modes(), + }; + if modes.is_empty() { + let agent = json!({ + "name": name, + "description": format!("{name} compatibility layer"), + "mode": "all", + "native": false, + "hidden": false, + "permission": [], + "options": {}, + }); + return (StatusCode::OK, Json(json!([agent]))); + } + modes.sort_by(|a, b| a.id.cmp(&b.id)); + let agents: Vec = modes + .into_iter() + .map(|mode| { + json!({ + "id": mode.id, + "name": mode.id, + "description": mode.description, + "mode": "all", + "native": false, + "hidden": false, + "permission": [], + "options": {}, + }) + }) + .collect(); + (StatusCode::OK, Json(json!(agents))) +} + +fn fallback_opencode_modes() -> Vec { + vec![ + AgentModeInfo { + id: "build".to_string(), + name: "Build".to_string(), + description: "Default build mode".to_string(), + }, + AgentModeInfo { + id: "plan".to_string(), + name: "Plan".to_string(), + description: "Planning mode".to_string(), + }, + AgentModeInfo { + id: "custom".to_string(), + name: "Custom".to_string(), + description: "Any user-defined OpenCode agent name".to_string(), + }, + ] } #[utoipa::path( diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs index c89e874..6508848 100644 --- a/server/packages/sandbox-agent/src/router.rs +++ b/server/packages/sandbox-agent/src/router.rs @@ -1823,7 +1823,10 @@ impl SessionManager { Ok(()) } - async fn agent_modes(&self, agent: AgentId) -> Result, SandboxError> { + pub(crate) async fn agent_modes( + &self, + agent: AgentId, + ) -> Result, SandboxError> { if agent != AgentId::Opencode { return Ok(agent_modes_for(agent)); } @@ -4986,11 +4989,28 @@ fn agent_modes_for(agent: AgentId) -> Vec { description: "Plan mode (prompt-only)".to_string(), }, ], - AgentId::Amp => vec![AgentModeInfo { - id: "build".to_string(), - name: "Build".to_string(), - description: "Default build mode".to_string(), - }], + AgentId::Amp => vec![ + AgentModeInfo { + id: "smart".to_string(), + name: "Smart".to_string(), + description: "Default Amp mode".to_string(), + }, + AgentModeInfo { + id: "rush".to_string(), + name: "Rush".to_string(), + description: "Fast mode with smaller context".to_string(), + }, + AgentModeInfo { + id: "deep".to_string(), + name: "Deep".to_string(), + description: "Deep reasoning mode".to_string(), + }, + AgentModeInfo { + id: "free".to_string(), + name: "Free".to_string(), + description: "Free/experimental mode".to_string(), + }, + ], AgentId::Mock => vec![ AgentModeInfo { id: "build".to_string(), @@ -5007,23 +5027,14 @@ fn agent_modes_for(agent: AgentId) -> Vec { } fn amp_models_response() -> AgentModelsResponse { - // NOTE: Amp models are hardcoded based on ampcode.com manual: - // - smart - // - rush - // - deep - // - free - let models = ["smart", "rush", "deep", "free"] - .into_iter() - .map(|id| AgentModelInfo { - id: id.to_string(), - name: None, + AgentModelsResponse { + models: vec![AgentModelInfo { + id: "default".to_string(), + name: Some("Default".to_string()), variants: Some(amp_variants()), default_variant: Some("medium".to_string()), - }) - .collect(); - AgentModelsResponse { - models, - default_model: Some("smart".to_string()), + }], + default_model: Some("default".to_string()), } } @@ -5159,7 +5170,8 @@ fn normalize_agent_mode(agent: AgentId, agent_mode: Option<&str>) -> Result match mode { - "build" => Ok("build".to_string()), + "build" => Ok("smart".to_string()), + "smart" | "rush" | "deep" | "free" => Ok(mode.to_string()), value => Err(SandboxError::ModeNotSupported { agent: agent.as_str().to_string(), mode: value.to_string(), diff --git a/server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_amp.snap b/server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_amp.snap index 10c6ff5..2da2500 100644 --- a/server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_amp.snap +++ b/server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_amp.snap @@ -6,13 +6,10 @@ nonEmpty: true hasDefault: true defaultInList: true hasVariants: true -modelCount: 4 +modelCount: 1 ids: - - deep - - free - - rush - - smart -defaultModel: smart + - default +defaultModel: default variants: - high - medium