Skip to content
Merged
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
44 changes: 40 additions & 4 deletions src/crates/acp/src/client/builtin_clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ pub(crate) struct BuiltinAcpClientPreset {
pub(crate) command: &'static str,
pub(crate) args: &'static [&'static str],
pub(crate) tool_command: &'static str,
pub(crate) install_package: &'static str,
/// npm package BitFun can install on the user's behalf. `None` means the
/// agent is user-managed (BitFun only provides the integration, the user
/// installs the CLI themselves) — the UI then shows no one-click installer.
pub(crate) install_package: Option<&'static str>,
pub(crate) adapter_package: Option<&'static str>,
pub(crate) adapter_bin: Option<&'static str>,
}
Expand All @@ -18,7 +21,22 @@ const BUILTIN_ACP_CLIENT_PRESETS: &[BuiltinAcpClientPreset] = &[
command: "opencode",
args: &["acp"],
tool_command: "opencode",
install_package: "opencode-ai",
install_package: Some("opencode-ai"),
adapter_package: None,
adapter_bin: None,
},
// Oh My Pi (omp) — a terminal coding agent that speaks ACP natively via
// `omp acp` (no adapter needed, like opencode). User-managed: omp targets
// the bun runtime (installed via `bun install -g @oh-my-pi/pi-coding-agent`
// or `curl -fsSL https://omp.sh/install | sh`), which BitFun's npm-based
// installer cannot provide — so install_package is None and BitFun only
// detects `omp` on PATH and launches it. https://github.com/can1357/oh-my-pi
BuiltinAcpClientPreset {
id: "omp",
command: "omp",
args: &["acp"],
tool_command: "omp",
install_package: None,
adapter_package: None,
adapter_bin: None,
},
Expand All @@ -27,7 +45,7 @@ const BUILTIN_ACP_CLIENT_PRESETS: &[BuiltinAcpClientPreset] = &[
command: "npx",
args: &["--yes", "@zed-industries/claude-code-acp@latest"],
tool_command: "claude",
install_package: "@anthropic-ai/claude-code",
install_package: Some("@anthropic-ai/claude-code"),
adapter_package: Some("@zed-industries/claude-code-acp"),
adapter_bin: Some("claude-code-acp"),
},
Expand All @@ -36,7 +54,7 @@ const BUILTIN_ACP_CLIENT_PRESETS: &[BuiltinAcpClientPreset] = &[
command: "npx",
args: &["--yes", "@zed-industries/codex-acp@latest"],
tool_command: "codex",
install_package: "@openai/codex",
install_package: Some("@openai/codex"),
adapter_package: Some("@zed-industries/codex-acp"),
adapter_bin: Some("codex-acp"),
},
Expand Down Expand Up @@ -85,4 +103,22 @@ mod tests {
vec!["--yes", "@zed-industries/claude-code-acp@latest"]
);
}

#[test]
fn omp_is_a_native_acp_preset() {
let preset = builtin_acp_client_preset("omp").expect("omp preset registered");
assert_eq!(preset.command, "omp");
assert_eq!(preset.args, &["acp"]);
assert_eq!(preset.tool_command, "omp");
// Native ACP — no adapter package/bin, like opencode.
assert!(preset.adapter_package.is_none());
assert!(preset.adapter_bin.is_none());
// User-managed: BitFun provides no installer for omp.
assert!(preset.install_package.is_none());

let config = default_config_for_builtin_client("omp").expect("omp config");
assert!(config.enabled);
assert_eq!(config.command, "omp");
assert_eq!(config.args, vec!["acp"]);
}
}
2 changes: 1 addition & 1 deletion src/crates/acp/src/client/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) fn acp_requirement_spec<'a>(
if let Some(preset) = builtin_acp_client_preset(client_id) {
return AcpRequirementSpec {
tool_command: preset.tool_command,
install_package: Some(preset.install_package),
install_package: preset.install_package,
adapter: match (preset.adapter_package, preset.adapter_bin) {
(Some(package), Some(bin)) => Some(AcpAdapterSpec { package, bin }),
_ => None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ interface AcpClientPreset {
args: string[];
}

// Presets that speak ACP natively and therefore need no separate adapter
// package (their CLI binary is launched directly).
const NATIVE_ACP_PRESET_IDS = new Set(['opencode', 'omp']);

// Presets BitFun cannot install on the user's behalf — the agent must be
// installed manually (e.g. omp targets bun and ships via its own installer).
// The UI hides the one-click "Install CLI" action for these.
const SELF_MANAGED_INSTALL_PRESET_IDS = new Set(['omp']);

const PRESETS: AcpClientPreset[] = [
{
id: 'opencode',
Expand All @@ -68,6 +77,13 @@ const PRESETS: AcpClientPreset[] = [
command: 'opencode',
args: ['acp'],
},
{
id: 'omp',
name: 'Oh My Pi',
description: 'Native ACP coding agent (omp acp).',
command: 'omp',
args: ['acp'],
},
{
id: 'claude-code',
name: 'Claude Code',
Expand Down Expand Up @@ -388,7 +404,7 @@ const AcpAgentsConfig: React.FC = () => {
enabled,
toolInstalled: probe?.tool.installed,
adapterInstalled: probe?.adapter?.installed,
requiresAdapter: Boolean(probe?.adapter || preset.id !== 'opencode'),
requiresAdapter: Boolean(probe?.adapter || !NATIVE_ACP_PRESET_IDS.has(preset.id)),
probePending,
probe,
});
Expand Down Expand Up @@ -1031,7 +1047,9 @@ const AcpAgentsConfig: React.FC = () => {
const installing = installingClientIds.has(preset.id);
const configuring = installingClientIds.has(preset.id);
const showSelect = hasConfigEntry && (status === 'enabled' || status === 'ready');
const canInstallCli = status === 'not_installed' && issueKind !== 'connection_failed';
const canInstallCli = status === 'not_installed'
&& issueKind !== 'connection_failed'
&& !SELF_MANAGED_INSTALL_PRESET_IDS.has(preset.id);
const canConfigureAcp = !requiresAdapter
? false
: issueKind === 'adapter_missing' || (status === 'partial' && issueKind === 'config_invalid');
Expand Down Expand Up @@ -1351,7 +1369,8 @@ const AcpAgentsConfig: React.FC = () => {
probe: row.requirementProbe,
requiresAdapter: row.requiresAdapter,
});
const canInstallCli = row.preset && row.status === 'not_installed' && row.issueKind === 'cli_missing';
const canInstallCli = row.preset && row.status === 'not_installed' && row.issueKind === 'cli_missing'
&& !SELF_MANAGED_INSTALL_PRESET_IDS.has(row.preset.id);
const canViewError = row.status === 'invalid' || row.status === 'partial'
|| row.issueKind === 'connection_failed'
|| row.issueKind === 'permission_denied'
Expand Down
Loading