> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instructorphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 10 opencode bridge

## Overview

The OpenCode bridge wraps the `opencode` CLI, providing access to a multi-provider code agent through Agent-Ctrl's unified API. OpenCode is the most flexible bridge in terms of model selection -- it supports provider-prefixed model IDs that let you use models from Anthropic, OpenAI, Google, and other providers through a single CLI tool. It exposes both token usage and cost data (as does the Pi bridge).

The bridge is implemented by `OpenCodeBridge` and configured through `OpenCodeBridgeBuilder`. Access the builder through the `AgentCtrl` facade:

```php theme={null}
use Cognesy\AgentCtrl\AgentCtrl;
use Cognesy\AgentCtrl\Enum\AgentType;

// Dedicated factory method
$builder = AgentCtrl::openCode();

// Or via the generic factory
$builder = AgentCtrl::make(AgentType::OpenCode);
// @doctest id="7e77"
```

## Basic Usage

```php theme={null}
use Cognesy\AgentCtrl\AgentCtrl;

$response = AgentCtrl::openCode()
    ->execute('Explain the architecture of this project in short paragraphs.');

echo $response->text();
// @doctest id="0f12"
```

With model selection:

```php theme={null}
$response = AgentCtrl::openCode()
    ->withModel('anthropic/claude-sonnet-4-5')
    ->execute('Review the test suite.');

echo $response->text();
// @doctest id="e0ae"
```

## Model Selection

OpenCode uses provider-prefixed model IDs, giving you access to models from multiple providers through a single CLI. The format is `provider/model-name`:

```php theme={null}
// Anthropic models
AgentCtrl::openCode()->withModel('anthropic/claude-sonnet-4-5');
AgentCtrl::openCode()->withModel('anthropic/claude-opus-4');

// OpenAI models
AgentCtrl::openCode()->withModel('openai/gpt-4o');
AgentCtrl::openCode()->withModel('openai/o4-mini');

// Google models
AgentCtrl::openCode()->withModel('google/gemini-2.5-pro');
// @doctest id="9036"
```

The exact set of available providers and models depends on your OpenCode installation and configuration. If no model is specified, OpenCode uses its configured default. Check OpenCode's documentation for the full list of supported providers and models.

## Named Agents

OpenCode supports named agents -- preconfigured agent profiles that define specific behaviors, tools, and system prompts. Use `withAgent()` to select one:

```php theme={null}
$response = AgentCtrl::openCode()
    ->withAgent('coder')
    ->execute('Refactor the authentication module.');
// @doctest id="29b6"
```

```php theme={null}
$response = AgentCtrl::openCode()
    ->withAgent('task')
    ->execute('Create a detailed implementation plan.');
// @doctest id="c297"
```

The available agent names depend on your OpenCode configuration. Common agents include `coder` (for code-focused tasks) and `task` (for planning and general tasks).

## File Attachments

Use `withFiles()` to attach specific files to the prompt. The agent will have direct access to these files as context, without needing to discover and read them:

```php theme={null}
$response = AgentCtrl::openCode()
    ->withFiles([
        '/projects/my-app/src/Services/PaymentService.php',
        '/projects/my-app/src/Models/Payment.php',
    ])
    ->execute('Refactor the PaymentService to handle partial refunds.');
// @doctest id="12bb"
```

Unlike `inDirectory()` which sets the working directory, `withFiles()` explicitly includes specific files in the prompt context. This is useful when you want the agent to focus on particular files rather than browsing the project directory.

## Session Title

Use `withTitle()` to set a descriptive title for the session. The title appears in OpenCode's session listing and makes it easier to identify sessions later:

```php theme={null}
$response = AgentCtrl::openCode()
    ->withTitle('Payment module refactoring')
    ->execute('Plan the payment module refactoring.');
// @doctest id="c0a3"
```

Titles are especially useful when you manage multiple sessions and need to identify them by purpose rather than by opaque session IDs.

## Session Sharing

Use `shareSession()` to mark the session for sharing after completion. Shared sessions can be accessed by other users or tools, enabling collaborative workflows where multiple team members can review or continue an agent's work:

