neosoftware/openai-codex-sdk

PHP 8.2 SDK for OpenAI Codex CLI agent

Maintainers

Package info

github.com/NEOSoftWare/openai-codex-sdk

pkg:composer/neosoftware/openai-codex-sdk

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.125.0 2026-06-05 11:11 UTC

This package is auto-updated.

Last update: 2026-06-05 11:14:35 UTC


README

PHP 8.2 SDK for working with the OpenAI Codex CLI agent. An exact port of the @openai/codex-sdk package (TypeScript).

Requirements

  • PHP 8.2+
  • Composer

Installation

composer require neosoftware/openai-codex-sdk

Codex Binary

The SDK runs codex (Codex CLI) as a subprocess. Specify its path in one of the following ways:

Via CodexOptions — explicitly for a specific instance:

new CodexOptions(codexPathOverride: '/usr/local/bin/codex')

Installation into vendor/bin — convenient for containers and servers where Codex CLI is not installed globally. The command downloads the binary for your platform from npm and adds a vendor/bin/codex symlink:

vendor/bin/install-codex

allow-plugins permissions are not required.

Quick Start

<?php
require 'vendor/autoload.php';

use OpenAI\Codex\Codex;
use OpenAI\Codex\Types\Options\CodexOptions;
use OpenAI\Codex\Types\Options\ThreadOptions;

$codex = new Codex(new CodexOptions(
    apiKey: $_ENV['OPENAI_API_KEY'],
));

$thread = $codex->startThread(new ThreadOptions(
    model: 'codex-mini-latest',
    skipGitRepoCheck: true,
));

$turn = $thread->run('Explain what the array_chunk function does in PHP.');

echo $turn->finalResponse;

Initialization

new Codex(CodexOptions $options = null)

Creates the main SDK object.

$codex = new Codex(new CodexOptions(
    apiKey:            'sk-...',             // OpenAI API key (or via the OPENAI_API_KEY env variable)
    baseUrl:           'https://...',        // custom API endpoint (optional)
    codexPathOverride: '/usr/local/bin/codex', // custom binary (optional)
    config: [                                // additional --config flags
        'disable_response_storage' => true,
    ],
    env: [                                   // if set, process env is NOT inherited
        'OPENAI_API_KEY' => 'sk-...',
        'HOME' => '/home/user',
    ],
));

CodexOptions

Property Type Default Description
apiKey ?string null OpenAI API key. Passed to the binary as CODEX_API_KEY
baseUrl ?string null Custom API URL (--config openai_base_url=...)
codexPathOverride ?string null Absolute path to your custom codex binary
config ?array null Arbitrary key => value pairs for --config flags
env ?array null Environment variables for the subprocess. If null, the subprocess inherits the entire PHP process environment. If explicitly set, the subprocess receives only the specified variables

Threads

Codex works within threads — each thread stores the conversation history.

// New thread
$thread = $codex->startThread(new ThreadOptions(...));

// Resume an existing thread by ID
$thread = $codex->resumeThread('019dc15c-a8aa-...', new ThreadOptions(...));

// Get the ID after the first request
echo $thread->getId(); // string|null

ThreadOptions

Property Type Default Description
model ?string null Model name (codex-mini-latest, gpt-4o, …)
sandboxMode ?SandboxMode null Sandbox mode for running commands
workingDirectory ?string null Agent working directory (--cd)
skipGitRepoCheck bool false Do not require a Git repository
modelReasoningEffort ?ModelReasoningEffort null Depth of the model's “reasoning”
networkAccessEnabled ?bool null Network access from the sandbox
webSearchMode ?WebSearchMode null Web search mode
webSearchEnabled ?bool null Enable/disable web search (legacy)
approvalPolicy ?ApprovalMode null Action approval policy
additionalDirectories ?string[] null Additional directories (--add-dir)

Running Requests

run() — blocking

Runs a request, waits for the full response, and returns a Turn.

$turn = $thread->run('Find a bug in src/Payment.php');

echo $turn->finalResponse;           // final text response from the agent
echo $turn->usage->outputTokens;     // tokens consumed
foreach ($turn->items as $item) {   // all completed items
    // AgentMessageItem | CommandExecutionItem | FileChangeItem | ...
}

Turn

Property Type Description
finalResponse string The agent's final message (or JSON when using outputSchema)
items object[] List of all completed ThreadItem objects
usage ?Usage Token usage information

runStreamed() — streaming

Returns StreamedTurn immediately. Events are read as they arrive through a generator.

$streamed = $thread->runStreamed('Check all tests and fix the errors');

foreach ($streamed->events() as $event) {
    match (true) {
        $event instanceof ThreadStartedEvent  => print("Thread: {$event->threadId}\n"),
        $event instanceof TurnCompletedEvent  => print("Tokens: {$event->usage?->outputTokens}\n"),
        $event instanceof ItemStartedEvent    => print("Started: {$event->item->type}\n"),
        $event instanceof ItemCompletedEvent  => match (true) {
            $event->item instanceof AgentMessageItem     => print($event->item->text . "\n"),
            $event->item instanceof CommandExecutionItem => print("$ {$event->item->command}\n"),
            $event->item instanceof FileChangeItem       => print("Files changed: " . count($event->item->changes) . "\n"),
            default => null,
        },
        default => null,
    };
}

StreamedTurn::events()Generator<ThreadEvent>

A generator that sequentially yields events:

Event Fields Description
ThreadStartedEvent threadId Thread created; ID is now available
TurnStartedEvent Request processing started
ItemStartedEvent item Item started running
ItemUpdatedEvent item Intermediate item update (command output, etc.)
ItemCompletedEvent item Item completed
TurnCompletedEvent usage Request completed; token statistics
TurnFailedEvent error The agent returned an error (throws TurnFailedException)
ThreadErrorEvent message Non-fatal error

Input Types

The run() and runStreamed() methods accept either a string or an array of objects:

// Simple string
$thread->run('Explain this code');

// Text + images
use OpenAI\Codex\Types\Input\TextInput;
use OpenAI\Codex\Types\Input\LocalImageInput;

$thread->run([
    new TextInput('What is wrong with this screenshot?'),
    new LocalImageInput('/path/to/screenshot.png'),
    new TextInput('Pay attention to the upper-right corner.'),
]);

Multiple TextInput instances are concatenated with \n\n. Images are passed as --image flags.

ThreadItems

Each completed agent step is an item of one of the following types:

AgentMessageItem

Agent text response.

$item->id;    // string
$item->text;  // string — agent message

ReasoningItem

Internal model “reasoning” (if enabled).

$item->id;    // string
$item->text;  // string

CommandExecutionItem

Executed shell command.

$item->id;               // string
$item->command;          // string — command, for example "/bin/zsh -c 'php artisan test'"
$item->aggregatedOutput; // string — full command output
$item->exitCode;         // ?int — return code
$item->status;           // CommandExecutionStatus::InProgress|Completed|Failed

FileChangeItem

File changes in the working directory.

$item->id;      // string
$item->status;  // PatchApplyStatus::Completed|Failed
foreach ($item->changes as $change) {
    $change->path;        // string — file path
    $change->kind;        // PatchChangeKind::Add|Delete|Update
    $change->applyStatus; // PatchApplyStatus::Completed|Failed
}

McpToolCallItem

Tool call via the Model Context Protocol.

$item->id;        // string
$item->server;    // string — MCP server name
$item->tool;      // string — tool name
$item->arguments; // mixed — arguments
$item->result;    // mixed — result (after completion)
$item->error;     // ?string — error (if any)
$item->status;    // McpToolCallStatus::InProgress|Completed|Failed

WebSearchItem

Web search request.

$item->id;     // string
$item->query;  // string — search query

TodoListItem

Agent task list.

$item->id;  // string
foreach ($item->items as $todo) {
    $todo->id;          // string
    $todo->description; // string
    $todo->completed;   // bool
}

ErrorItem

Non-fatal error during execution.

$item->id;      // string
$item->message; // string

TurnOptions

Options for a specific request (passed as the second argument to run() / runStreamed()):

use OpenAI\Codex\Types\Options\TurnOptions;

$turn = $thread->run('Task', new TurnOptions(
    outputSchema:   [...],   // JSON Schema for structured output
    timeoutSeconds: 120,     // response wait timeout in seconds
));
Property Type Description
outputSchema ?array JSON Schema — the agent will return JSON compatible with the schema
timeoutSeconds ?int Maximum response wait time

Structured Output

The OpenAI API requires "additionalProperties": false in the schema:

$turn = $thread->run(
    'Return project data',
    new TurnOptions(
        outputSchema: [
            'type'                 => 'object',
            'additionalProperties' => false,
            'properties' => [
                'name'    => ['type' => 'string'],
                'version' => ['type' => 'string'],
                'license' => ['type' => 'string'],
            ],
            'required' => ['name', 'version'],
        ],
    ),
);

$data = json_decode($turn->finalResponse, true);
echo $data['name'];

Enumerations (Enums)

SandboxMode

use OpenAI\Codex\Types\Enums\SandboxMode;

SandboxMode::ReadOnly        // file system read-only
SandboxMode::WorkspaceWrite  // read + write in the working directory
SandboxMode::DangerFullAccess // full access (no restrictions)

