# Adding Agent Support This guide explains how to add support for a new AI coding agent (provider) in Maestro. It covers the architecture, required implementations, and step-by-step instructions. ## Multi-Provider Architecture Status **Status:** ✅ Foundation Complete (2025-12-16) The multi-provider refactoring has established the pluggable architecture for supporting multiple AI agents: | Component | Status | Description | |-----------|--------|-------------| | Capability System | ✅ Complete | `AgentCapabilities` interface, capability gating in UI | | Generic Identifiers | ✅ Complete | `claudeSessionId` → `agentSessionId` across 47+ files | | Session Storage | ✅ Complete | `AgentSessionStorage` interface, Claude + OpenCode implementations | | Output Parsers | ✅ Complete | `AgentOutputParser` interface, Claude + OpenCode parsers | | Error Handling | ✅ Complete | `AgentError` types, detection patterns, recovery UI | | IPC API | ✅ Complete | `window.maestro.agentSessions.*` replaces `claude.*` | | UI Capability Gates | ✅ Complete | Features hidden/shown based on agent capabilities | ### Adding a New Agent To add support for a new agent (e.g., Gemini CLI, Codex), follow these steps: 1. Add agent definition to `src/main/agent-detector.ts` 2. Define capabilities in `src/main/agent-capabilities.ts` 3. Create output parser in `src/main/parsers/{agent}-output-parser.ts` 4. Register parser in `src/main/parsers/index.ts` 5. (Optional) Create session storage in `src/main/storage/{agent}-session-storage.ts` 6. (Optional) Add error patterns to `src/main/parsers/error-patterns.ts` See detailed instructions below. ## Table of Contents - [Vernacular](#vernacular) - [Architecture Overview](#architecture-overview) - [Agent Capability Model](#agent-capability-model) - [Step-by-Step: Adding a New Agent](#step-by-step-adding-a-new-agent) - [Implementation Details](#implementation-details) - [Error Handling](#error-handling) - [Testing Your Agent](#testing-your-agent) - [Supported Agents Reference](#supported-agents-reference) --- ## Vernacular Use these terms consistently throughout the codebase: | Term | Definition | |------|------------| | **Maestro Agent** | A configured AI assistant in Maestro (e.g., "My Claude Assistant") | | **Provider** | The underlying AI service (Claude Code, OpenCode, Codex, Gemini CLI) | | **Provider Session** | A conversation session managed by the provider (e.g., Claude's `session_id`) | | **Tab** | A Maestro UI tab that maps 1:1 to a Provider Session | **Hierarchy:** `Maestro Agent → Provider → Provider Sessions → Tabs` --- ## Architecture Overview Maestro uses a pluggable architecture for AI agents. Each agent integrates through: 1. **Agent Definition** (`src/main/agent-detector.ts`) - CLI binary, arguments, detection 2. **Capabilities** (`src/main/agent-capabilities.ts`) - Feature flags controlling UI 3. **Output Parser** (`src/main/parsers/`) - Translates agent JSON to Maestro events 4. **Session Storage** (`src/main/storage/`) - Optional browsing of past sessions 5. **Error Patterns** (`src/main/parsers/error-patterns.ts`) - Error detection and recovery ``` ┌─────────────────────────────────────────────────────────────┐ │ Maestro UI │ │ (InputArea, MainPanel, AgentSessionsBrowser, etc.) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Capability Gates │ │ useAgentCapabilities() → show/hide UI features │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ ProcessManager │ │ Spawns agent, routes output through parser │ └─────────────────────────────────────────────────────────────┘ │ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ ClaudeOutput │ │ OpenCodeOut │ │ YourAgent │ │ Parser │ │ Parser │ │ Parser │ └──────────────┘ └──────────────┘ └──────────────┘ ``` --- ## Agent Capability Model Each agent declares capabilities that determine which UI features are available. ### Capability Interface ```typescript // src/main/agent-capabilities.ts interface AgentCapabilities { // Core features supportsResume: boolean; // Can resume previous sessions supportsReadOnlyMode: boolean; // Has a plan/read-only mode supportsJsonOutput: boolean; // Emits structured JSON for parsing supportsSessionId: boolean; // Emits session ID for tracking // Advanced features supportsImageInput: boolean; // Can receive images in prompts supportsSlashCommands: boolean; // Has discoverable slash commands supportsSessionStorage: boolean; // Persists sessions we can browse supportsCostTracking: boolean; // Reports token costs supportsUsageStats: boolean; // Reports token counts // Streaming behavior supportsBatchMode: boolean; // Runs per-message (vs persistent process) supportsStreaming: boolean; // Streams output incrementally // Message classification supportsResultMessages: boolean; // Distinguishes final result from intermediary } ``` ### Capability-to-UI Feature Mapping | Capability | UI Feature | Hidden When False | |------------|------------|-------------------| | `supportsReadOnlyMode` | Read-only toggle | Toggle hidden | | `supportsSessionStorage` | Sessions browser tab | Tab hidden | | `supportsResume` | Resume button | Button disabled | | `supportsCostTracking` | Cost widget | Widget hidden | | `supportsUsageStats` | Token usage display | Display hidden | | `supportsImageInput` | Image attachment button | Button hidden | | `supportsSlashCommands` | Slash command autocomplete | Autocomplete disabled | | `supportsSessionId` | Session ID pill | Pill hidden | | `supportsResultMessages` | Show only final result | Shows all messages | ### Context Window Configuration For agents where context window size varies by model (like OpenCode or Codex), Maestro provides a user-configurable setting: **Configuration Location:** Settings → Agent Configuration → Context Window Size **How It Works:** 1. **Parser-reported value:** If the agent reports `contextWindow` in JSON output, that value takes priority 2. **User configuration:** If the parser doesn't report context window, the user-configured value is used 3. **Hidden when zero:** If no value is configured (0), the context usage widget is hidden entirely **Agent-Specific Behavior:** | Agent | Default Context Window | Notes | |-------|----------------------|-------| | Claude Code | 200,000 | Always reported in JSON output | | Codex | 200,000 | Default for GPT-5.x models; user can override in settings | | OpenCode | 128,000 | Default for common models (GPT-4, etc.); user can override in settings | **Adding Context Window Config to an Agent:** ```typescript // In agent-detector.ts, add to configOptions: configOptions: [ { key: 'contextWindow', type: 'number', label: 'Context Window Size', description: 'Maximum context window size in tokens. Required for context usage display.', default: 128000, // Set a sane default for the agent's typical model }, ], ``` The value is passed to `ProcessManager.spawn()` and used when emitting usage stats if the parser doesn't provide a context window value. ### Starting Point: All False When adding a new agent, start with all capabilities set to `false`: ```typescript 'your-agent': { supportsResume: false, supportsReadOnlyMode: false, supportsJsonOutput: false, supportsSessionId: false, supportsImageInput: false, supportsSlashCommands: false, supportsSessionStorage: false, supportsCostTracking: false, supportsUsageStats: false, supportsBatchMode: false, supportsStreaming: false, supportsResultMessages: false, }, ``` Then enable capabilities as you implement and verify each feature. --- ## Step-by-Step: Adding a New Agent ### Step 1: Agent Discovery Before writing code, investigate your agent's CLI: ```bash # Check for JSON output mode your-agent --help | grep -i json your-agent --help | grep -i format # Check for session resume your-agent --help | grep -i session your-agent --help | grep -i resume your-agent --help | grep -i continue # Check for read-only/plan mode your-agent --help | grep -i plan your-agent --help | grep -i readonly your-agent --help | grep -i permission # Test JSON output your-agent run --format json "say hello" 2>&1 | head -20 ``` Document: - [ ] How to get JSON output - [ ] Session ID field name and format - [ ] How to resume a session - [ ] How to enable read-only mode - [ ] Token/usage reporting format ### Step 2: Add Agent Definition Edit `src/main/agent-detector.ts`: ```typescript const AGENT_DEFINITIONS: AgentConfig[] = [ // ... existing agents { id: 'your-agent', name: 'Your Agent', binaryName: 'your-agent', command: 'your-agent', args: [], // CLI argument builders batchModePrefix: ['run'], // Subcommand for batch mode jsonOutputArgs: ['--format', 'json'], // JSON output flag resumeArgs: (sessionId) => ['--session', sessionId], readOnlyArgs: ['--mode', 'readonly'], // Runtime (set by detection) available: false, path: undefined, }, ]; ``` ### Step 3: Define Capabilities Edit `src/main/agent-capabilities.ts`: ```typescript const AGENT_CAPABILITIES: Record = { // ... existing agents 'your-agent': { supportsResume: true, // If --session works supportsReadOnlyMode: true, // If readonly mode exists supportsJsonOutput: true, // If JSON output works supportsSessionId: true, // If session ID in output supportsImageInput: false, // Start false, enable if supported supportsSlashCommands: false, supportsSessionStorage: false, // Enable if you implement storage supportsCostTracking: false, // Enable if API-based with costs supportsUsageStats: true, // If token counts in output supportsBatchMode: true, supportsStreaming: true, supportsResultMessages: false, // Enable if result vs intermediary distinction }, }; ``` ### Step 4: Create Output Parser Create `src/main/parsers/your-agent-output-parser.ts`: ```typescript import { AgentOutputParser, ParsedEvent } from './agent-output-parser'; export class YourAgentOutputParser implements AgentOutputParser { parseJsonLine(line: string): ParsedEvent | null { try { const event = JSON.parse(line); // Map your agent's event types to Maestro's ParsedEvent switch (event.type) { case 'your_text_event': return { type: 'text', sessionId: event.sessionId, text: event.content, raw: event, }; case 'your_tool_event': return { type: 'tool_use', sessionId: event.sessionId, toolName: event.tool, toolState: event.state, raw: event, }; case 'your_finish_event': return { type: 'result', sessionId: event.sessionId, text: event.finalText, usage: { input: event.tokens?.input ?? 0, output: event.tokens?.output ?? 0, }, raw: event, }; default: return null; } } catch { return null; } } isResultMessage(event: ParsedEvent): boolean { return event.type === 'result'; } extractSessionId(event: ParsedEvent): string | null { return event.sessionId ?? null; } } ``` ### Step 5: Register Parser in Factory Edit `src/main/parsers/agent-output-parser.ts`: ```typescript import { YourAgentOutputParser } from './your-agent-output-parser'; export function getOutputParser(agentId: string): AgentOutputParser { switch (agentId) { case 'claude-code': return new ClaudeOutputParser(); case 'opencode': return new OpenCodeOutputParser(); case 'your-agent': return new YourAgentOutputParser(); default: return new GenericOutputParser(); } } ``` ### Step 6: Add Error Patterns (Optional but Recommended) Edit `src/main/parsers/error-patterns.ts`: ```typescript export const YOUR_AGENT_ERROR_PATTERNS = { auth_expired: [ /authentication failed/i, /invalid.*key/i, /please login/i, ], token_exhaustion: [ /context.*exceeded/i, /too many tokens/i, ], rate_limited: [ /rate limit/i, /too many requests/i, ], }; ``` ### Step 7: Implement Session Storage (Optional) If your agent stores sessions in browseable files, create `src/main/storage/your-agent-session-storage.ts`: ```typescript import { AgentSessionStorage, AgentSession } from '../agent-session-storage'; export class YourAgentSessionStorage implements AgentSessionStorage { async listSessions(projectPath: string): Promise { // Find and parse session files const sessionDir = this.getSessionDir(projectPath); // ... implementation } async readSession(projectPath: string, sessionId: string): Promise { // Read and parse session file // ... implementation } // ... other methods } ``` ### Step 8: Test Your Integration ```bash # Run dev build npm run dev # Create a session with your agent # 1. Open Maestro # 2. Create new session, select your agent # 3. Send a message # 4. Verify output displays correctly # 5. Test session resume (if supported) # 6. Test read-only mode (if supported) ``` --- ## Implementation Details ### Message Display Classification Agents may emit **intermediary messages** (streaming, tool calls) and **result messages** (final response). Configure display behavior via `supportsResultMessages`: | supportsResultMessages | Behavior | |------------------------|----------| | `true` | Only show result messages prominently; collapse intermediary | | `false` | Show all messages as they stream | ### CLI Argument Builders The `AgentConfig` supports several argument builder patterns: ```typescript interface AgentConfig { // Static arguments always included args: string[]; // Subcommand prefix for batch mode (e.g., ['run'] for opencode) batchModePrefix?: string[]; // Arguments for JSON output jsonOutputArgs?: string[]; // Function to build resume arguments resumeArgs?: (sessionId: string) => string[]; // Arguments for read-only mode readOnlyArgs?: string[]; } ``` ### ParsedEvent Types Your output parser should emit these normalized event types. See [`src/main/parsers/agent-output-parser.ts`](src/main/parsers/agent-output-parser.ts) for the canonical `ParsedEvent` interface definition. Key event types: - `init` - Agent initialization (may contain session ID, available commands) - `text` - Text content to display to user - `tool_use` - Agent is using a tool (file read, bash, etc.) - `result` - Final result/response from agent - `error` - Error occurred - `usage` - Token usage statistics - `system` - System-level messages (not user-facing content) Import the interface directly rather than defining your own: ```typescript import { type ParsedEvent } from './agent-output-parser'; ``` --- ## Error Handling Maestro has unified error handling for agent failures. Your agent should integrate with this system. ### Error Types | Error Type | When to Detect | |------------|----------------| | `auth_expired` | API key invalid, login required | | `token_exhaustion` | Context window full | | `rate_limited` | Too many requests | | `network_error` | Connection failed | | `agent_crashed` | Non-zero exit code | | `permission_denied` | Operation not allowed | ### Adding Error Detection In your output parser, implement the `detectError` method: ```typescript detectError(line: string): AgentError | null { for (const [errorType, patterns] of Object.entries(YOUR_AGENT_ERROR_PATTERNS)) { for (const pattern of patterns) { if (pattern.test(line)) { return { type: errorType as AgentError['type'], message: line, recoverable: errorType !== 'agent_crashed', agentId: 'your-agent', timestamp: Date.now(), }; } } } return null; } ``` --- ## Testing Your Agent ### Unit Tests Create `src/__tests__/parsers/your-agent-output-parser.test.ts`: ```typescript import { YourAgentOutputParser } from '../../main/parsers/your-agent-output-parser'; describe('YourAgentOutputParser', () => { const parser = new YourAgentOutputParser(); it('parses text events', () => { const line = '{"type": "your_text_event", "sessionId": "123", "content": "Hello"}'; const event = parser.parseJsonLine(line); expect(event).toEqual({ type: 'text', sessionId: '123', text: 'Hello', raw: expect.any(Object), }); }); it('extracts session ID', () => { const event = { type: 'text', sessionId: 'abc-123', raw: {} }; expect(parser.extractSessionId(event)).toBe('abc-123'); }); it('detects auth errors', () => { const error = parser.detectError('Error: authentication failed'); expect(error?.type).toBe('auth_expired'); }); }); ``` ### Integration Testing Checklist - [ ] Agent appears in agent selection dropdown - [ ] New session starts successfully - [ ] Output streams to AI Terminal - [ ] Session ID captured and displayed - [ ] Token usage updates (if applicable) - [ ] Session resume works (if applicable) - [ ] Read-only mode works (if applicable) - [ ] Error modal appears on auth/token errors - [ ] Auto Run works with your agent --- ## Supported Agents Reference ### Claude Code ✅ Fully Implemented | Aspect | Value | |--------|-------| | Binary | `claude` | | JSON Output | `--output-format stream-json` | | Resume | `--resume ` | | Read-only | `--permission-mode plan` | | Session ID Field | `session_id` (snake_case) | | Session Storage | `~/.claude/projects//` | **Implementation Status:** - ✅ Output Parser: `src/main/parsers/claude-output-parser.ts` - ✅ Session Storage: `src/main/storage/claude-session-storage.ts` - ✅ Error Patterns: `src/main/parsers/error-patterns.ts` - ✅ All capabilities enabled **JSON Event Types:** - `system` (init) → session_id, slash_commands - `assistant` → streaming content - `result` → final response, modelUsage --- ### OpenCode 🔄 Stub Ready | Aspect | Value | |--------|-------| | Binary | `opencode` | | JSON Output | `--format json` | | Resume | `--session ` | | Read-only | `--agent plan` | | Session ID Field | `sessionID` (camelCase) | | Session Storage | ✅ File-based (see below) | | YOLO Mode | ✅ Auto-enabled in batch mode | | Model Selection | `--model provider/model` | | Config File | `~/.config/opencode/opencode.json` or project `opencode.json` | **YOLO Mode (Auto-Approval) Details:** OpenCode automatically approves all tool operations in batch mode (`opencode run`). Per [official documentation](https://opencode.ai/docs/permissions/): - **Batch mode behavior:** "All permissions are auto-approved for the session" when running non-interactively - **No explicit flag needed:** Unlike Claude Code's `--dangerously-skip-permissions`, OpenCode's `run` subcommand inherently auto-approves - **Permission defaults:** Most tools run without approval by default; only `doom_loop` and `external_directory` require explicit approval in interactive mode - **Configurable permissions:** Advanced users can customize via `opencode.json` with granular tool-level controls (`allow`, `ask`, `deny`) - **Read-only operations:** Tools like `view`, `glob`, `grep`, `ls`, and `diagnostics` never require approval This makes OpenCode suitable for Maestro's batch processing use case without additional configuration. **Session Storage Details:** OpenCode stores session data in `~/.local/share/opencode/storage/` with the following structure: ``` ~/.local/share/opencode/ ├── log/ # Log files ├── snapshot/ # Git-style snapshots └── storage/ ├── project/ # Project metadata (JSON per project) │ └── {projectID}.json # Contains: id, worktree path, vcs info, timestamps ├── session/ # Session metadata (organized by project) │ ├── global/ # Sessions not tied to a specific project │ │ └── {sessionID}.json # Session info: id, version, projectID, title, timestamps │ └── {projectID}/ # Project-specific sessions │ └── {sessionID}.json ├── message/ # Message metadata (organized by session) │ └── {sessionID}/ # One folder per session │ └── {messageID}.json # Message info: role, time, model, tokens, etc. └── part/ # Message parts (content chunks) └── {messageID}/ # One folder per message └── {partID}.json # Part content: type (text/tool/reasoning), text, etc. ``` **Key findings:** - **CLI Commands:** `opencode session list`, `opencode export `, `opencode import ` - **Project IDs:** SHA1 hash of project path (e.g., `ca85ff7c488724e85fc5b4be14ba44a0f6ce5b40`) - **Session IDs:** Format `ses_{base62-ish}` (e.g., `ses_4d585107dffeO9bO3HvMdvLYyC`) - **Message IDs:** Format `msg_{base62-ish}` (e.g., `msg_b2a7aef8d001MjwADMqsUcIj3k`) - **Export format:** `opencode export ` outputs complete session JSON with all messages and parts - **Message parts include:** `text`, `reasoning`, `tool`, `step-start`, etc. - **Token tracking:** Available in message metadata with `input`, `output`, `reasoning`, and cache fields **Implementation Status:** - ✅ Output Parser: `src/main/parsers/opencode-output-parser.ts` (based on expected format) - ⏳ Session Storage: `src/main/storage/opencode-session-storage.ts` (stub, needs implementation using storage paths above) - ⏳ Error Patterns: Placeholder, needs real-world testing - ⏳ Capabilities: Set to minimal defaults; `supportsSessionStorage` can be enabled once storage is implemented **JSON Event Types:** - `step_start` → session start (includes snapshot reference) - `text` → streaming content - `reasoning` → model thinking/chain-of-thought - `tool` → tool invocations with state (running/complete) - `step_finish` → tokens, completion **Provider & Model Configuration:** OpenCode supports 75+ LLM providers including local models via Ollama, LM Studio, and llama.cpp. Configuration is stored in: - **Global config:** `~/.config/opencode/opencode.json` - **Per-project config:** `opencode.json` in project root - **Custom path:** Via `OPENCODE_CONFIG` environment variable Configuration files are merged, with project config overriding global config for conflicting keys. **Ollama Setup Example:** ```json { "$schema": "https://opencode.ai/config.json", "model": "ollama/qwen3:8b-16k", "provider": { "ollama": { "npm": "@ai-sdk/openai-compatible", "name": "Ollama (local)", "options": { "baseURL": "http://localhost:11434/v1" }, "models": { "qwen3:8b-16k": { "name": "Qwen3 8B", "tools": true } } } } } ``` **Key Configuration Options:** - `npm`: Provider package (use `@ai-sdk/openai-compatible` for OpenAI-compatible APIs) - `options.baseURL`: API endpoint URL - `models..tools`: Enable tool calling support (critical for agentic use) - `models..limit.context`: Max input tokens - `models..limit.output`: Max output tokens **Context Window Configuration (Ollama):** Ollama defaults to 4096 context regardless of model capability. To increase context: ```bash # Create a model variant with larger context ollama run qwen3:8b /set parameter num_ctx 16384 /save qwen3:8b-16k ``` Then reference the custom model name in OpenCode config. **Other Local Provider Examples:** ```json // LM Studio "lmstudio": { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "http://127.0.0.1:1234/v1" } } // llama.cpp "llamacpp": { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "http://127.0.0.1:8080/v1" } } ``` **Model Selection Methods:** 1. **Command-line:** `opencode run --model ollama/qwen3:8b-16k "prompt"` 2. **Config file:** Set `"model": "provider/model"` in opencode.json 3. **Interactive:** Use `/models` command in interactive mode Model ID format: `provider_id/model_id` (e.g., `ollama/llama2`, `anthropic/claude-sonnet-4-5`) **Maestro Integration Considerations:** Since OpenCode supports multiple providers/models, Maestro should consider: 1. **Model selection UI:** Add model dropdown when OpenCode is selected, populated from config or `opencode models` command 2. **Default config generation:** Optionally generate `~/.config/opencode/opencode.json` for Ollama on first use 3. **Per-session model:** Pass `--model` flag based on user selection 4. **Provider status:** Detect which providers are configured and available **Documentation Sources:** - [OpenCode Config Docs](https://opencode.ai/docs/config/) - [OpenCode Providers Docs](https://opencode.ai/docs/providers/) - [OpenCode Models Docs](https://opencode.ai/docs/models/) --- ### Gemini CLI 📋 Planned **Status:** Not yet implemented **To Add:** 1. Agent definition in `agent-detector.ts` 2. Capabilities in `agent-capabilities.ts` 3. Output parser for Gemini JSON format 4. Error patterns for Google API errors --- ### Codex ✅ Fully Implemented | Aspect | Value | |--------|-------| | Binary | `codex` | | JSON Output | `--json` | | Batch Mode | `exec` subcommand | | Resume | `resume ` (v0.30.0+) | | Read-only | `--sandbox read-only` | | YOLO Mode | `--dangerously-bypass-approvals-and-sandbox` (enabled by default) | | Session ID Field | `thread_id` (from `thread.started` event) | | Session Storage | `~/.codex/sessions/YYYY/MM/DD/*.jsonl` | | Context Window | 128K tokens | | Pricing | o4-mini: $1.10/$4.40 per million tokens (input/output) | **Implementation Status:** - ✅ Output Parser: `src/main/parsers/codex-output-parser.ts` (42 tests) - ✅ Session Storage: `src/main/storage/codex-session-storage.ts` (8 tests) - ✅ Error Patterns: `src/main/parsers/error-patterns.ts` (25 tests) - ✅ All capabilities enabled **JSON Event Types:** - `thread.started` → session_id (`thread_id`), initialization - `turn.started` → processing indicator - `item.completed (agent_message)` → final text response - `item.completed (reasoning)` → model thinking (partial text) - `item.completed (tool_call)` → tool invocation - `item.completed (tool_result)` → tool output - `turn.completed` → token usage (`input_tokens`, `output_tokens`, `reasoning_output_tokens`, `cached_input_tokens`) **Unique Features:** - **Reasoning Tokens:** Reports `reasoning_output_tokens` separately from `output_tokens`, displayed in UI - **Three Sandbox Levels:** `read-only`, `workspace-write`, `danger-full-access` - **Cached Input Discount:** 75% discount on cached input tokens ($0.275/million) - **YOLO Mode Default:** Full system access enabled by default in Maestro **Command Line Pattern:** ```bash # Basic execution codex exec --json -C /path/to/project "prompt" # With YOLO mode (default in Maestro) codex exec --json --dangerously-bypass-approvals-and-sandbox -C /path/to/project "prompt" # Resume session codex exec --json resume "continue" ``` **Documentation Sources:** - [Codex CLI GitHub](https://github.com/openai/codex) - [OpenAI API Pricing](https://openai.com/api/pricing/) --- ### Qwen3 Coder 📋 Planned **Status:** Not yet implemented **To Add:** 1. Agent definition in `agent-detector.ts` 2. Capabilities in `agent-capabilities.ts` (likely local model, no cost tracking) 3. Output parser for Qwen JSON format 4. Error patterns (likely minimal for local models)