Files
tanmay11k 453389f55c feat: add pluggable multi-CLI backend system
Implement BackendAdapter interface with four CLI backends:
- ClaudeCodeBackend (extracted from AgentRuntime)
- CodexBackend (OpenAI Codex CLI)
- GeminiBackend (Google Gemini CLI)
- OpenCodeBackend (OpenCode CLI)

Add BackendRegistry for resolution/creation via AGENT_BACKEND env var.
Refactor AgentRuntime to delegate to BackendAdapter instead of
hardcoding Claude CLI. Update GatewayConfig with new env vars
(AGENT_BACKEND, BACKEND_CLI_PATH, BACKEND_MODEL, BACKEND_MAX_TURNS).

Includes 10 property-based test files and unit tests for edge cases.
2026-02-22 23:41:30 -05:00

6.0 KiB

Tasks

Task 1: Create BackendAdapter interface and shared types

  • 1.1 Create src/backends/types.ts with BackendAdapter interface, BackendAdapterConfig, BackendEventResult, StreamCallback, and BackendName type
  • 1.2 Export all types from src/backends/index.ts barrel file

Task 2: Implement ClaudeCodeBackend

  • 2.1 Create src/backends/claude-backend.ts implementing BackendAdapter
  • 2.2 Extract CLI spawning logic from AgentRuntime.runClaude() into execute() method with arg building for -p, --output-format json, --dangerously-skip-permissions, --append-system-prompt-file, --allowedTools, --max-turns, and --resume
  • 2.3 Implement validate() to check CLI binary is executable
  • 2.4 Implement JSON array output parser extracting session_id from system/init and result from result objects
  • 2.5 Write property test: Claude backend required flags (Property 1)
    • 🧪 PBT: For any prompt, system prompt, and tools list, generated args contain all required flags

Task 3: Implement CodexBackend

  • 3.1 Create src/backends/codex-backend.ts implementing BackendAdapter
  • 3.2 Implement execute() with codex exec subcommand, --json, --dangerously-bypass-approvals-and-sandbox, --cd, and codex exec resume <id> for sessions
  • 3.3 Implement newline-delimited JSON parser extracting final assistant message
  • 3.4 Write property test: Codex backend required flags (Property 2)
    • 🧪 PBT: For any prompt and working directory, generated args contain exec, --json, --dangerously-bypass-approvals-and-sandbox, and --cd

Task 4: Implement GeminiBackend

  • 4.1 Create src/backends/gemini-backend.ts implementing BackendAdapter
  • 4.2 Implement execute() with prompt as positional arg, --output-format json, --approval-mode yolo, and --resume for sessions
  • 4.3 Implement JSON output parser extracting response text
  • 4.4 Write property test: Gemini backend required flags (Property 3)
    • 🧪 PBT: For any prompt, generated args contain the prompt positionally, --output-format json, and --approval-mode yolo

Task 5: Implement OpenCodeBackend

  • 5.1 Create src/backends/opencode-backend.ts implementing BackendAdapter
  • 5.2 Implement execute() with opencode run subcommand, --format json, --model, and --session <id> --continue for sessions
  • 5.3 Implement JSON event parser extracting final response text
  • 5.4 Write property test: OpenCode backend required flags (Property 4)
    • 🧪 PBT: For any prompt and optional model, generated args contain run, --format json, and --model when configured

Task 6: Implement BackendRegistry

  • 6.1 Create src/backends/registry.ts with resolveBackendName() and createBackend() functions
  • 6.2 resolveBackendName accepts "claude", "codex", "gemini", "opencode", defaults to "claude" for undefined, throws for invalid values
  • 6.3 createBackend instantiates the correct backend implementation from a BackendName
  • 6.4 Write property test: Backend name resolution (Property 7)
    • 🧪 PBT: For any string, resolveBackendName returns correct BackendName for valid values, "claude" for undefined, and throws for invalid

Task 7: Cross-backend property tests

  • 7.1 Write property test: Session resume args across backends (Property 5)
    • 🧪 PBT: For any backend and session ID, session flags appear when ID is provided and are absent when not
  • 7.2 Write property test: Output parsing extracts correct fields (Property 6)
    • 🧪 PBT: For any valid backend-specific JSON output, parser produces BackendEventResult with correct responseText and sessionId
  • 7.3 Write property test: Non-zero exit code produces error result (Property 8)
    • 🧪 PBT: For any backend, non-zero exit code, and stderr string, result has isError=true and responseText contains stderr

Task 8: Update GatewayConfig

  • 8.1 Add agentBackend, backendCliPath, backendModel, backendMaxTurns fields to GatewayConfig interface in src/config.ts
  • 8.2 Update loadConfig() to read AGENT_BACKEND, BACKEND_CLI_PATH, BACKEND_MODEL, BACKEND_MAX_TURNS env vars with defaults
  • 8.3 Deprecate claudeCliPath field (keep for backward compat, map to backendCliPath when AGENT_BACKEND=claude)

Task 9: Refactor AgentRuntime

  • 9.1 Add BackendAdapter parameter to AgentRuntime constructor
  • 9.2 Replace executeClaude() and runClaude() with calls to this.backend.execute()
  • 9.3 Implement BackendEventResult → gateway EventResult mapping in a helper method
  • 9.4 Remove ClaudeJsonResponse interface and Claude-specific parsing from AgentRuntime
  • 9.5 Write property test: EventResult mapping preserves semantics (Property 9)
    • 🧪 PBT: For any BackendEventResult and channel ID, mapping sets error or responseText correctly based on isError
  • 9.6 Write property test: Session ID storage after backend execution (Property 10)
    • 🧪 PBT: For any channel ID and BackendEventResult with sessionId, SessionManager contains that sessionId after processing

Task 10: Startup validation and wiring

  • 10.1 Update main entry point to call resolveBackendName() and createBackend() from config
  • 10.2 Call backend.validate() at startup; log error with backend name and path, exit(1) on failure
  • 10.3 Inject the BackendAdapter instance into AgentRuntime constructor
  • 10.4 Write unit tests for startup validation flow (valid backend, invalid backend name, missing CLI binary)

Task 11: Unit tests for edge cases

  • 11.1 Write unit tests for each backend's validate() method (binary exists vs missing)
  • 11.2 Write unit tests for timeout behavior (process killed after queryTimeoutMs)
  • 11.3 Write unit tests for session corruption detection and cleanup
  • 11.4 Write unit tests for default config values when env vars are unset
  • 11.5 Write unit tests for unsupported option warning (e.g., ALLOWED_TOOLS on backends without tool filtering)