docs: document codex app-server client helpers

This commit is contained in:
Peter Steinberger
2026-06-04 08:25:40 -04:00
parent 0b5298d24e
commit 0dc1d6a989
4 changed files with 50 additions and 0 deletions

View File

@@ -1,5 +1,9 @@
/**
* Capability helpers for optional Codex app-server control-plane methods.
*/
import { CodexAppServerRpcError } from "./client.js";
/** Known app-server methods used by OpenClaw control surfaces. */
export const CODEX_CONTROL_METHODS = {
account: "account/read",
compact: "thread/compact/start",
@@ -13,8 +17,10 @@ export const CODEX_CONTROL_METHODS = {
} as const;
type CodexControlName = keyof typeof CODEX_CONTROL_METHODS;
/** App-server method name from the known control method map. */
export type CodexControlMethod = (typeof CODEX_CONTROL_METHODS)[CodexControlName];
/** Formats unsupported control calls differently from ordinary RPC failures. */
export function describeControlFailure(error: unknown): string {
if (isUnsupportedControlError(error)) {
return "unsupported by this Codex app-server";

View File

@@ -1,3 +1,6 @@
/**
* Lazy factories for shared and leased Codex app-server clients.
*/
import type { resolveCodexAppServerAuthProfileIdForAgent } from "./auth-bridge.js";
import type { CodexAppServerClient } from "./client.js";
import type { CodexAppServerStartOptions } from "./config.js";
@@ -6,6 +9,7 @@ type AuthProfileOrderConfig = Parameters<
typeof resolveCodexAppServerAuthProfileIdForAgent
>[0]["config"];
/** Factory signature used by Codex attempt startup to acquire a client. */
export type CodexAppServerClientFactory = (
startOptions?: CodexAppServerStartOptions,
authProfileId?: string,
@@ -24,6 +28,7 @@ const loadSharedClientModule = async () => {
return await sharedClientModulePromise;
};
/** Returns the process-shared app-server client for normal attempt reuse. */
export const defaultCodexAppServerClientFactory: CodexAppServerClientFactory = (
startOptions,
authProfileId,
@@ -42,6 +47,7 @@ export const defaultCodexAppServerClientFactory: CodexAppServerClientFactory = (
}),
);
/** Returns a leased shared client so startup can release ownership explicitly. */
export const defaultLeasedCodexAppServerClientFactory: CodexAppServerClientFactory = (
startOptions,
authProfileId,

View File

@@ -1,3 +1,7 @@
/**
* JSON-RPC client for Codex app-server transports, including request/response
* routing, notification fanout, server request handlers, and version checks.
*/
import { createInterface, type Interface as ReadlineInterface } from "node:readline";
import { embeddedAgentLog, OPENCLAW_VERSION } from "openclaw/plugin-sdk/agent-harness-runtime";
import { resolveCodexAppServerRuntimeOptions, type CodexAppServerStartOptions } from "./config.js";
@@ -23,6 +27,7 @@ import {
} from "./transport.js";
import { MIN_CODEX_APP_SERVER_VERSION } from "./version.js";
/** Minimum supported Codex app-server version exported for callers/tests. */
export { MIN_CODEX_APP_SERVER_VERSION } from "./version.js";
const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 1_000_000;
@@ -39,6 +44,7 @@ type PendingRequest = {
cleanup: () => void;
};
/** RPC error wrapper that preserves app-server error code and data. */
export class CodexAppServerRpcError extends Error {
readonly code?: number;
readonly data?: JsonValue;
@@ -77,6 +83,7 @@ function isJsonObject(value: unknown): value is { [key: string]: JsonValue } {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
/** Returns true for errors that mean the app-server transport is closed. */
export function isCodexAppServerConnectionClosedError(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
@@ -91,10 +98,12 @@ type CodexServerRequestHandler = (
request: Required<Pick<RpcRequest, "id" | "method">> & { params?: JsonValue },
) => Promise<JsonValue | undefined> | JsonValue | undefined;
/** Notification handler registered on a Codex app-server client. */
export type CodexServerNotificationHandler = (
notification: CodexServerNotification,
) => Promise<void> | void;
/** Stateful app-server JSON-RPC client over stdio or websocket transport. */
export class CodexAppServerClient {
private readonly child: CodexAppServerTransport;
private readonly lines: ReadlineInterface;
@@ -144,6 +153,7 @@ export class CodexAppServerClient {
);
}
/** Starts a new app-server client using resolved runtime start options. */
static start(options?: Partial<CodexAppServerStartOptions>): CodexAppServerClient {
const defaults = resolveCodexAppServerRuntimeOptions().start;
const startOptions = {
@@ -160,10 +170,12 @@ export class CodexAppServerClient {
return new CodexAppServerClient(createStdioTransport(startOptions));
}
/** Builds a client around a fake transport for tests. */
static fromTransportForTests(child: CodexAppServerTransport): CodexAppServerClient {
return new CodexAppServerClient(child);
}
/** Performs the app-server initialize handshake and validates protocol version. */
async initialize(): Promise<void> {
if (this.initialized) {
return;
@@ -185,6 +197,7 @@ export class CodexAppServerClient {
this.initialized = true;
}
/** Returns the version detected during initialize. */
getServerVersion(): string | undefined {
return this.serverVersion;
}
@@ -269,35 +282,42 @@ export class CodexAppServerClient {
});
}
/** Sends a fire-and-forget JSON-RPC notification to the app-server. */
notify(method: string, params?: JsonValue): void {
this.writeMessage({ method, params });
}
/** Registers a handler for app-server requests sent back to OpenClaw. */
addRequestHandler(handler: CodexServerRequestHandler): () => void {
this.requestHandlers.add(handler);
return () => this.requestHandlers.delete(handler);
}
/** Registers a notification handler and returns its disposer. */
addNotificationHandler(handler: CodexServerNotificationHandler): () => void {
this.notificationHandlers.add(handler);
return () => this.notificationHandlers.delete(handler);
}
/** Installs a lease-count provider used to route unscoped notifications. */
setActiveSharedLeaseCountProviderForUnscopedNotifications(
provider: (() => number | undefined) | undefined,
): void {
this.activeSharedLeaseCountProvider = provider;
}
/** Reads the active shared-client lease count when available. */
getActiveSharedLeaseCountForUnscopedNotifications(): number | undefined {
return this.activeSharedLeaseCountProvider?.();
}
/** Registers a close handler and returns its disposer. */
addCloseHandler(handler: (client: CodexAppServerClient) => void): () => void {
this.closeHandlers.add(handler);
return () => this.closeHandlers.delete(handler);
}
/** Closes the transport without waiting for process/socket shutdown. */
close(): void {
if (!this.markClosed(new Error("codex app-server client is closed"))) {
return;
@@ -305,6 +325,7 @@ export class CodexAppServerClient {
closeCodexAppServerTransport(this.child);
}
/** Closes the transport and waits for shutdown according to transport policy. */
async closeAndWait(options?: {
exitTimeoutMs?: number;
forceKillDelayMs?: number;
@@ -602,6 +623,7 @@ function assertSupportedCodexAppServerVersion(response: CodexInitializeResponse)
return detectedVersion;
}
/** Extracts the Codex version from the app-server initialize user-agent field. */
export function readCodexVersionFromUserAgent(userAgent: string | undefined): string | undefined {
// Codex returns `<originator>/<codex-version> ...`; the originator can be
// OpenClaw, Codex Desktop, or an env override, so only the slash-delimited
@@ -612,6 +634,7 @@ export function readCodexVersionFromUserAgent(userAgent: string | undefined): st
return match?.[1];
}
/** Compares stable Codex app-server versions for protocol floor checks. */
export function compareCodexAppServerVersions(left: string, right: string): number {
const leftVersion = parseVersionForComparison(left);
const rightVersion = parseVersionForComparison(right);
@@ -712,6 +735,7 @@ const CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS = new Set([
"item/permissions/requestApproval",
]);
/** Returns true for app-server approval request methods OpenClaw can answer. */
export function isCodexAppServerApprovalRequest(method: string): boolean {
return CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS.has(method);
}
@@ -726,6 +750,7 @@ function formatExitValue(value: unknown): string {
return "unknown";
}
/** Test-only access to transport close helpers and parser redaction internals. */
export const testing = {
closeCodexAppServerTransport,
closeCodexAppServerTransportAndWait,

View File

@@ -1,3 +1,7 @@
/**
* Computer Use plugin/MCP readiness checks and optional install flow for Codex
* app-server sessions.
*/
import { existsSync } from "node:fs";
import { describeControlFailure } from "./capabilities.js";
import type { CodexAppServerClient } from "./client.js";
@@ -18,6 +22,7 @@ import type {
} from "./protocol.js";
import { requestCodexAppServerJson } from "./request.js";
/** Minimal app-server request function needed by Computer Use setup. */
export type CodexComputerUseRequest = <T = JsonValue | undefined>(
method: string,
params?: unknown,
@@ -34,6 +39,7 @@ type CodexComputerUseStatusReason =
| "check_failed"
| "auto_install_blocked";
/** Readiness status for Codex Computer Use plugin and MCP server wiring. */
export type CodexComputerUseStatus = {
enabled: boolean;
ready: boolean;
@@ -59,6 +65,7 @@ class CodexComputerUseSetupError extends Error {
}
}
/** Inputs for checking, ensuring, or installing Codex Computer Use support. */
export type CodexComputerUseSetupParams = {
pluginConfig?: unknown;
overrides?: Partial<CodexComputerUseConfig>;
@@ -102,6 +109,7 @@ const COMPUTER_USE_MARKETPLACE_NAME_PRIORITY = ["openai-bundled", "openai-curate
const DEFAULT_CODEX_BUNDLED_MARKETPLACE_PATH =
"/Applications/Codex.app/Contents/Resources/plugins/openai-bundled";
/** Reads Computer Use readiness without installing or mutating app-server state. */
export async function readCodexComputerUseStatus(
params: CodexComputerUseSetupParams = {},
): Promise<CodexComputerUseStatus> {
@@ -124,6 +132,10 @@ export async function readCodexComputerUseStatus(
}
}
/**
* Ensures Computer Use is ready when enabled, optionally installing when config
* allows safe auto-install.
*/
export async function ensureCodexComputerUse(
params: CodexComputerUseSetupParams = {},
): Promise<CodexComputerUseStatus> {
@@ -160,6 +172,7 @@ export async function ensureCodexComputerUse(
return status;
}
/** Forces Computer Use plugin installation and returns the ready status. */
export async function installCodexComputerUse(
params: CodexComputerUseSetupParams = {},
): Promise<CodexComputerUseStatus> {