ModelReasoningEffort

use OpenAI\Codex\Types\Enums\ModelReasoningEffort;

ModelReasoningEffort::Minimal
ModelReasoningEffort::Low
ModelReasoningEffort::Medium
ModelReasoningEffort::High
ModelReasoningEffort::Xhigh

WebSearchMode

use OpenAI\Codex\Types\Enums\WebSearchMode;

WebSearchMode::Disabled  // web search disabled
WebSearchMode::Cached    // cached results only
WebSearchMode::Live      // live search

ApprovalMode

use OpenAI\Codex\Types\Enums\ApprovalMode;

ApprovalMode::Never      // run everything without confirmation
ApprovalMode::OnRequest  // ask the user for permission
ApprovalMode::OnFailure  // ask only on failure
ApprovalMode::Untrusted  // strict mode

Error Handling

use OpenAI\Codex\Exceptions\CodexException;
use OpenAI\Codex\Exceptions\SpawnException;
use OpenAI\Codex\Exceptions\TurnFailedException;
use OpenAI\Codex\Exceptions\ParseException;

try {
    $turn = $thread->run('...');
} catch (TurnFailedException $e) {
    // The agent returned an error (API error, invalid schema, etc.)
    echo "Agent error: " . $e->getMessage();
} catch (SpawnException $e) {
    // Startup error or subprocess timeout
    echo "Process error: " . $e->getMessage();
} catch (ParseException $e) {
    // The agent returned invalid JSONL
    echo "Parse error: " . $e->getMessage();
} catch (CodexException $e) {
    // Any other SDK error
    echo "SDK error: " . $e->getMessage();
}
Exception When
TurnFailedException The agent returned turn.failed (API error, invalid schema, …)
SpawnException Binary not found, failed to start, hung, or exited with a non-zero code
ParseException Binary returned invalid JSON in the JSONL stream
CodexException Base class for all SDK exceptions

Complete Example

<?php
require 'vendor/autoload.php';

use OpenAI\Codex\Codex;
use OpenAI\Codex\Types\Enums\ApprovalMode;
use OpenAI\Codex\Types\Enums\ModelReasoningEffort;
use OpenAI\Codex\Types\Enums\SandboxMode;
use OpenAI\Codex\Types\Events\ItemCompletedEvent;
use OpenAI\Codex\Types\Events\ThreadStartedEvent;
use OpenAI\Codex\Types\Events\TurnCompletedEvent;
use OpenAI\Codex\Types\Input\LocalImageInput;
use OpenAI\Codex\Types\Input\TextInput;
use OpenAI\Codex\Types\Items\AgentMessageItem;
use OpenAI\Codex\Types\Items\CommandExecutionItem;
use OpenAI\Codex\Types\Items\FileChangeItem;
use OpenAI\Codex\Types\Options\CodexOptions;
use OpenAI\Codex\Types\Options\ThreadOptions;
use OpenAI\Codex\Types\Options\TurnOptions;

$codex = new Codex(new CodexOptions(
    apiKey: $_ENV['OPENAI_API_KEY'],
));

$thread = $codex->startThread(new ThreadOptions(
    model:                 'codex-mini-latest',
    sandboxMode:           SandboxMode::WorkspaceWrite,
    workingDirectory:      '/path/to/project',
    modelReasoningEffort:  ModelReasoningEffort::High,
    approvalPolicy:        ApprovalMode::Never,
));

// Streaming with an image
$streamed = $thread->runStreamed(
    [
        new TextInput('What is wrong with this code?'),
        new LocalImageInput('/tmp/screenshot.png'),
    ],
    new TurnOptions(timeoutSeconds: 180),
);

foreach ($streamed->events() as $event) {
    match (true) {
        $event instanceof ThreadStartedEvent  => printf("Thread: %s\n", $event->threadId),
        $event instanceof TurnCompletedEvent  => printf("Tokens: %d\n", $event->usage?->outputTokens ?? 0),
        $event instanceof ItemCompletedEvent  => match (true) {
            $event->item instanceof AgentMessageItem     => printf("%s\n", $event->item->text),
            $event->item instanceof CommandExecutionItem => printf("$ %s\n", $event->item->command),
            $event->item instanceof FileChangeItem       => printf("Files: %d\n", count($event->item->changes)),
            default                                      => null,
        },
        default => null,
    };
}

// Continue the conversation in the same thread
$turn = $thread->run('Now write a test for the fixed code.');
echo $turn->finalResponse;

// Resume the thread in a new session
$resumed = $codex->resumeThread($thread->getId());
$turn = $resumed->run('What did you do last time?');
echo $turn->finalResponse;

License

MIT