mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-08 22:54:02 +00:00
271 lines
10 KiB
TypeScript
271 lines
10 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
// Check Codex App Server Protocol script supports OpenClaw repository automation.
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import {
|
|
generateExperimentalCodexAppServerProtocolSource,
|
|
normalizeCodexAppServerProtocolJsonText,
|
|
selectedCodexAppServerJsonSchemas,
|
|
} from "./lib/codex-app-server-protocol-source.js";
|
|
|
|
const generatedRoot = path.resolve(
|
|
process.cwd(),
|
|
"extensions/codex/src/app-server/protocol-generated",
|
|
);
|
|
|
|
const checks: Array<{ file: string; snippets: string[] }> = [
|
|
{
|
|
file: "ServerRequest.ts",
|
|
snippets: [
|
|
'"item/commandExecution/requestApproval"',
|
|
'"item/fileChange/requestApproval"',
|
|
'"item/permissions/requestApproval"',
|
|
'"item/tool/call"',
|
|
],
|
|
},
|
|
{
|
|
file: "v2/ThreadItem.ts",
|
|
snippets: [
|
|
'type: "contextCompaction"',
|
|
'type: "dynamicToolCall"',
|
|
'type: "commandExecution"',
|
|
'type: "mcpToolCall"',
|
|
],
|
|
},
|
|
{
|
|
file: "v2/DynamicToolSpec.ts",
|
|
snippets: [
|
|
'"function"',
|
|
"& DynamicToolFunctionSpec",
|
|
'"namespace"',
|
|
"& DynamicToolNamespaceSpec",
|
|
],
|
|
},
|
|
{
|
|
file: "v2/DynamicToolFunctionSpec.ts",
|
|
snippets: ["name: string", "description: string", "inputSchema: JsonValue"],
|
|
},
|
|
{
|
|
file: "v2/DynamicToolNamespaceSpec.ts",
|
|
snippets: ["name: string", "description: string", "tools: Array<DynamicToolNamespaceTool>"],
|
|
},
|
|
{
|
|
file: "v2/CommandExecutionApprovalDecision.ts",
|
|
snippets: ['"accept"', '"acceptForSession"', '"decline"', '"cancel"'],
|
|
},
|
|
{
|
|
file: "v2/Account.ts",
|
|
snippets: ['type: "apiKey"', 'type: "chatgpt"', 'type: "amazonBedrock"'],
|
|
},
|
|
{
|
|
file: "v2/ThreadStartParams.ts",
|
|
snippets: [
|
|
"permissions?: string | null",
|
|
"dynamicTools?: Array<DynamicToolSpec> | null",
|
|
"experimentalRawEvents",
|
|
],
|
|
},
|
|
{
|
|
file: "v2/TurnStartParams.ts",
|
|
snippets: ["permissions?: string | null", "serviceTier?: string | null"],
|
|
},
|
|
{
|
|
file: "ReviewDecision.ts",
|
|
snippets: ['"approved"', '"approved_for_session"', '"denied"', '"abort"'],
|
|
},
|
|
{
|
|
file: "v2/PlanDeltaNotification.ts",
|
|
snippets: ["itemId: string", "delta: string"],
|
|
},
|
|
{
|
|
file: "v2/TurnPlanUpdatedNotification.ts",
|
|
snippets: ["explanation: string | null", "plan: Array<TurnPlanStep>"],
|
|
},
|
|
];
|
|
|
|
const failures: string[] = [];
|
|
await main().catch((error: unknown) => {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
});
|
|
|
|
async function main(): Promise<void> {
|
|
const source = await generateExperimentalCodexAppServerProtocolSource();
|
|
|
|
try {
|
|
await compareGeneratedProtocolMirror(source.jsonRoot);
|
|
await checkMaintainedProtocolTypes(source.typescriptRoot);
|
|
|
|
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) {
|
|
console.error("Codex app-server generated protocol drift:");
|
|
for (const failure of failures) {
|
|
console.error(`- ${failure}`);
|
|
}
|
|
console.error(
|
|
`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: ${source.codexRepo}`,
|
|
);
|
|
}
|
|
|
|
async function checkMaintainedProtocolTypes(sourceRoot: string): Promise<void> {
|
|
// Raw requests go to Codex; raw responses flow into OpenClaw. Keep the
|
|
// assignability direction explicit so the probe permits deliberate projections.
|
|
const probePath = path.join(sourceRoot, "openclaw-protocol-compatibility.ts");
|
|
const protocolPath = path.resolve(process.cwd(), "extensions/codex/src/app-server/protocol.ts");
|
|
const protocolImport = relativeTypeScriptImport(probePath, protocolPath);
|
|
const generatedImport = (file: string) =>
|
|
relativeTypeScriptImport(probePath, path.join(sourceRoot, file));
|
|
const probe = `
|
|
import type {
|
|
CodexDynamicToolSpec,
|
|
CodexDynamicToolCallParams,
|
|
CodexErrorNotification,
|
|
CodexModelListResponse,
|
|
CodexThreadForkParams,
|
|
CodexThreadForkResponse,
|
|
CodexThreadResumeParams,
|
|
CodexThreadResumeResponse,
|
|
CodexThreadStartParams,
|
|
CodexThreadStartResponse,
|
|
CodexTurnEnvironmentParams,
|
|
CodexTurnInterruptParams,
|
|
CodexTurnStartParams,
|
|
} from ${JSON.stringify(protocolImport)};
|
|
import type { DynamicToolCallParams } from ${JSON.stringify(generatedImport("v2/DynamicToolCallParams.ts"))};
|
|
import type { DynamicToolSpec } from ${JSON.stringify(generatedImport("v2/DynamicToolSpec.ts"))};
|
|
import type { ErrorNotification } from ${JSON.stringify(generatedImport("v2/ErrorNotification.ts"))};
|
|
import type { ModelListResponse } from ${JSON.stringify(generatedImport("v2/ModelListResponse.ts"))};
|
|
import type { ThreadForkParams } from ${JSON.stringify(generatedImport("v2/ThreadForkParams.ts"))};
|
|
import type { ThreadForkResponse } from ${JSON.stringify(generatedImport("v2/ThreadForkResponse.ts"))};
|
|
import type { ThreadResumeParams } from ${JSON.stringify(generatedImport("v2/ThreadResumeParams.ts"))};
|
|
import type { ThreadResumeResponse } from ${JSON.stringify(generatedImport("v2/ThreadResumeResponse.ts"))};
|
|
import type { ThreadStartParams } from ${JSON.stringify(generatedImport("v2/ThreadStartParams.ts"))};
|
|
import type { ThreadStartResponse } from ${JSON.stringify(generatedImport("v2/ThreadStartResponse.ts"))};
|
|
import type { TurnEnvironmentParams } from ${JSON.stringify(generatedImport("v2/TurnEnvironmentParams.ts"))};
|
|
import type { TurnInterruptParams } from ${JSON.stringify(generatedImport("v2/TurnInterruptParams.ts"))};
|
|
import type { TurnStartParams } from ${JSON.stringify(generatedImport("v2/TurnStartParams.ts"))};
|
|
|
|
declare const openClawDynamicToolSpec: CodexDynamicToolSpec;
|
|
const generatedDynamicToolSpec: DynamicToolSpec = openClawDynamicToolSpec;
|
|
declare const openClawTurnEnvironmentParams: CodexTurnEnvironmentParams;
|
|
const generatedTurnEnvironmentParams: TurnEnvironmentParams = openClawTurnEnvironmentParams;
|
|
declare const openClawThreadStartParams: CodexThreadStartParams;
|
|
const generatedThreadStartParams: ThreadStartParams = openClawThreadStartParams;
|
|
declare const openClawThreadResumeParams: CodexThreadResumeParams;
|
|
const generatedThreadResumeParams: ThreadResumeParams = openClawThreadResumeParams;
|
|
declare const openClawThreadForkParams: CodexThreadForkParams;
|
|
const generatedThreadForkParams: ThreadForkParams = openClawThreadForkParams;
|
|
declare const openClawTurnInterruptParams: CodexTurnInterruptParams;
|
|
const generatedTurnInterruptParams: TurnInterruptParams = openClawTurnInterruptParams;
|
|
declare const openClawTurnStartParams: CodexTurnStartParams;
|
|
const generatedTurnStartParams: TurnStartParams = openClawTurnStartParams;
|
|
|
|
declare const generatedDynamicToolCallParams: Omit<DynamicToolCallParams, "arguments">;
|
|
const openClawDynamicToolCallParams: Omit<CodexDynamicToolCallParams, "arguments"> =
|
|
generatedDynamicToolCallParams;
|
|
declare const generatedErrorNotification: ErrorNotification;
|
|
const openClawErrorNotification: CodexErrorNotification = generatedErrorNotification;
|
|
declare const generatedModelListResponse: ModelListResponse;
|
|
const openClawModelListResponse: CodexModelListResponse = generatedModelListResponse;
|
|
|
|
// Thread and turn bodies are normalized behind checked-in JSON schemas. Their
|
|
// raw generated shapes must not be confused with the projector-facing types.
|
|
declare const generatedThreadForkResponse: Omit<ThreadForkResponse, "thread">;
|
|
const openClawThreadForkResponse: Omit<CodexThreadForkResponse, "thread"> =
|
|
generatedThreadForkResponse;
|
|
declare const generatedThreadResumeResponse: Omit<ThreadResumeResponse, "thread">;
|
|
const openClawThreadResumeResponse: Omit<CodexThreadResumeResponse, "thread"> =
|
|
generatedThreadResumeResponse;
|
|
declare const generatedThreadStartResponse: Omit<ThreadStartResponse, "thread">;
|
|
const openClawThreadStartResponse: Omit<CodexThreadStartResponse, "thread"> =
|
|
generatedThreadStartResponse;
|
|
|
|
export {};
|
|
`;
|
|
await fs.writeFile(probePath, probe);
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
"scripts/run-tsgo.mjs",
|
|
"--ignoreConfig",
|
|
"--noEmit",
|
|
"--allowImportingTsExtensions",
|
|
"--strict",
|
|
"--skipLibCheck",
|
|
"--module",
|
|
"nodenext",
|
|
"--moduleResolution",
|
|
"nodenext",
|
|
probePath,
|
|
],
|
|
{ cwd: process.cwd(), encoding: "utf8" },
|
|
);
|
|
if (result.error) {
|
|
failures.push(`maintained protocol types: failed to start tsgo (${result.error.message})`);
|
|
return;
|
|
}
|
|
if (result.status !== 0) {
|
|
const output = `${result.stdout}${result.stderr}`.trim();
|
|
failures.push(`maintained protocol types differ from generated Codex types\n${output}`);
|
|
}
|
|
}
|
|
|
|
function relativeTypeScriptImport(fromFile: string, toFile: string): string {
|
|
const relative = path.relative(path.dirname(fromFile), toFile).replaceAll(path.sep, "/");
|
|
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
}
|
|
|
|
async function compareGeneratedProtocolMirror(sourceJsonRoot: string): Promise<void> {
|
|
for (const schema of selectedCodexAppServerJsonSchemas) {
|
|
const sourcePath = path.join(sourceJsonRoot, schema);
|
|
const targetPath = path.join(generatedRoot, "json", schema);
|
|
let sourceValue: string;
|
|
let target: string;
|
|
try {
|
|
sourceValue = await fs.readFile(sourcePath, "utf8");
|
|
} catch (error) {
|
|
failures.push(
|
|
`protocol-generated/json/${schema}: missing upstream schema (${String(error)})`,
|
|
);
|
|
continue;
|
|
}
|
|
try {
|
|
target = await fs.readFile(targetPath, "utf8");
|
|
} catch (error) {
|
|
failures.push(`protocol-generated/json/${schema}: missing local schema (${String(error)})`);
|
|
continue;
|
|
}
|
|
if (normalizeJsonSchema(sourceValue) !== normalizeJsonSchema(target)) {
|
|
failures.push(`protocol-generated/json/${schema}: differs from source schema`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function normalizeJsonSchema(sourceLocal: string): string {
|
|
return normalizeCodexAppServerProtocolJsonText(sourceLocal);
|
|
}
|