mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document codex app-server client helpers
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user