Skip to content

About

Swift port of the Codex SDK

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

swift-codex

Swift SDK for the codex CLI.

swift-codex now uses the Codex JSON-RPC v2 app-server transport exclusively. The SDK starts codex app-server --listen stdio://, speaks JSON-RPC over stdio, and exposes both a high-level Codex API and a low-level CodexRPCClient with typed protocol models generated from the vendored upstream schema.

This repository still exists as a Swift port of the OpenAI Codex SDK work in openai/codex, with upstream attribution and sync notes recorded in NOTICE, LICENSE, and UPSTREAM.md.

Starting with release 0.130.0, swift-codex version numbers follow the shipped upstream openai/codex SDK release number when an upstream sync is released.

Status

Current implementation includes:

  • async Codex, CodexThread, and CodexTurnHandle
  • low-level CodexRPCClient
  • typed generated protocol models such as Thread, Turn, ThreadItem, ModelListResponse, WorkspaceMessage, and CodexNotificationPayload
  • thread start, resume, fork, archive, unarchive, rename, compact, list, and read
  • thread instruction source metadata, fork/subagent ancestry, multi-cwd list filtering, and typed guardian auto-review payloads from the latest v2 schema
  • plugin list retrieval with typed marketplace metadata
  • account rate-limit, token-usage, and workspace-message reads plus external-agent import-history reads, thread attachment management, and skills extra-root updates through the low-level RPC client
  • turn start, steer, interrupt, buffered run, and streamed notifications
  • friendly sandbox presets for thread and turn APIs
  • typed goal, model-verification, process, remote-control, moderation-metadata, and guardian-warning notifications from the latest upstream registry
  • typed guardian approval actions and collaboration/authentication enum values from the latest reviewed schema
  • typed approval handling for command and file-change requests
  • structured input items with text, remote images, local images, skills, and mentions
  • transport launch overrides for explicit process cwd and full argv replacement
  • typed union fallback with rawJSON, additionalFields, and .unknown(JSONValue) support
  • parity-focused tests using swift-testing

Current scope does not include:

  • Windows support
  • a Swift MCP dependency
  • Node/npm-specific binary discovery

This is still a WIP SDK. Breaking changes are expected while the JSON-RPC surface settles.

Upstream Basis

  • Upstream repository: openai/codex
  • Vendored upstream checkout: vendor/openai-codex
  • Vendored upstream commit: a956835d020762cb2b570053af06f643a11c0ecc (rust-v0.160.0)
  • Primary reviewed upstream basis for the current transport and schema:
    • sdk/python/src/openai_codex
    • codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json

The rust-v0.160.0 sync adds generated flexUnavailable and tooManyDenials error cases and the promax plan type. This stable release's schema omits the earlier plugin extension definitions, so the generated plugin extension types and PluginSummary.extensions property are no longer present. Unknown plugin fields remain available through PluginSummary.additionalFields.

See UPSTREAM.md for the exact reviewed files, the raw app-server schema parity target used for this sync, and the remaining schema-only request surfaces not wrapped by the Swift convenience API yet.

Requirements

  • Swift 6.2
  • Installed codex CLI available on PATH, or an explicit binary path in CodexConfig

Example installation:

brew install --cask codex

Installation

Add the package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/ainame/swift-codex.git", from: "0.137.0")
]

Then depend on the Codex product:

.target(
    name: "MyTarget",
    dependencies: [
        .product(name: "Codex", package: "swift-codex"),
    ]
)

If you want to pass a custom swift-log logger, also add the Logging product from swift-log in your target dependencies:

.target(
    name: "MyTarget",
    dependencies: [
        .product(name: "Codex", package: "swift-codex"),
        .product(name: "Logging", package: "swift-log"),
    ]
)

Quickstart

import Codex

let codex = try await Codex(config: .init())
let thread = try await codex.startThread(options: .init(
    model: "gpt-5-codex",
    sandboxPreset: .workspaceWrite
))

let result = try await thread.run(
    "Diagnose the failing test and propose a fix.",
    options: .init(summary: .concise)
)

print(result.finalResponse ?? "")
print(result.items.count)

await codex.close()

Custom logging:

import Codex
import Logging

let logger = Logger(label: "com.example.my-app.codex")
let codex = try await Codex(config: .init(), logger: logger)

Continue on the same thread:

let next = try await thread.run("Implement the fix.")

Resume or fork an existing thread:

let resumed = try await codex.resumeThread(id: "thread_123")
let forked = try await codex.forkThread(id: "thread_123")

Streaming

Use turn(...) plus stream() for incremental notifications:

let handle = try await thread.turn(
    [.text("Inspect the repository and stream progress.")],
    options: .init(summary: .concise)
)

for try await notification in try await handle.stream() {
    switch notification.payload {
    case .itemCompleted(let payload):
        print(payload.item)
    case .turnCompleted(let payload):
        print(payload.turn.status)
    default:
        break
    }
}

Only one active turn consumer is supported per CodexRPCClient/Codex instance at a time.

Structured Input

let input: [InputItem] = [
    .text("Describe these files."),
    .localImage(path: "/tmp/ui.png"),
    .image(url: "https://example.test/diagram.png"),
    .skill(name: "checks", path: "/tmp/checks"),
    .mention(name: "repo", path: "/tmp/repo"),
]

let result = try await thread.run(input)

Configuration

Client-wide configuration:

let config = CodexConfig(
    codexPathOverride: "/opt/homebrew/bin/codex",
    baseURL: "https://api.example.test",
    apiKey: "test-key",
    config: [
        "approval_policy": .string("never"),
        "sandbox_workspace_write": .object([
            "network_access": .bool(true),
        ]),
    ],
    environment: [
        "PATH": "/opt/homebrew/bin:/usr/bin:/bin",
    ],
    commandApprovalHandler: { request in
        print(request.command ?? "")
        return .approve
    },
    fileChangeApprovalHandler: { request in
        print(request.grantRoot ?? "")
        return .approve
    }
)

let codex = try await Codex(config: config)

baseURL is serialized into the process config as openai_base_url. Thread-level config overrides are sent as JSON-RPC request params.

Thread options:

let thread = try await codex.startThread(options: .init(
    approvalPolicy: .onRequest,
    cwd: "/path/to/repo",
    model: "gpt-5-codex",
    sandboxPreset: .workspaceWrite,
    sessionStartSource: .startup
))

Turn options with an output schema:

let schema: JSONObject = [
    "type": .string("object"),
    "properties": .object([
        "summary": .object(["type": .string("string")]),
        "status": .object([
            "type": .string("string"),
            "enum": .array([.string("ok"), .string("action_required")]),
        ]),
    ]),
    "required": .array([.string("summary"), .string("status")]),
    "additionalProperties": .bool(false),
]

let result = try await thread.run(
    "Summarize the repository status.",
    options: .init(
        effort: .high,
        outputSchema: schema,
        summary: .concise
    )
)

Low-Level RPC Client

CodexRPCClient exposes the raw JSON-RPC method surface with typed request and response models:

let client = CodexRPCClient(config: .init())
let initialize = try await client.initialize()
print(initialize.serverInfo?.name ?? "")

let started = try await client.threadStart(options: .init(model: "gpt-5-codex"))
let listed = try await client.threadList()
let read = try await client.threadRead(threadID: started.thread.id, includeTurns: true)
let models = try await client.modelList()

print(listed.data.count)
print(read.thread.id)
print(models.data.map(\.id))

await client.close()

Codex and CodexRPCClient both accept a swift-log Logger. If you omit it, they use Codex.defaultLogger() with the label swift-codex.

swift-codex does not call LoggingSystem.bootstrap(...) for you. Applications and test executables should bootstrap their preferred swift-log backend at process startup when they want logs routed to a specific sink.

Known inbound approval requests are modeled as:

  • ServerRequest.commandApproval
  • ServerRequest.fileChangeApproval

Unknown request methods fall back to:

  • ServerRequest.unknown(method:params:)

Generated Models

Generated protocol models live in:

The generator writes one Swift file per generated type plus CodexNotificationPayload.swift so the model layer stays editor-friendly.

Regenerate them with:

python3 Scripts/generate_app_server_v2.py

Forward-compatibility hooks:

  • rawJSON on generated models and enums
  • additionalFields on object models
  • .unknown(JSONValue) on generated unions

Examples

A standalone example package lives in Examples:

cd Examples
swift build
swift run basic-example

The example demonstrates startup, approvals, thread list/read, and streamed notifications using the current RPC API.

Testing

Run the package tests:

swift test

The suite uses swift-testing and a stub codex binary that simulates the JSON-RPC app-server protocol.

Repository Layout

About

Swift port of the Codex SDK

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages