mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 06:04:43 +00:00
Merge pull request #148 from bobbythelobster/add-cursor-agent-support
Add cursor-agent support
This commit is contained in:
commit
4322cb1d8e
2 changed files with 45 additions and 4 deletions
|
|
@ -5,7 +5,7 @@
|
||||||
<h3 align="center">Run Coding Agents in Sandboxes. Control Them Over HTTP.</h3>
|
<h3 align="center">Run Coding Agents in Sandboxes. Control Them Over HTTP.</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
A server that runs inside your sandbox. Your app connects remotely to control Claude Code, Codex, OpenCode, or Amp — streaming events, handling permissions, managing sessions.
|
A server that runs inside your sandbox. Your app connects remotely to control Claude Code, Codex, OpenCode, Cursor, or Amp — streaming events, handling permissions, managing sessions.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
@ -24,13 +24,13 @@ Sandbox Agent solves three problems:
|
||||||
|
|
||||||
1. **Coding agents need sandboxes** — You can't let AI execute arbitrary code on your production servers. Coding agents need isolated environments, but existing SDKs assume local execution. Sandbox Agent is a server that runs inside the sandbox and exposes HTTP/SSE.
|
1. **Coding agents need sandboxes** — You can't let AI execute arbitrary code on your production servers. Coding agents need isolated environments, but existing SDKs assume local execution. Sandbox Agent is a server that runs inside the sandbox and exposes HTTP/SSE.
|
||||||
|
|
||||||
2. **Every coding agent is different** — Claude Code, Codex, OpenCode, and Amp each have proprietary APIs, event formats, and behaviors. Swapping agents means rewriting your integration. Sandbox Agent provides one HTTP API — write your code once, swap agents with a config change.
|
2. **Every coding agent is different** — Claude Code, Codex, OpenCode, Cursor, and Amp each have proprietary APIs, event formats, and behaviors. Swapping agents means rewriting your integration. Sandbox Agent provides one HTTP API — write your code once, swap agents with a config change.
|
||||||
|
|
||||||
3. **Sessions are ephemeral** — Agent transcripts live in the sandbox. When the process ends, you lose everything. Sandbox Agent streams events in a universal schema to your storage. Persist to Postgres, ClickHouse, or [Rivet](https://rivet.dev). Replay later, audit everything.
|
3. **Sessions are ephemeral** — Agent transcripts live in the sandbox. When the process ends, you lose everything. Sandbox Agent streams events in a universal schema to your storage. Persist to Postgres, ClickHouse, or [Rivet](https://rivet.dev). Replay later, audit everything.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Universal Agent API**: Single interface to control Claude Code, Codex, OpenCode, and Amp with full feature coverage
|
- **Universal Agent API**: Single interface to control Claude Code, Codex, OpenCode, Cursor, and Amp with full feature coverage
|
||||||
- **Streaming Events**: Real-time SSE stream of everything the agent does — tool calls, permission requests, file edits, and more
|
- **Streaming Events**: Real-time SSE stream of everything the agent does — tool calls, permission requests, file edits, and more
|
||||||
- **Universal Session Schema**: [Standardized schema](https://sandboxagent.dev/docs/session-transcript-schema) that normalizes all agent event formats for storage and replay
|
- **Universal Session Schema**: [Standardized schema](https://sandboxagent.dev/docs/session-transcript-schema) that normalizes all agent event formats for storage and replay
|
||||||
- **Human-in-the-Loop**: Approve or deny tool executions and answer agent questions remotely over HTTP
|
- **Human-in-the-Loop**: Approve or deny tool executions and answer agent questions remotely over HTTP
|
||||||
|
|
@ -234,7 +234,7 @@ No, they're complementary. AI SDK is for building chat interfaces and calling LL
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>Which coding agents are supported?</strong></summary>
|
<summary><strong>Which coding agents are supported?</strong></summary>
|
||||||
|
|
||||||
Claude Code, Codex, OpenCode, and Amp. The SDK normalizes their APIs so you can swap between them without changing your code.
|
Claude Code, Codex, OpenCode, Cursor, and Amp. The SDK normalizes their APIs so you can swap between them without changing your code.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ pub enum AgentId {
|
||||||
Codex,
|
Codex,
|
||||||
Opencode,
|
Opencode,
|
||||||
Amp,
|
Amp,
|
||||||
|
Cursor,
|
||||||
Mock,
|
Mock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,6 +32,7 @@ impl AgentId {
|
||||||
AgentId::Codex => "codex",
|
AgentId::Codex => "codex",
|
||||||
AgentId::Opencode => "opencode",
|
AgentId::Opencode => "opencode",
|
||||||
AgentId::Amp => "amp",
|
AgentId::Amp => "amp",
|
||||||
|
AgentId::Cursor => "cursor",
|
||||||
AgentId::Mock => "mock",
|
AgentId::Mock => "mock",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +43,7 @@ impl AgentId {
|
||||||
AgentId::Codex => "codex",
|
AgentId::Codex => "codex",
|
||||||
AgentId::Opencode => "opencode",
|
AgentId::Opencode => "opencode",
|
||||||
AgentId::Amp => "amp",
|
AgentId::Amp => "amp",
|
||||||
|
AgentId::Cursor => "cursor-agent",
|
||||||
AgentId::Mock => "mock",
|
AgentId::Mock => "mock",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +54,7 @@ impl AgentId {
|
||||||
"codex" => Some(AgentId::Codex),
|
"codex" => Some(AgentId::Codex),
|
||||||
"opencode" => Some(AgentId::Opencode),
|
"opencode" => Some(AgentId::Opencode),
|
||||||
"amp" => Some(AgentId::Amp),
|
"amp" => Some(AgentId::Amp),
|
||||||
|
"cursor" => Some(AgentId::Cursor),
|
||||||
"mock" => Some(AgentId::Mock),
|
"mock" => Some(AgentId::Mock),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +155,7 @@ impl AgentManager {
|
||||||
install_opencode(&install_path, self.platform, options.version.as_deref())?
|
install_opencode(&install_path, self.platform, options.version.as_deref())?
|
||||||
}
|
}
|
||||||
AgentId::Amp => install_amp(&install_path, self.platform, options.version.as_deref())?,
|
AgentId::Amp => install_amp(&install_path, self.platform, options.version.as_deref())?,
|
||||||
|
AgentId::Cursor => install_cursor(&install_path, self.platform, options.version.as_deref())?,
|
||||||
AgentId::Mock => {
|
AgentId::Mock => {
|
||||||
if !install_path.exists() {
|
if !install_path.exists() {
|
||||||
fs::write(&install_path, b"mock")?;
|
fs::write(&install_path, b"mock")?;
|
||||||
|
|
@ -268,6 +273,18 @@ impl AgentManager {
|
||||||
}
|
}
|
||||||
command.arg(&options.prompt);
|
command.arg(&options.prompt);
|
||||||
}
|
}
|
||||||
|
AgentId::Cursor => {
|
||||||
|
// cursor-agent typically runs as HTTP server on localhost:32123
|
||||||
|
// For CLI usage similar to opencode
|
||||||
|
command.arg("run").arg("--format").arg("json");
|
||||||
|
if let Some(model) = options.model.as_deref() {
|
||||||
|
command.arg("-m").arg(model);
|
||||||
|
}
|
||||||
|
if let Some(session_id) = options.session_id.as_deref() {
|
||||||
|
command.arg("-s").arg(session_id);
|
||||||
|
}
|
||||||
|
command.arg(&options.prompt);
|
||||||
|
}
|
||||||
AgentId::Amp => {
|
AgentId::Amp => {
|
||||||
let output = spawn_amp(&path, &working_dir, &options)?;
|
let output = spawn_amp(&path, &working_dir, &options)?;
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
|
@ -1199,6 +1216,30 @@ fn find_in_path(binary_name: &str) -> Option<PathBuf> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn install_cursor(path: &Path, platform: Platform, _version: Option<&str>) -> Result<(), AgentError> {
|
||||||
|
// Note: cursor-agent binary URL needs to be verified
|
||||||
|
// Cursor Pro includes cursor-agent, typically installed via: curl -fsS https://cursor.com/install | bash
|
||||||
|
// For sandbox-agent, we need standalone cursor-agent binary
|
||||||
|
// TODO: Determine correct download URL for cursor-agent releases
|
||||||
|
|
||||||
|
let platform_segment = match platform {
|
||||||
|
Platform::LinuxX64 | Platform::LinuxX64Musl => "linux-x64",
|
||||||
|
Platform::LinuxArm64 => "linux-arm64",
|
||||||
|
Platform::MacosArm64 => "darwin-arm64",
|
||||||
|
Platform::MacosX64 => "darwin-x64",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Placeholder URL - needs to be updated with actual cursor-agent release URL
|
||||||
|
let url = Url::parse(&format!(
|
||||||
|
"https://cursor.com/api/v1/releases/latest/download/cursor-agent-{platform_segment}",
|
||||||
|
platform_segment = platform_segment
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let bytes = download_bytes(&url)?;
|
||||||
|
write_executable(path, &bytes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn download_bytes(url: &Url) -> Result<Vec<u8>, AgentError> {
|
fn download_bytes(url: &Url) -> Result<Vec<u8>, AgentError> {
|
||||||
let client = Client::builder().build()?;
|
let client = Client::builder().build()?;
|
||||||
let mut response = client.get(url.clone()).send()?;
|
let mut response = client.get(url.clone()).send()?;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue