Swift client primitives for the Codex App Server JSONL protocol.
The package keeps the transport boundary narrow so application code can decide whether Codex runs as a local subprocess, a WebSocket service, or another implementation later.
flowchart LR
App["Swift app"] --> Client["CodexAppServerClient"]
Client --> Transport["CodexAppServerLineTransport"]
Transport --> Server["codex app-server"]
Server --> Notifications["thread / turn / item notifications"]
Notifications --> Client
initializehandshake- thread lifecycle: start, resume, fork, list, read, archive, unarchive, unsubscribe, name, compact, rollback
- turn lifecycle: start, steer, interrupt
- review start
- model list
- notification forwarding
- server-initiated request handling for approvals and client-side tools
- local
codex app-serversubprocess transport over stdio - WebSocket transport for
ws://IP:PORT
The protocol surface follows the public Codex App Server documentation:
import CodexAppServer
import Foundation
@MainActor
func run() async throws {
let transport = try CodexAppServerProcessTransport()
let client = CodexAppServerClient(transport: transport) { notification in
print(notification.method)
} onServerRequest: { request in
switch request.method {
case "item/commandExecution/requestApproval":
return .commandApproval(.accept)
case "item/fileChange/requestApproval":
return .fileChangeApproval(.decline)
default:
return .failure(
CodexAppServerError(
code: -32601,
message: "Unsupported server request"
)
)
}
}
_ = try await client.initialize(
clientInfo: .init(
name: "bioid_board",
title: "Bioid Board",
version: "0.1.0"
)
)
let thread = try await client.startThread(
configuration: .init(cwd: URL(fileURLWithPath: "/Users/me/project", isDirectory: true))
)
_ = try await client.startTurn(
threadID: thread.id,
text: "Inspect this directory and summarize the next step.",
cwd: URL(fileURLWithPath: "/Users/me/project", isDirectory: true)
)
try await client.close()
}