Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions sandbox-agent/docs/content/docs/agents/omp.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: "oh-my-pi"
description: "Use oh-my-pi (omp) as a sandbox agent."
---

## Usage

```typescript
const session = await client.createSession({
agent: "omp",
});
```

`sandbox-agent install-agent omp` downloads the `omp` release binary from [GitHub](https://github.com/can1357/oh-my-pi/releases) (pin one with `--agent-version`) and runs it with `omp acp`, its native ACP mode. No adapter package is needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep protocol internals out of the omp usage page

This agent-usage page describes the underlying ACP launch mode and adapter implementation, contrary to the Docs Terminology rules in sandbox-agent/AGENTS.md, which prohibit ACP terminology and protocol implementation details outside ACP-specific documentation. Keep this paragraph focused on installation and SDK usage, and move the protocol details to an ACP-specific reference.

AGENTS.md reference: sandbox-agent/AGENTS.md:L13-L15

Useful? React with 👍 / 👎.


## Capabilities

| Category | Values |
|----------|--------|
| **Models** | Every model omp can use with its configured providers, reported by the agent |
| **Modes** | `default`, `plan` |
| **Thought levels** | Reported by the agent for the selected model |

## Credentials

omp reads its usual provider environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and others) and `~/.omp/agent/models.yml`.

## Permissions

omp asks the client before shell commands, file deletion and moves. For unattended sandboxes, set `tools.approvalMode: yolo` in `~/.omp/agent/config.yml`.
4 changes: 4 additions & 0 deletions sandbox-agent/docs/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@
{
"title": "Pi",
"href": "/sandbox-agent/docs/agents/pi"
},
{
"title": "oh-my-pi",
"href": "/sandbox-agent/docs/agents/omp"
}
]
},
Expand Down
52 changes: 50 additions & 2 deletions sandbox-agent/server/packages/agent-management/src/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub enum AgentId {
Amp,
Pi,
Cursor,
Omp,
Mock,
}

Expand All @@ -38,6 +39,7 @@ impl AgentId {
AgentId::Amp => "amp",
AgentId::Pi => "pi",
AgentId::Cursor => "cursor",
AgentId::Omp => "omp",
AgentId::Mock => "mock",
}
}
Expand All @@ -50,6 +52,7 @@ impl AgentId {
AgentId::Amp => "amp",
AgentId::Pi => "pi",
AgentId::Cursor => "cursor-agent",
AgentId::Omp => "omp",
AgentId::Mock => "mock",
}
}
Expand All @@ -62,6 +65,7 @@ impl AgentId {
"amp" => Some(AgentId::Amp),
"pi" => Some(AgentId::Pi),
"cursor" => Some(AgentId::Cursor),
"omp" => Some(AgentId::Omp),
"mock" => Some(AgentId::Mock),
_ => None,
}
Expand All @@ -75,6 +79,7 @@ impl AgentId {
AgentId::Amp,
AgentId::Pi,
AgentId::Cursor,
AgentId::Omp,
AgentId::Mock,
]
}
Expand All @@ -87,6 +92,7 @@ impl AgentId {
AgentId::Amp => Some("amp-acp"),
AgentId::Pi => Some("pi-acp"),
AgentId::Cursor => Some("cursor-agent-acp"),
AgentId::Omp => None,
AgentId::Mock => None,
}
}
Expand All @@ -99,12 +105,16 @@ impl AgentId {
AgentId::Amp => Some("amp-acp"),
AgentId::Pi => Some("pi-acp"),
AgentId::Cursor => Some("cursor-agent-acp"),
AgentId::Omp => None,
AgentId::Mock => None,
}
}

fn native_required(self) -> bool {
matches!(self, AgentId::Claude | AgentId::Codex | AgentId::Opencode)
matches!(
self,
AgentId::Claude | AgentId::Codex | AgentId::Opencode | AgentId::Omp
)
}

