mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 19:05:18 +00:00
feat: add raw session args/opts for agent passthrough
This commit is contained in:
parent
375d73e4cb
commit
2f26f76d9b
14 changed files with 365 additions and 37 deletions
|
|
@ -237,6 +237,10 @@ impl AgentManager {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
if options.streaming_input {
|
||||
command
|
||||
.arg("--input-format")
|
||||
|
|
@ -268,6 +272,10 @@ impl AgentManager {
|
|||
if let Some(session_id) = options.session_id.as_deref() {
|
||||
command.arg("-s").arg(session_id);
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
command.arg(&options.prompt);
|
||||
}
|
||||
AgentId::Amp => {
|
||||
|
|
@ -583,6 +591,10 @@ impl AgentManager {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
if options.streaming_input {
|
||||
command
|
||||
.arg("--input-format")
|
||||
|
|
@ -614,6 +626,10 @@ impl AgentManager {
|
|||
if let Some(session_id) = options.session_id.as_deref() {
|
||||
command.arg("-s").arg(session_id);
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
command.arg(&options.prompt);
|
||||
}
|
||||
AgentId::Amp => {
|
||||
|
|
@ -682,6 +698,8 @@ pub struct SpawnOptions {
|
|||
pub env: HashMap<String, String>,
|
||||
/// Use stream-json input via stdin (Claude only).
|
||||
pub streaming_input: bool,
|
||||
/// Raw CLI arguments to pass to the agent (for CLI-based agents).
|
||||
pub raw_args: Vec<String>,
|
||||
}
|
||||
|
||||
impl SpawnOptions {
|
||||
|
|
@ -696,6 +714,7 @@ impl SpawnOptions {
|
|||
working_dir: None,
|
||||
env: HashMap::new(),
|
||||
streaming_input: false,
|
||||
raw_args: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1054,7 +1073,12 @@ fn spawn_amp(
|
|||
if let Some(session_id) = options.session_id.as_deref() {
|
||||
command.arg("--continue").arg(session_id);
|
||||
}
|
||||
command.args(&args).arg(&options.prompt);
|
||||
command.args(&args);
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
command.arg(&options.prompt);
|
||||
for (key, value) in &options.env {
|
||||
command.env(key, value);
|
||||
}
|
||||
|
|
@ -1095,6 +1119,10 @@ fn build_amp_command(path: &Path, working_dir: &Path, options: &SpawnOptions) ->
|
|||
if flags.dangerously_skip_permissions && options.permission_mode.as_deref() == Some("bypass") {
|
||||
command.arg("--dangerously-skip-permissions");
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
command.arg(&options.prompt);
|
||||
for (key, value) in &options.env {
|
||||
command.env(key, value);
|
||||
|
|
@ -1157,6 +1185,10 @@ fn spawn_amp_fallback(
|
|||
if !args.is_empty() {
|
||||
command.args(&args);
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
command.arg(&options.prompt);
|
||||
for (key, value) in &options.env {
|
||||
command.env(key, value);
|
||||
|
|
@ -1175,6 +1207,10 @@ fn spawn_amp_fallback(
|
|||
if let Some(session_id) = options.session_id.as_deref() {
|
||||
command.arg("--continue").arg(session_id);
|
||||
}
|
||||
// Apply raw CLI args
|
||||
for arg in &options.raw_args {
|
||||
command.arg(arg);
|
||||
}
|
||||
command.arg(&options.prompt);
|
||||
for (key, value) in &options.env {
|
||||
command.env(key, value);
|
||||
|
|
|
|||
|
|
@ -591,6 +591,8 @@ fn run_sessions(command: &SessionsCommand, cli: &Cli) -> Result<(), CliError> {
|
|||
model: args.model.clone(),
|
||||
variant: args.variant.clone(),
|
||||
agent_version: args.agent_version.clone(),
|
||||
raw_session_args: None,
|
||||
raw_session_options: None,
|
||||
};
|
||||
let path = format!("{API_PREFIX}/sessions/{}", args.session_id);
|
||||
let response = ctx.post(&path, &body)?;
|
||||
|
|
|
|||
|
|
@ -379,6 +379,8 @@ async fn ensure_backing_session(
|
|||
model: None,
|
||||
variant: None,
|
||||
agent_version: None,
|
||||
raw_session_args: None,
|
||||
raw_session_options: None,
|
||||
};
|
||||
match state
|
||||
.inner
|
||||
|
|
|
|||
|
|
@ -323,6 +323,8 @@ struct SessionState {
|
|||
model: Option<String>,
|
||||
variant: Option<String>,
|
||||
native_session_id: Option<String>,
|
||||
raw_session_args: Option<Vec<String>>,
|
||||
raw_session_options: Option<serde_json::Value>,
|
||||
ended: bool,
|
||||
ended_exit_code: Option<i32>,
|
||||
ended_message: Option<String>,
|
||||
|
|
@ -381,6 +383,8 @@ impl SessionState {
|
|||
model: request.model.clone(),
|
||||
variant: request.variant.clone(),
|
||||
native_session_id: None,
|
||||
raw_session_args: request.raw_session_args.clone(),
|
||||
raw_session_options: request.raw_session_options.clone(),
|
||||
ended: false,
|
||||
ended_exit_code: None,
|
||||
ended_message: None,
|
||||
|
|
@ -1614,6 +1618,8 @@ impl SessionManager {
|
|||
model: session.model.clone(),
|
||||
variant: session.variant.clone(),
|
||||
native_session_id: None,
|
||||
raw_session_args: session.raw_session_args.clone(),
|
||||
raw_session_options: session.raw_session_options.clone(),
|
||||
};
|
||||
let thread_id = self.create_codex_thread(&session_id, &snapshot).await?;
|
||||
session.native_session_id = Some(thread_id);
|
||||
|
|
@ -3079,6 +3085,15 @@ impl SessionManager {
|
|||
params.sandbox = codex_sandbox_mode(Some(&session.permission_mode));
|
||||
params.model = session.model.clone();
|
||||
|
||||
// Merge raw_session_options into the config field if provided
|
||||
if let Some(serde_json::Value::Object(raw_options)) = &session.raw_session_options {
|
||||
let mut config = params.config.take().unwrap_or_default();
|
||||
for (key, value) in raw_options {
|
||||
config.insert(key.clone(), value.clone());
|
||||
}
|
||||
params.config = Some(config);
|
||||
}
|
||||
|
||||
let request = codex_schema::ClientRequest::ThreadStart {
|
||||
id: codex_schema::RequestId::from(id),
|
||||
params,
|
||||
|
|
@ -3488,6 +3503,10 @@ pub struct AgentCapabilities {
|
|||
pub item_started: bool,
|
||||
/// Whether this agent uses a shared long-running server process (vs per-turn subprocess)
|
||||
pub shared_process: bool,
|
||||
/// Whether this agent supports raw CLI arguments passed at session creation
|
||||
pub raw_session_args: bool,
|
||||
/// Whether this agent supports raw options passed at session creation (long-running server agents)
|
||||
pub raw_session_options: bool,
|
||||
}
|
||||
|
||||
/// Status of a shared server process for an agent
|
||||
|
|
@ -3575,6 +3594,12 @@ pub struct CreateSessionRequest {
|
|||
pub variant: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub agent_version: Option<String>,
|
||||
/// Raw CLI arguments to pass to the agent (for CLI-based agents like Claude, OpenCode, Amp)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub raw_session_args: Option<Vec<String>>,
|
||||
/// Raw options to pass to the agent (for long-running server agents like Codex)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub raw_session_options: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
|
||||
|
|
@ -4120,6 +4145,8 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
|||
streaming_deltas: true,
|
||||
item_started: false,
|
||||
shared_process: false, // per-turn subprocess with --resume
|
||||
raw_session_args: true,
|
||||
raw_session_options: false,
|
||||
},
|
||||
AgentId::Codex => AgentCapabilities {
|
||||
plan_mode: true,
|
||||
|
|
@ -4140,6 +4167,8 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
|||
streaming_deltas: true,
|
||||
item_started: true,
|
||||
shared_process: true, // shared app-server via JSON-RPC
|
||||
raw_session_args: false,
|
||||
raw_session_options: true,
|
||||
},
|
||||
AgentId::Opencode => AgentCapabilities {
|
||||
plan_mode: false,
|
||||
|
|
@ -4160,6 +4189,8 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
|||
streaming_deltas: true,
|
||||
item_started: true,
|
||||
shared_process: true, // shared HTTP server
|
||||
raw_session_args: true,
|
||||
raw_session_options: false,
|
||||
},
|
||||
AgentId::Amp => AgentCapabilities {
|
||||
plan_mode: false,
|
||||
|
|
@ -4180,6 +4211,8 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
|||
streaming_deltas: false,
|
||||
item_started: false,
|
||||
shared_process: false, // per-turn subprocess with --continue
|
||||
raw_session_args: true,
|
||||
raw_session_options: false,
|
||||
},
|
||||
AgentId::Mock => AgentCapabilities {
|
||||
plan_mode: true,
|
||||
|
|
@ -4200,6 +4233,8 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
|||
streaming_deltas: true,
|
||||
item_started: true,
|
||||
shared_process: false, // in-memory mock (no subprocess)
|
||||
raw_session_args: false,
|
||||
raw_session_options: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -4434,6 +4469,7 @@ fn build_spawn_options(
|
|||
None
|
||||
}
|
||||
});
|
||||
options.raw_args = session.raw_session_args.clone().unwrap_or_default();
|
||||
if let Some(anthropic) = credentials.anthropic {
|
||||
options
|
||||
.env
|
||||
|
|
@ -6461,6 +6497,8 @@ struct SessionSnapshot {
|
|||
model: Option<String>,
|
||||
variant: Option<String>,
|
||||
native_session_id: Option<String>,
|
||||
raw_session_args: Option<Vec<String>>,
|
||||
raw_session_options: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl From<&SessionState> for SessionSnapshot {
|
||||
|
|
@ -6473,6 +6511,8 @@ impl From<&SessionState> for SessionSnapshot {
|
|||
model: session.model.clone(),
|
||||
variant: session.variant.clone(),
|
||||
native_session_id: session.native_session_id.clone(),
|
||||
raw_session_args: session.raw_session_args.clone(),
|
||||
raw_session_options: session.raw_session_options.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
mod agents;
|
||||
mod raw_session_args;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
use sandbox_agent_agent_management::agents::{AgentId, AgentManager, InstallOptions, SpawnOptions};
|
||||
|
||||
/// Tests that raw_args are passed to CLI-based agents.
|
||||
/// We use `--version` as a raw arg which causes agents to print version info and exit.
|
||||
#[test]
|
||||
fn test_raw_args_version_flag() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let manager = AgentManager::new(temp_dir.path().join("bin"))?;
|
||||
|
||||
// Test Claude with --version
|
||||
manager.install(AgentId::Claude, InstallOptions::default())?;
|
||||
let mut spawn = SpawnOptions::new("test");
|
||||
spawn.raw_args = vec!["--version".to_string()];
|
||||
let result = manager.spawn(AgentId::Claude, spawn)?;
|
||||
let output = format!("{}{}", result.stdout, result.stderr);
|
||||
assert!(
|
||||
output.to_lowercase().contains("version")
|
||||
|| output.contains("claude")
|
||||
|| result.status.code() == Some(0),
|
||||
"Claude --version failed: {output}"
|
||||
);
|
||||
|
||||
// Test OpenCode with --version
|
||||
manager.install(AgentId::Opencode, InstallOptions::default())?;
|
||||
let mut spawn = SpawnOptions::new("test");
|
||||
spawn.raw_args = vec!["--version".to_string()];
|
||||
let result = manager.spawn(AgentId::Opencode, spawn)?;
|
||||
let output = format!("{}{}", result.stdout, result.stderr);
|
||||
assert!(
|
||||
output.to_lowercase().contains("version")
|
||||
|| output.contains("opencode")
|
||||
|| result.status.code() == Some(0),
|
||||
"OpenCode --version failed: {output}"
|
||||
);
|
||||
|
||||
// Test Amp with --version
|
||||
manager.install(AgentId::Amp, InstallOptions::default())?;
|
||||
let mut spawn = SpawnOptions::new("test");
|
||||
spawn.raw_args = vec!["--version".to_string()];
|
||||
let result = manager.spawn(AgentId::Amp, spawn)?;
|
||||
let output = format!("{}{}", result.stdout, result.stderr);
|
||||
assert!(
|
||||
output.to_lowercase().contains("version")
|
||||
|| output.contains("amp")
|
||||
|| result.status.code() == Some(0),
|
||||
"Amp --version failed: {output}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue