Skip to content

Make the agent CLI a port (Claude Code as one adapter) #19

Description

@jellologic

Problem

The refactor made providers pluggable but left the agent CLI hardcoded.
Claude Code is assumed everywhere:

  • resolve looks for a binary literally named claude
  • the core emits ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN,
    ANTHROPIC_DEFAULT_*_MODEL — Claude Code's vocabulary, not a neutral one
  • --dangerously-skip-permissions is a Claude Code flag, modelled in the core
    as skipPermissions
  • the compat flags (CLAUDE_CODE_*) are Claude-Code-specific by definition

These are two orthogonal axes that are currently fused:

Axis Question Today
Provider which model backend? pluggable ✅
Agent CLI which coding agent? hardcoded ❌

Running opencode or another CLI against the
same profiles should be a matter of writing one adapter.

Proposed port

AgentCliPort
  id / label
  resolveBinary(env)          -> path        (each CLI has its own name + install paths)
  translate(intent)           -> { env, args }
  capabilities                -> { modelTiers, skipPermissions, extendedContext, … }

The domain core should speak a neutral intent:

{ endpoint, credential, models: { opus, sonnet, haiku, fable }, permissions, extendedContext }

…and each adapter lowers that into its own CLI's env vars and flags. Today the
core speaks Claude Code's dialect directly, so there is no seam to swap.

The hard part — capability mismatch

This is what makes it a real design problem rather than a rename:

The likely answer is that AgentCliPort declares capabilities, and the core
refuses or warns on profiles that ask for something the target CLI can't
express — rather than silently dropping it, which is the failure mode this
whole project exists to avoid.

Sequencing

Do this after #18 (TypeScript). Rationale:

  1. This is a design change; Migrate to TypeScript #18 is a mechanical one. Reviewing a language
    migration and a boundary change together means reviewing neither.
  2. Ports are far better expressed as real TS interfaces, and capabilities
    with per-CLI variance is exactly where a union type earns its keep.
  3. Migrate to TypeScript #18 should not deepen the coupling while it passes through. Types that
    encode Claude Code specifics belong near the eventual adapter boundary, not
    baked into core domain types.

Acceptance criteria

  • AgentCliPort defined; claude-code as the reference adapter
  • Core speaks neutral intent — no ANTHROPIC_* or CLAUDE_CODE_* literals
    outside the Claude Code adapter
  • Binary resolution is per-adapter, and the self-resolution guard survives
  • Capability declaration, with a defined behaviour when a profile asks for
    something unsupported (warn or refuse — never silently drop)
  • [1m], the 200K clamp and compat flags all move into the Claude Code
    adapter
  • execve stays the launch mechanism regardless of adapter
  • A second adapter — even a stub — proves the abstraction, the same way
    ModelScope proved the catalog port
  • Existing profiles keep working with no user-visible change

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider presets and catalogs

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions