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.
This commit is contained in:
77
.kiro/specs/multi-cli-backend/tasks.md
Normal file
77
.kiro/specs/multi-cli-backend/tasks.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Tasks
|
||||
|
||||
## Task 1: Create BackendAdapter interface and shared types
|
||||
- [x] 1.1 Create `src/backends/types.ts` with `BackendAdapter` interface, `BackendAdapterConfig`, `BackendEventResult`, `StreamCallback`, and `BackendName` type
|
||||
- [x] 1.2 Export all types from `src/backends/index.ts` barrel file
|
||||
|
||||
## Task 2: Implement ClaudeCodeBackend
|
||||
- [x] 2.1 Create `src/backends/claude-backend.ts` implementing `BackendAdapter`
|
||||
- [x] 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`
|
||||
- [x] 2.3 Implement `validate()` to check CLI binary is executable
|
||||
- [x] 2.4 Implement JSON array output parser extracting `session_id` from `system/init` and `result` from `result` objects
|
||||
- [x] 2.5 Write property test: Claude backend required flags (Property 1)
|
||||
- [x] 🧪 PBT: *For any* prompt, system prompt, and tools list, generated args contain all required flags
|
||||
|
||||
## Task 3: Implement CodexBackend
|
||||
- [x] 3.1 Create `src/backends/codex-backend.ts` implementing `BackendAdapter`
|
||||
- [x] 3.2 Implement `execute()` with `codex exec` subcommand, `--json`, `--dangerously-bypass-approvals-and-sandbox`, `--cd`, and `codex exec resume <id>` for sessions
|
||||
- [x] 3.3 Implement newline-delimited JSON parser extracting final assistant message
|
||||
- [x] 3.4 Write property test: Codex backend required flags (Property 2)
|
||||
- [x] 🧪 PBT: *For any* prompt and working directory, generated args contain exec, --json, --dangerously-bypass-approvals-and-sandbox, and --cd
|
||||
|
||||
## Task 4: Implement GeminiBackend
|
||||
- [x] 4.1 Create `src/backends/gemini-backend.ts` implementing `BackendAdapter`
|
||||
- [x] 4.2 Implement `execute()` with prompt as positional arg, `--output-format json`, `--approval-mode yolo`, and `--resume` for sessions
|
||||
- [x] 4.3 Implement JSON output parser extracting response text
|
||||
- [x] 4.4 Write property test: Gemini backend required flags (Property 3)
|
||||
- [x] 🧪 PBT: *For any* prompt, generated args contain the prompt positionally, --output-format json, and --approval-mode yolo
|
||||
|
||||
## Task 5: Implement OpenCodeBackend
|
||||
- [x] 5.1 Create `src/backends/opencode-backend.ts` implementing `BackendAdapter`
|
||||
- [x] 5.2 Implement `execute()` with `opencode run` subcommand, `--format json`, `--model`, and `--session <id> --continue` for sessions
|
||||
- [x] 5.3 Implement JSON event parser extracting final response text
|
||||
- [x] 5.4 Write property test: OpenCode backend required flags (Property 4)
|
||||
- [x] 🧪 PBT: *For any* prompt and optional model, generated args contain run, --format json, and --model when configured
|
||||
|
||||
## Task 6: Implement BackendRegistry
|
||||
- [x] 6.1 Create `src/backends/registry.ts` with `resolveBackendName()` and `createBackend()` functions
|
||||
- [x] 6.2 `resolveBackendName` accepts "claude", "codex", "gemini", "opencode", defaults to "claude" for undefined, throws for invalid values
|
||||
- [x] 6.3 `createBackend` instantiates the correct backend implementation from a `BackendName`
|
||||
- [x] 6.4 Write property test: Backend name resolution (Property 7)
|
||||
- [x] 🧪 PBT: *For any* string, resolveBackendName returns correct BackendName for valid values, "claude" for undefined, and throws for invalid
|
||||
|
||||
## Task 7: Cross-backend property tests
|
||||
- [x] 7.1 Write property test: Session resume args across backends (Property 5)
|
||||
- [x] 🧪 PBT: *For any* backend and session ID, session flags appear when ID is provided and are absent when not
|
||||
- [x] 7.2 Write property test: Output parsing extracts correct fields (Property 6)
|
||||
- [x] 🧪 PBT: *For any* valid backend-specific JSON output, parser produces BackendEventResult with correct responseText and sessionId
|
||||
- [x] 7.3 Write property test: Non-zero exit code produces error result (Property 8)
|
||||
- [x] 🧪 PBT: *For any* backend, non-zero exit code, and stderr string, result has isError=true and responseText contains stderr
|
||||
|
||||
## Task 8: Update GatewayConfig
|
||||
- [x] 8.1 Add `agentBackend`, `backendCliPath`, `backendModel`, `backendMaxTurns` fields to `GatewayConfig` interface in `src/config.ts`
|
||||
- [x] 8.2 Update `loadConfig()` to read `AGENT_BACKEND`, `BACKEND_CLI_PATH`, `BACKEND_MODEL`, `BACKEND_MAX_TURNS` env vars with defaults
|
||||
- [x] 8.3 Deprecate `claudeCliPath` field (keep for backward compat, map to `backendCliPath` when `AGENT_BACKEND=claude`)
|
||||
|
||||
## Task 9: Refactor AgentRuntime
|
||||
- [x] 9.1 Add `BackendAdapter` parameter to `AgentRuntime` constructor
|
||||
- [x] 9.2 Replace `executeClaude()` and `runClaude()` with calls to `this.backend.execute()`
|
||||
- [x] 9.3 Implement `BackendEventResult` → gateway `EventResult` mapping in a helper method
|
||||
- [x] 9.4 Remove `ClaudeJsonResponse` interface and Claude-specific parsing from `AgentRuntime`
|
||||
- [x] 9.5 Write property test: EventResult mapping preserves semantics (Property 9)
|
||||
- [x] 🧪 PBT: *For any* BackendEventResult and channel ID, mapping sets error or responseText correctly based on isError
|
||||
- [x] 9.6 Write property test: Session ID storage after backend execution (Property 10)
|
||||
- [x] 🧪 PBT: *For any* channel ID and BackendEventResult with sessionId, SessionManager contains that sessionId after processing
|
||||
|
||||
## Task 10: Startup validation and wiring
|
||||
- [x] 10.1 Update main entry point to call `resolveBackendName()` and `createBackend()` from config
|
||||
- [x] 10.2 Call `backend.validate()` at startup; log error with backend name and path, exit(1) on failure
|
||||
- [x] 10.3 Inject the `BackendAdapter` instance into `AgentRuntime` constructor
|
||||
- [x] 10.4 Write unit tests for startup validation flow (valid backend, invalid backend name, missing CLI binary)
|
||||
|
||||
## Task 11: Unit tests for edge cases
|
||||
- [x] 11.1 Write unit tests for each backend's `validate()` method (binary exists vs missing)
|
||||
- [x] 11.2 Write unit tests for timeout behavior (process killed after queryTimeoutMs)
|
||||
- [x] 11.3 Write unit tests for session corruption detection and cleanup
|
||||
- [x] 11.4 Write unit tests for default config values when env vars are unset
|
||||
- [x] 11.5 Write unit tests for unsupported option warning (e.g., ALLOWED_TOOLS on backends without tool filtering)
|
||||
Reference in New Issue
Block a user