```php theme={null}
$response = AgentCtrl::openCode()
    ->shareSession()
    ->withTitle('Architecture review for team')
    ->execute('Create a comprehensive architecture review.');

// The session ID can be shared with others
$sessionId = $response->sessionId();
if ($sessionId !== null) {
    echo "Share this session ID with your team: {$sessionId}\n";
}
// @doctest id="d88a"
```

## Streaming with OpenCode

OpenCode streams output as JSON Lines containing text events, tool use events, step events, and error events. The bridge normalizes these into the standard callback API:

```php theme={null}
use Cognesy\AgentCtrl\AgentCtrl;
use Cognesy\AgentCtrl\Dto\AgentResponse;

$response = AgentCtrl::openCode()
    ->onText(fn(string $text) => print($text))
    ->onToolUse(fn(string $tool, array $input, ?string $output) => print("\n> [{$tool}]\n"))
    ->onError(fn(string $message, ?string $code) => print("\nError [{$code}]: {$message}\n"))
    ->executeStreaming('Analyze the error handling in this codebase.');
// @doctest id="bcf7"
```

### Event Normalization

During streaming, OpenCode emits several event types that are normalized:

* **`TextEvent`** -- Text content is delivered through `onText()` with the text string.
* **`ToolUseEvent`** -- Tool invocations are delivered through `onToolUse()` with the tool name, input parameters, optional output, and call ID. The `isError` flag is set based on whether the tool completed successfully.
* **`ErrorEvent`** -- Error events are delivered through `onError()` with the message, optional code, and raw data.
* **`StepStartEvent` / `StepFinishEvent`** -- Step lifecycle events are processed internally and available through the `wiretap()` event system but not directly exposed through user callbacks.

## Session Management

OpenCode maintains its own session system with session IDs. Agent-Ctrl extracts and normalizes these into `AgentSessionId` value objects. Internally, OpenCode uses `OpenCodeSessionId` which is mapped to the unified `AgentSessionId`:

```php theme={null}
// First execution
$first = AgentCtrl::openCode()->execute('Create an implementation plan.');
$sessionId = $first->sessionId();

// Continue the most recent session (no ID needed)
$next = AgentCtrl::openCode()
    ->continueSession()
    ->execute('Begin implementing the plan.');

// Resume a specific session by ID
if ($sessionId !== null) {
    $next = AgentCtrl::openCode()
        ->resumeSession((string) $sessionId)
        ->execute('Continue with the next step.');
}
// @doctest id="fff5"
```

## Usage and Cost Data

OpenCode provides comprehensive usage and cost reporting. Both token usage and cost data are available after execution.

### Token Usage

OpenCode's `TokenUsage` includes all five token categories -- input, output, cache read, cache write, and reasoning:

```php theme={null}
$response = AgentCtrl::openCode()
    ->withModel('anthropic/claude-sonnet-4-5')
    ->execute('Analyze the project dependencies.');

$usage = $response->usage();
if ($usage !== null) {
    echo "Input tokens:    {$usage->input}\n";
    echo "Output tokens:   {$usage->output}\n";
    echo "Total tokens:    {$usage->total()}\n";

    if ($usage->cacheRead !== null) {
        echo "Cache read:      {$usage->cacheRead}\n";
    }
    if ($usage->cacheWrite !== null) {
        echo "Cache write:     {$usage->cacheWrite}\n";
    }
    if ($usage->reasoning !== null) {
        echo "Reasoning:       {$usage->reasoning}\n";
    }
}
// @doctest id="0244"
```

### Cost Tracking

OpenCode exposes cost data (as does Pi). The cost is returned in USD:

```php theme={null}
$cost = $response->cost();
if ($cost !== null) {
    echo sprintf("This execution cost $%.4f\n", $cost);
}
// @doctest id="2669"
```

This makes OpenCode a good choice when you need to track and report on the cost of agent executions, build usage dashboards, or enforce cost budgets.

## Data Availability

| Data Point        | Available | Notes                                                              |
| ----------------- | --------- | ------------------------------------------------------------------ |
| Text output       | Yes       | Extracted from `TextEvent` stream events                           |
| Tool calls        | Yes       | Normalized from `ToolUseEvent` with call IDs and completion status |
| Session ID        | Yes       | Extracted from OpenCode session data                               |
| Token usage       | Yes       | Input, output, cache read, cache write, reasoning tokens           |
| Cost              | Yes       | Cost in USD                                                        |
| Parse diagnostics | Yes       | Malformed JSON line counts and samples                             |

