mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
- Add isValidToolType() type guard to replace unsafe `as ToolType` casts - Make `raw` field optional in ParsedEvent interface since never read - Fix MockParser in tests to implement missing error detection methods - Add @internal JSDoc to test-only exports (hasOutputParser, getAllOutputParsers, clearParserRegistry) - Update AGENT_SUPPORT.md to reference canonical ParsedEvent source instead of duplicating type definition - Add tests for new isValidToolType function
843 lines
29 KiB
Markdown
843 lines
29 KiB
Markdown
# 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<string, AgentCapabilities> = {
|
|
// ... 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<AgentSession[]> {
|
|
// Find and parse session files
|
|
const sessionDir = this.getSessionDir(projectPath);
|
|
// ... implementation
|
|
}
|
|
|
|
async readSession(projectPath: string, sessionId: string): Promise<SessionMessage[]> {
|
|
// 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 <session-id>` |
|
|
| Read-only | `--permission-mode plan` |
|
|
| Session ID Field | `session_id` (snake_case) |
|
|
| Session Storage | `~/.claude/projects/<encoded-path>/` |
|
|
|
|
**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 <session-id>` |
|
|
| 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 <sessionID>`, `opencode import <file>`
|
|
- **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 <sessionID>` 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.<id>.tools`: Enable tool calling support (critical for agentic use)
|
|
- `models.<id>.limit.context`: Max input tokens
|
|
- `models.<id>.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 <thread_id>` (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 <thread_id> "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)
|