Skip to main content

Overview

This example demonstrates real-time streaming output from the Codex CLI. Events are parsed and displayed as they arrive, providing live feedback on the agent’s progress.

Example

<?php
require 'examples/boot.php';

use Cognesy\Auxiliary\Agents\OpenAICodex\Application\Builder\CodexCommandBuilder;
use Cognesy\Auxiliary\Agents\OpenAICodex\Application\Dto\CodexRequest;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\StreamEvent\StreamEvent;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\StreamEvent\ThreadStartedEvent;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\StreamEvent\TurnStartedEvent;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\StreamEvent\TurnCompletedEvent;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\StreamEvent\ItemStartedEvent;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\StreamEvent\ItemCompletedEvent;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\Item\AgentMessage;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Dto\Item\CommandExecution;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Enum\OutputFormat;
use Cognesy\Auxiliary\Agents\OpenAICodex\Domain\Enum\SandboxMode;
use Cognesy\Auxiliary\Agents\Common\Execution\SandboxCommandExecutor;

// Create a request - must specify working directory for sandbox access
$request = new CodexRequest(
    prompt: 'List the files in the current directory and explain what you see.',
    outputFormat: OutputFormat::Json,
    sandboxMode: SandboxMode::ReadOnly,
    workingDirectory: getcwd(),
);

print("Streaming Codex CLI output...\n");
print("Prompt: {$request->prompt()}\n\n");

// Build command
$builder = new CodexCommandBuilder();
$spec = $builder->buildExec($request);

// Execute with streaming callback
$executor = SandboxCommandExecutor::forCodex();

$execResult = $executor->executeStreaming($spec, function (string $type, string $chunk) {
    if ($type !== 'out') {
        return;
    }

    // Each line is a JSON object
    $lines = preg_split('/\r\n|\r|\n/', $chunk);
    foreach ($lines as $line) {
        $trimmed = trim($line);
        if ($trimmed === '') {
            continue;
        }

        $decoded = json_decode($trimmed, true);
        if (!is_array($decoded)) {
            continue;
        }

        $event = StreamEvent::fromArray($decoded);

        // Handle different event types
        match (true) {
            $event instanceof ThreadStartedEvent => print("[THREAD] Started: {$event->threadId}\n"),
            $event instanceof TurnStartedEvent => print("[TURN] Started\n"),
            $event instanceof TurnCompletedEvent => handleTurnCompleted($event),
            $event instanceof ItemStartedEvent => handleItemStarted($event),
            $event instanceof ItemCompletedEvent => handleItemCompleted($event),
            default => print("[{$event->type()}]\n"),
        };
    }
});

function handleTurnCompleted(TurnCompletedEvent $event): void {
    print("[TURN] Completed");
    if ($event->usage) {
        print(" (input: {$event->usage->inputTokens}, output: {$event->usage->outputTokens})");
    }
    print("\n");
}

function handleItemStarted(ItemStartedEvent $event): void {
    $item = $event->item;
    print("[ITEM] Started: {$item->itemType()} ({$item->id})\n");

    if ($item instanceof CommandExecution) {
        print("       Command: {$item->command}\n");
    }
}

function handleItemCompleted(ItemCompletedEvent $event): void {
    $item = $event->item;
    print("[ITEM] Completed: {$item->itemType()} ({$item->id})\n");

    if ($item instanceof AgentMessage) {
        print("       Message: " . substr($item->text, 0, 100) . "...\n");
    } elseif ($item instanceof CommandExecution && $item->output !== null) {
        $preview = substr($item->output, 0, 100);
        print("       Output: {$preview}...\n");
    }
}

print("\nFinal exit code: {$execResult->exitCode()}\n");

assert($execResult->exitCode() === 0, 'Command should execute successfully');
?>