mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:40:44 +00:00
fix: sync Codex app-server protocol (#77578)
* fix: sync codex app-server protocol * docs: add codex protocol changelog * fix: refresh codex protocol schemas
This commit is contained in:
committed by
GitHub
parent
0677a4f8b3
commit
d522a18971
@@ -1,25 +1,16 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolveCodexAppServerProtocolSource } from "./lib/codex-app-server-protocol-source.js";
|
||||
import {
|
||||
generateExperimentalCodexAppServerProtocolSource,
|
||||
normalizeGeneratedTypeScript,
|
||||
selectedCodexAppServerJsonSchemas,
|
||||
} from "./lib/codex-app-server-protocol-source.js";
|
||||
|
||||
const { sourceRoot: sourceSchemaRoot } = await resolveCodexAppServerProtocolSource(process.cwd());
|
||||
const schemaRoot = path.join(sourceSchemaRoot, "typescript");
|
||||
const generatedRoot = path.resolve(
|
||||
process.cwd(),
|
||||
"extensions/codex/src/app-server/protocol-generated",
|
||||
);
|
||||
|
||||
const selectedJsonSchemas = [
|
||||
"DynamicToolCallParams.json",
|
||||
"v2/ErrorNotification.json",
|
||||
"v2/GetAccountResponse.json",
|
||||
"v2/ModelListResponse.json",
|
||||
"v2/ThreadResumeResponse.json",
|
||||
"v2/ThreadStartResponse.json",
|
||||
"v2/TurnCompletedNotification.json",
|
||||
"v2/TurnStartResponse.json",
|
||||
] as const;
|
||||
|
||||
const checks: Array<{ file: string; snippets: string[] }> = [
|
||||
{
|
||||
file: "ServerRequest.ts",
|
||||
@@ -33,10 +24,10 @@ const checks: Array<{ file: string; snippets: string[] }> = [
|
||||
{
|
||||
file: "v2/ThreadItem.ts",
|
||||
snippets: [
|
||||
'"type": "contextCompaction"',
|
||||
'"type": "dynamicToolCall"',
|
||||
'"type": "commandExecution"',
|
||||
'"type": "mcpToolCall"',
|
||||
'type: "contextCompaction"',
|
||||
'type: "dynamicToolCall"',
|
||||
'type: "commandExecution"',
|
||||
'type: "mcpToolCall"',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -49,19 +40,23 @@ const checks: Array<{ file: string; snippets: string[] }> = [
|
||||
},
|
||||
{
|
||||
file: "v2/Account.ts",
|
||||
snippets: ['"type": "apiKey"', '"type": "chatgpt"', '"type": "amazonBedrock"'],
|
||||
snippets: ['type: "apiKey"', 'type: "chatgpt"', 'type: "amazonBedrock"'],
|
||||
},
|
||||
{
|
||||
file: "v2/ThreadStartParams.ts",
|
||||
snippets: [
|
||||
"permissionProfile?: PermissionProfile | null",
|
||||
"permissions?: PermissionProfileSelectionParams | null",
|
||||
"dynamicTools?: Array<DynamicToolSpec> | null",
|
||||
"experimentalRawEvents: boolean",
|
||||
"persistExtendedHistory: boolean",
|
||||
],
|
||||
},
|
||||
{
|
||||
file: "v2/TurnStartParams.ts",
|
||||
snippets: ["permissionProfile?: PermissionProfile | null", "serviceTier?: ServiceTier | null"],
|
||||
snippets: [
|
||||
"permissions?: PermissionProfileSelectionParams | null",
|
||||
"serviceTier?: ServiceTier | null",
|
||||
],
|
||||
},
|
||||
{
|
||||
file: "ReviewDecision.ts",
|
||||
@@ -78,23 +73,28 @@ const checks: Array<{ file: string; snippets: string[] }> = [
|
||||
];
|
||||
|
||||
const failures: string[] = [];
|
||||
const source = await generateExperimentalCodexAppServerProtocolSource();
|
||||
|
||||
await compareGeneratedProtocolMirror();
|
||||
try {
|
||||
await compareGeneratedProtocolMirror(source.typescriptRoot, source.jsonRoot);
|
||||
|
||||
for (const check of checks) {
|
||||
const filePath = path.join(schemaRoot, check.file);
|
||||
let text: string;
|
||||
try {
|
||||
text = await fs.readFile(filePath, "utf8");
|
||||
} catch (error) {
|
||||
failures.push(`${check.file}: missing (${String(error)})`);
|
||||
continue;
|
||||
}
|
||||
for (const snippet of check.snippets) {
|
||||
if (!text.includes(snippet)) {
|
||||
failures.push(`${check.file}: missing ${snippet}`);
|
||||
for (const check of checks) {
|
||||
const filePath = path.join(source.typescriptRoot, check.file);
|
||||
let text: string;
|
||||
try {
|
||||
text = await fs.readFile(filePath, "utf8");
|
||||
} catch (error) {
|
||||
failures.push(`${check.file}: missing (${String(error)})`);
|
||||
continue;
|
||||
}
|
||||
for (const snippet of check.snippets) {
|
||||
if (!text.includes(snippet)) {
|
||||
failures.push(`${check.file}: missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await source.cleanup();
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
@@ -103,17 +103,19 @@ if (failures.length > 0) {
|
||||
console.error(`- ${failure}`);
|
||||
}
|
||||
console.error(
|
||||
`Run \`pnpm codex-app-server:protocol:sync\` after refreshing the Codex checkout at ${path.resolve(sourceSchemaRoot, "../../..")}.`,
|
||||
`Run \`pnpm codex-app-server:protocol:sync\` after refreshing the Codex checkout at ${source.codexRepo}.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Codex app-server generated protocol matches OpenClaw bridge assumptions: ${sourceSchemaRoot}`,
|
||||
`Codex app-server generated protocol matches OpenClaw bridge assumptions: ${source.codexRepo}`,
|
||||
);
|
||||
|
||||
async function compareGeneratedProtocolMirror(): Promise<void> {
|
||||
const sourceTsRoot = path.join(sourceSchemaRoot, "typescript");
|
||||
async function compareGeneratedProtocolMirror(
|
||||
sourceTsRoot: string,
|
||||
sourceJsonRoot: string,
|
||||
): Promise<void> {
|
||||
const targetTsRoot = path.join(generatedRoot, "typescript");
|
||||
const sourceFiles = await listFiles(sourceTsRoot, ".ts");
|
||||
const targetFiles = await listFiles(targetTsRoot, ".ts");
|
||||
@@ -139,8 +141,8 @@ async function compareGeneratedProtocolMirror(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
for (const schema of selectedJsonSchemas) {
|
||||
const sourcePath = path.join(sourceSchemaRoot, "json", schema);
|
||||
for (const schema of selectedCodexAppServerJsonSchemas) {
|
||||
const sourcePath = path.join(sourceJsonRoot, schema);
|
||||
const targetPath = path.join(generatedRoot, "json", schema);
|
||||
let source: string;
|
||||
let target: string;
|
||||
@@ -180,10 +182,3 @@ async function listFiles(root: string, suffix: string): Promise<string[]> {
|
||||
await visit(root);
|
||||
return files.toSorted();
|
||||
}
|
||||
|
||||
function normalizeGeneratedTypeScript(text: string): string {
|
||||
return text
|
||||
.replace(/(from\s+["'])(\.{1,2}\/[^"']+?)(\.js)?(["'])/g, "$1$2.js$4")
|
||||
.replace('export * as v2 from "./v2.js";', 'export * as v2 from "./v2/index.js";')
|
||||
.replaceAll("| null | null", "| null");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const PROTOCOL_SCHEMA_RELATIVE_PATH = "codex-rs/app-server-protocol/schema";
|
||||
|
||||
export const selectedCodexAppServerJsonSchemas = [
|
||||
"DynamicToolCallParams.json",
|
||||
"v2/ErrorNotification.json",
|
||||
"v2/GetAccountResponse.json",
|
||||
"v2/ModelListResponse.json",
|
||||
"v2/ThreadResumeResponse.json",
|
||||
"v2/ThreadStartResponse.json",
|
||||
"v2/TurnCompletedNotification.json",
|
||||
"v2/TurnStartResponse.json",
|
||||
] as const;
|
||||
|
||||
export type GeneratedCodexAppServerProtocolSource = {
|
||||
root: string;
|
||||
codexRepo: string;
|
||||
typescriptRoot: string;
|
||||
jsonRoot: string;
|
||||
cleanup: () => Promise<void>;
|
||||
};
|
||||
|
||||
export async function resolveCodexAppServerProtocolSource(repoRoot: string): Promise<{
|
||||
codexRepo: string;
|
||||
sourceRoot: string;
|
||||
@@ -31,6 +51,61 @@ export async function resolveCodexAppServerProtocolSource(repoRoot: string): Pro
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateExperimentalCodexAppServerProtocolSource(
|
||||
repoRoot = process.cwd(),
|
||||
): Promise<GeneratedCodexAppServerProtocolSource> {
|
||||
const { codexRepo } = await resolveCodexAppServerProtocolSource(repoRoot);
|
||||
const root = await fs.mkdtemp(path.join(repoRoot, ".tmp-codex-app-server-protocol-"));
|
||||
const typescriptRoot = path.join(root, "typescript");
|
||||
const jsonRoot = path.join(root, "json");
|
||||
const manifestPath = path.join(codexRepo, "codex-rs/Cargo.toml");
|
||||
const cleanup = async () => {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
};
|
||||
|
||||
try {
|
||||
runCargoProtocolGenerator(codexRepo, [
|
||||
"run",
|
||||
"--manifest-path",
|
||||
manifestPath,
|
||||
"-p",
|
||||
"codex-cli",
|
||||
"--",
|
||||
"app-server",
|
||||
"generate-ts",
|
||||
"--out",
|
||||
typescriptRoot,
|
||||
"--experimental",
|
||||
]);
|
||||
runCargoProtocolGenerator(codexRepo, [
|
||||
"run",
|
||||
"--manifest-path",
|
||||
manifestPath,
|
||||
"-p",
|
||||
"codex-cli",
|
||||
"--",
|
||||
"app-server",
|
||||
"generate-json-schema",
|
||||
"--out",
|
||||
jsonRoot,
|
||||
"--experimental",
|
||||
]);
|
||||
await rewriteTypeScriptImports(typescriptRoot);
|
||||
formatGeneratedTypeScript(repoRoot, typescriptRoot);
|
||||
} catch (error) {
|
||||
await cleanup();
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
root,
|
||||
codexRepo,
|
||||
typescriptRoot,
|
||||
jsonRoot,
|
||||
cleanup,
|
||||
};
|
||||
}
|
||||
|
||||
async function collectCodexRepoCandidates(repoRoot: string): Promise<string[]> {
|
||||
const candidates = [
|
||||
process.env.OPENCLAW_CODEX_REPO,
|
||||
@@ -72,3 +147,52 @@ async function isDirectory(candidate: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function runCargoProtocolGenerator(codexRepo: string, args: string[]): void {
|
||||
const result = spawnSync("cargo", args, {
|
||||
cwd: codexRepo,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`cargo ${args.join(" ")} failed with exit code ${result.status ?? "unknown"}`);
|
||||
}
|
||||
}
|
||||
|
||||
function formatGeneratedTypeScript(repoRoot: string, root: string): void {
|
||||
const result = spawnSync("pnpm", ["exec", "oxfmt", "--write", "--threads=1", root], {
|
||||
cwd: repoRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`pnpm exec oxfmt --write --threads=1 ${root} failed with exit code ${
|
||||
result.status ?? "unknown"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function rewriteTypeScriptImports(root: string): Promise<void> {
|
||||
const entries = await fs.readdir(root, { withFileTypes: true });
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.join(root, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await rewriteTypeScriptImports(fullPath);
|
||||
return;
|
||||
}
|
||||
if (!entry.isFile() || !entry.name.endsWith(".ts")) {
|
||||
return;
|
||||
}
|
||||
const text = await fs.readFile(fullPath, "utf8");
|
||||
await fs.writeFile(fullPath, normalizeGeneratedTypeScript(text));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeGeneratedTypeScript(text: string): string {
|
||||
return text
|
||||
.replace(/(from\s+["'])(\.{1,2}\/[^"']+?)(\.js)?(["'])/g, "$1$2.js$4")
|
||||
.replace('export * as v2 from "./v2.js";', 'export * as v2 from "./v2/index.js";')
|
||||
.replaceAll("| null | null", "| null");
|
||||
}
|
||||
|
||||
@@ -1,58 +1,29 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolveCodexAppServerProtocolSource } from "./lib/codex-app-server-protocol-source.js";
|
||||
import {
|
||||
generateExperimentalCodexAppServerProtocolSource,
|
||||
selectedCodexAppServerJsonSchemas,
|
||||
} from "./lib/codex-app-server-protocol-source.js";
|
||||
|
||||
const { sourceRoot } = await resolveCodexAppServerProtocolSource(process.cwd());
|
||||
const targetRoot = path.resolve(
|
||||
process.cwd(),
|
||||
"extensions/codex/src/app-server/protocol-generated",
|
||||
);
|
||||
|
||||
const selectedJsonSchemas = [
|
||||
"DynamicToolCallParams.json",
|
||||
"v2/ErrorNotification.json",
|
||||
"v2/GetAccountResponse.json",
|
||||
"v2/ModelListResponse.json",
|
||||
"v2/ThreadResumeResponse.json",
|
||||
"v2/ThreadStartResponse.json",
|
||||
"v2/TurnCompletedNotification.json",
|
||||
"v2/TurnStartResponse.json",
|
||||
] as const;
|
||||
const source = await generateExperimentalCodexAppServerProtocolSource();
|
||||
try {
|
||||
await fs.rm(targetRoot, { recursive: true, force: true });
|
||||
await fs.mkdir(targetRoot, { recursive: true });
|
||||
await fs.cp(source.typescriptRoot, path.join(targetRoot, "typescript"), {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
await fs.rm(targetRoot, { recursive: true, force: true });
|
||||
await fs.mkdir(targetRoot, { recursive: true });
|
||||
await fs.cp(path.join(sourceRoot, "typescript"), path.join(targetRoot, "typescript"), {
|
||||
recursive: true,
|
||||
});
|
||||
await rewriteTypeScriptImports(path.join(targetRoot, "typescript"));
|
||||
|
||||
for (const schema of selectedJsonSchemas) {
|
||||
await fs.mkdir(path.dirname(path.join(targetRoot, "json", schema)), { recursive: true });
|
||||
await fs.copyFile(path.join(sourceRoot, "json", schema), path.join(targetRoot, "json", schema));
|
||||
for (const schema of selectedCodexAppServerJsonSchemas) {
|
||||
await fs.mkdir(path.dirname(path.join(targetRoot, "json", schema)), { recursive: true });
|
||||
await fs.copyFile(path.join(source.jsonRoot, schema), path.join(targetRoot, "json", schema));
|
||||
}
|
||||
} finally {
|
||||
await source.cleanup();
|
||||
}
|
||||
|
||||
console.log(`Synced Codex app-server generated protocol from ${sourceRoot}`);
|
||||
|
||||
async function rewriteTypeScriptImports(root: string): Promise<void> {
|
||||
const entries = await fs.readdir(root, { withFileTypes: true });
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.join(root, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await rewriteTypeScriptImports(fullPath);
|
||||
return;
|
||||
}
|
||||
if (!entry.isFile() || !entry.name.endsWith(".ts")) {
|
||||
return;
|
||||
}
|
||||
const text = await fs.readFile(fullPath, "utf8");
|
||||
await fs.writeFile(
|
||||
fullPath,
|
||||
text
|
||||
.replace(/(from\s+["'])(\.{1,2}\/[^"']+?)(\.js)?(["'])/g, "$1$2.js$4")
|
||||
.replace('export * as v2 from "./v2.js";', 'export * as v2 from "./v2/index.js";')
|
||||
.replaceAll("| null | null", "| null"),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
console.log(`Synced Codex app-server generated protocol from ${source.codexRepo}`);
|
||||
|
||||
Reference in New Issue
Block a user