fn unstable_enabled(self) -> bool {
Expand Down Expand Up @@ -533,6 +543,7 @@ impl AgentManager {
install_opencode(&path, self.platform, options.version.as_deref())?
}
AgentId::Amp => install_amp(&path, self.platform, options.version.as_deref())?,
AgentId::Omp => install_omp(&path, self.platform, options.version.as_deref())?,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark omp as requiring a native binary

On a clean host without an omp binary, this installer is unreachable: AgentManager::install calls install_native only when native_required() is true, but that predicate still lists only Claude, Codex and OpenCode. Installation therefore reaches install_agent_process_fallback, which calls resolve_binary and returns BinaryNotFound instead of downloading omp; lazy installation fails the same way. Add Omp to native_required() and cover installation from an empty install directory.

Useful? React with 👍 / 👎.

AgentId::Pi | AgentId::Cursor => {
return Ok(None);
}
Expand Down Expand Up @@ -823,7 +834,7 @@ impl AgentManager {
) -> Result<InstalledArtifact, AgentError> {
let started = Instant::now();
let artifact = match agent {
AgentId::Opencode => {
AgentId::Opencode | AgentId::Omp => {
let launcher = self.agent_process_path(agent);
let native = self.resolve_binary(agent)?;
write_exec_agent_process_launcher(
Expand Down Expand Up @@ -1338,6 +1349,43 @@ fn install_claude(
Ok(())
}

fn install_omp(path: &Path, platform: Platform, version: Option<&str>) -> Result<(), AgentError> {
let started = Instant::now();
let asset = match platform {
Platform::LinuxX64 => "omp-linux-x64",
Platform::LinuxX64Musl => "omp-linux-musl-x64",
Platform::LinuxArm64 => "omp-linux-arm64",
Platform::MacosArm64 => "omp-darwin-arm64",
Platform::MacosX64 => "omp-darwin-x64",
Platform::WindowsX64 => "omp-windows-x64.exe",
Platform::WindowsArm64 => "omp-windows-arm64.exe",
};
// Release tags are `v<semver>`; accept the version with or without the prefix.
let url = match version {
Some(version) => Url::parse(&format!(
"https://github.com/can1357/oh-my-pi/releases/download/v{}/{asset}",
version.trim_start_matches('v')
))?,
None => Url::parse(&format!(
"https://github.com/can1357/oh-my-pi/releases/latest/download/{asset}"
))?,
};
let download_started = Instant::now();
let bytes = download_bytes(&url)?;
let download_ms = elapsed_ms(download_started);
let write_started = Instant::now();
write_executable(path, &bytes)?;
tracing::info!(
url = %url,
bytes = bytes.len(),
download_ms = download_ms,
write_ms = elapsed_ms(write_started),
total_ms = elapsed_ms(started),
"agent_manager.install_omp: completed"
);
Ok(())
}

fn install_amp(path: &Path, platform: Platform, version: Option<&str>) -> Result<(), AgentError> {
let started = Instant::now();
let version = match version {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ pub fn test_agents_from_env() -> Result<Vec<TestAgentConfig>, TestAgentConfigErr
}
credentials_with(anthropic_cred.clone(), openai_cred.clone())
}
AgentId::Pi | AgentId::Cursor => credentials_with(None, None),
AgentId::Pi | AgentId::Cursor | AgentId::Omp => credentials_with(None, None),
AgentId::Mock => credentials_with(None, None),
};
configs.push(TestAgentConfig { agent, credentials });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub(super) fn credentials_available_for(
AgentId::Claude | AgentId::Amp => has_anthropic,
AgentId::Codex => has_openai,
AgentId::Opencode => has_anthropic || has_openai,
AgentId::Pi | AgentId::Cursor => true,
AgentId::Pi | AgentId::Cursor | AgentId::Omp => true,
AgentId::Mock => true,
}
}
Expand Down Expand Up @@ -181,6 +181,8 @@ pub(super) fn fallback_config_options(agent: AgentId) -> Vec<Value> {
{ "value": "default", "name": "Default" }
]
})],
// omp returns its model, mode and thinking options from session/new.
AgentId::Omp => Vec::new(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Supply omp metadata to the existing configuration consumers

Even with a preinstalled, authenticated omp, get_v1_agents and get_v1_agent populate configuration only from this function (router.rs:1603-1605,1718-1720); live probing remains disabled, so the Inspector cannot discover omp's models or modes. build_provider_payload_for_opencode also skips agents without a model option (support.rs:674-680), so omp is omitted from /opencode/provider despite its new list entry. Options returned by session/new do not populate either consumer. Provide fallback metadata or wire live configuration into these paths before returning an empty vector.

Useful? React with 👍 / 👎.

AgentId::Mock => vec![
json!({
"id": "model",
Expand Down Expand Up @@ -403,6 +405,26 @@ pub(super) fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
item_started: true,
shared_process: false,
},
AgentId::Omp => AgentCapabilities {
plan_mode: true,
permissions: true,
questions: false,
tool_calls: true,
tool_results: true,
text_messages: true,
images: true,
file_attachments: false,
session_lifecycle: true,
error_events: true,
reasoning: true,
status: false,
command_execution: false,
file_changes: false,
mcp_tools: true,
streaming_deltas: true,
item_started: true,
shared_process: false,
},
AgentId::Cursor => AgentCapabilities {
plan_mode: true,
permissions: true,
Expand Down Expand Up @@ -636,6 +658,7 @@ pub(super) fn build_provider_payload_for_opencode(_state: &Arc<AppState>) -> Val
AgentId::Opencode,
AgentId::Pi,
AgentId::Cursor,
AgentId::Omp,
];

let has_anthropic = std::env::var("ANTHROPIC_API_KEY").is_ok();
Expand Down Expand Up @@ -721,6 +744,7 @@ fn agent_display_name(agent: AgentId) -> &'static str {
AgentId::Opencode => "OpenCode",
AgentId::Pi => "Pi",
AgentId::Cursor => "Cursor Agent",
AgentId::Omp => "oh-my-pi",
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn parse_json(bytes: &[u8]) -> Value {
#[cfg(unix)]
#[tokio::test]
async fn agent_process_matrix_smoke_and_jsonrpc_conformance() {
let native_agents = ["claude", "codex", "opencode"];
let native_agents = ["claude", "codex", "opencode", "omp"];
let agent_process_only_agents = ["pi", "cursor"];
let agents: Vec<&str> = native_agents
.iter()
Expand Down