## Complete Example

```php theme={null}
use Cognesy\AgentCtrl\AgentCtrl;
use Cognesy\AgentCtrl\Dto\AgentResponse;
use Cognesy\AgentCtrl\Broadcasting\AgentCtrlConsoleLogger;

$logger = new AgentCtrlConsoleLogger(
    showStreaming: true,
    showPipeline: true,
);

$response = AgentCtrl::openCode()
    ->withModel('anthropic/claude-sonnet-4-5')
    ->withAgent('coder')
    ->withTitle('Comprehensive architecture review')
    ->withFiles([
        '/projects/my-app/src/Kernel.php',
        '/projects/my-app/src/routes.php',
    ])
    ->shareSession()
    ->withTimeout(300)
    ->inDirectory('/projects/my-app')
    ->wiretap($logger->wiretap())
    ->onText(fn(string $text) => print($text))
    ->onToolUse(fn(string $tool, array $input, ?string $output) => print("\n> [{$tool}]\n"))
    ->onComplete(fn(AgentResponse $r) => print("\n--- Complete ---\n"))
    ->executeStreaming('Review the application architecture and suggest improvements.');

if ($response->isSuccess()) {
    echo "\nReview completed successfully.\n";
    echo "Tools used: " . count($response->toolCalls) . "\n";

    $usage = $response->usage();
    if ($usage !== null) {
        echo "Tokens: {$usage->total()} (in: {$usage->input}, out: {$usage->output})\n";
    }

    $cost = $response->cost();
    if ($cost !== null) {
        echo sprintf("Cost: $%.4f\n", $cost);
    }

    $sessionId = $response->sessionId();
    if ($sessionId !== null) {
        echo "Session: {$sessionId}\n";
        echo "Use this ID with resumeSession() to continue later.\n";
    }
} else {
    echo "\nReview failed with exit code: {$response->exitCode}\n";
    echo "Partial output: " . substr($response->text(), 0, 500) . "\n";
}
// @doctest id="02d2"
```

## Comparison with Other Bridges

| Feature               | Claude Code            | Codex          | OpenCode   | Pi                     | Gemini                              |
| --------------------- | ---------------------- | -------------- | ---------- | ---------------------- | ----------------------------------- |
| System prompts        | Yes (replace + append) | No             | No         | Yes (replace + append) | Yes (GEMINI.md file)                |
| Permission modes      | Yes (4 levels)         | No             | No         | No                     | Yes (4 modes)                       |
| Turn limits           | Yes                    | No             | No         | No                     | Yes (via settings)                  |
| Sandbox modes         | No                     | Yes (3 levels) | No         | No                     | Yes (Seatbelt/Docker/Podman/gVisor) |
| Image input           | No                     | Yes            | No         | No                     | No                                  |
| Thinking levels       | No                     | No             | No         | Yes (6 levels)         | No                                  |
| Named agents          | No                     | No             | Yes        | No                     | No                                  |
| File attachments      | No                     | No             | Yes        | Yes (@-prefix)         | No                                  |
| Extensions            | No                     | No             | No         | Yes (TypeScript)       | Yes                                 |
| Skills                | No                     | No             | No         | Yes                    | No                                  |
| Tool control          | No                     | No             | No         | Yes (select/disable)   | Yes (allowlist)                     |
| MCP servers           | No                     | No             | No         | No                     | Yes                                 |
| Policy engine         | No                     | No             | No         | No                     | Yes                                 |
| Session sharing       | No                     | No             | Yes        | No                     | No                                  |
| Session titles        | No                     | No             | Yes        | No                     | No                                  |
| Ephemeral mode        | No                     | No             | No         | Yes                    | No                                  |
| API key override      | No                     | No             | No         | Yes                    | No                                  |
| Token usage           | No                     | Yes (partial)  | Yes (full) | Yes                    | Yes (with cache)                    |
| Cost tracking         | No                     | No             | Yes        | Yes                    | No                                  |
| Multi-provider models | No                     | No             | Yes        | Yes                    | No                                  |
