mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix: canonicalize codex protocol JSON assets (#91507)
This commit is contained in:
@@ -4,21 +4,6 @@
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -135,6 +120,21 @@
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+42
-42
@@ -39,15 +39,6 @@
|
||||
},
|
||||
"AskForApproval": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -84,6 +75,15 @@
|
||||
],
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -109,21 +109,6 @@
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -240,6 +225,21 @@
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -830,16 +830,6 @@
|
||||
},
|
||||
"SessionSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"cli",
|
||||
"vscode",
|
||||
"exec",
|
||||
"appServer",
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -865,19 +855,21 @@
|
||||
],
|
||||
"title": "SubAgentSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"cli",
|
||||
"vscode",
|
||||
"exec",
|
||||
"appServer",
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"review",
|
||||
"compact",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -941,6 +933,14 @@
|
||||
],
|
||||
"title": "OtherSubAgentSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"review",
|
||||
"compact",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+42
-42
@@ -39,15 +39,6 @@
|
||||
},
|
||||
"AskForApproval": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -84,6 +75,15 @@
|
||||
],
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -109,21 +109,6 @@
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -240,6 +225,21 @@
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -830,16 +830,6 @@
|
||||
},
|
||||
"SessionSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"cli",
|
||||
"vscode",
|
||||
"exec",
|
||||
"appServer",
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -865,19 +855,21 @@
|
||||
],
|
||||
"title": "SubAgentSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"cli",
|
||||
"vscode",
|
||||
"exec",
|
||||
"appServer",
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"review",
|
||||
"compact",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -941,6 +933,14 @@
|
||||
],
|
||||
"title": "OtherSubAgentSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"review",
|
||||
"compact",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+15
-15
@@ -27,21 +27,6 @@
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -158,6 +143,21 @@
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -27,21 +27,6 @@
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -158,6 +143,21 @@
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"contextWindowExceeded",
|
||||
"usageLimitExceeded",
|
||||
"serverOverloaded",
|
||||
"cyberPolicy",
|
||||
"internalServerError",
|
||||
"unauthorized",
|
||||
"badRequest",
|
||||
"threadRollbackFailed",
|
||||
"sandboxError",
|
||||
"other"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
generateExperimentalCodexAppServerProtocolSource,
|
||||
normalizeCodexAppServerProtocolJsonText,
|
||||
selectedCodexAppServerJsonSchemas,
|
||||
} from "./lib/codex-app-server-protocol-source.js";
|
||||
|
||||
@@ -142,5 +143,5 @@ async function compareGeneratedProtocolMirror(sourceJsonRoot: string): Promise<v
|
||||
}
|
||||
|
||||
function normalizeJsonSchema(sourceLocal: string): string {
|
||||
return JSON.stringify(JSON.parse(sourceLocal));
|
||||
return normalizeCodexAppServerProtocolJsonText(sourceLocal);
|
||||
}
|
||||
|
||||
@@ -383,3 +383,83 @@ export function normalizeGeneratedTypeScript(text: string): string {
|
||||
.replace('export * as v2 from "./v2.js";', 'export * as v2 from "./v2/index.js";')
|
||||
.replaceAll("| null | null", "| null");
|
||||
}
|
||||
|
||||
export function canonicalizeCodexAppServerProtocolJson(value: unknown): unknown {
|
||||
if (Array.isArray(value)) {
|
||||
const items = value.map(canonicalizeCodexAppServerProtocolJson);
|
||||
return sortCodexProtocolJsonArrayByType(items);
|
||||
}
|
||||
|
||||
if (!isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const sorted: Record<string, unknown> = {};
|
||||
const entries = Object.entries(value)
|
||||
.map(([key, child]) => [key, canonicalizeCodexAppServerProtocolJson(child)] as const)
|
||||
.toSorted(([left], [right]) => {
|
||||
if (left < right) {
|
||||
return -1;
|
||||
}
|
||||
if (left > right) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
for (const [key, child] of entries) {
|
||||
sorted[key] = child;
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export function normalizeCodexAppServerProtocolJsonText(text: string): string {
|
||||
return JSON.stringify(canonicalizeCodexAppServerProtocolJson(JSON.parse(text)));
|
||||
}
|
||||
|
||||
export function formatCodexAppServerProtocolJsonText(text: string): string {
|
||||
return `${JSON.stringify(canonicalizeCodexAppServerProtocolJson(JSON.parse(text)), null, 2)}\n`;
|
||||
}
|
||||
|
||||
function sortCodexProtocolJsonArrayByType(items: unknown[]): unknown[] {
|
||||
if (!items.every(isPlainObject)) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const typed = items
|
||||
.map((item, index) => ({ index, item, type: stringRecordValue(item, "type") }))
|
||||
.filter(
|
||||
(entry): entry is { index: number; item: Record<string, unknown>; type: string } =>
|
||||
entry.type !== undefined,
|
||||
);
|
||||
if (typed.length < 2) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const sortedTyped = typed.toSorted((left, right) => {
|
||||
if (left.type < right.type) {
|
||||
return -1;
|
||||
}
|
||||
if (left.type > right.type) {
|
||||
return 1;
|
||||
}
|
||||
return left.index - right.index;
|
||||
});
|
||||
const sortedByOriginalIndex = new Map(
|
||||
typed.map((entry, index) => [entry.index, sortedTyped[index]?.item]),
|
||||
);
|
||||
|
||||
return items.map((item, index) => sortedByOriginalIndex.get(index) ?? item);
|
||||
}
|
||||
|
||||
function stringRecordValue(record: Record<string, unknown>, key: string): string | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === Object.prototype || prototype === null;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
formatCodexAppServerProtocolJsonText,
|
||||
generateExperimentalCodexAppServerProtocolSource,
|
||||
selectedCodexAppServerJsonSchemas,
|
||||
} from "./lib/codex-app-server-protocol-source.js";
|
||||
@@ -27,7 +28,7 @@ async function main(): Promise<void> {
|
||||
const schemaSource = await fs.readFile(path.join(source.jsonRoot, schema), "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(targetRoot, "json", schema),
|
||||
`${JSON.stringify(JSON.parse(schemaSource), null, 2)}\n`,
|
||||
formatCodexAppServerProtocolJsonText(schemaSource),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -4,6 +4,8 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildCodexProtocolExportArgs,
|
||||
canonicalizeCodexAppServerProtocolJson,
|
||||
formatCodexAppServerProtocolJsonText,
|
||||
resolveCodexAppServerProtocolSource,
|
||||
resolveCodexProtocolCargoTargetDir,
|
||||
resolveCodexProtocolMinFreeBytes,
|
||||
@@ -162,6 +164,81 @@ describe("codex app-server protocol source resolver", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Codex app-server protocol JSON canonicalizer", () => {
|
||||
it("sorts object keys recursively before formatting", () => {
|
||||
const source = JSON.stringify({
|
||||
z: {
|
||||
d: 1,
|
||||
b: {
|
||||
y: 2,
|
||||
x: 3,
|
||||
},
|
||||
},
|
||||
a: [
|
||||
{
|
||||
z: 4,
|
||||
a: {
|
||||
c: 5,
|
||||
b: 6,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(formatCodexAppServerProtocolJsonText(source)).toBe(`{
|
||||
"a": [
|
||||
{
|
||||
"a": {
|
||||
"b": 6,
|
||||
"c": 5
|
||||
},
|
||||
"z": 4
|
||||
}
|
||||
],
|
||||
"z": {
|
||||
"b": {
|
||||
"x": 3,
|
||||
"y": 2
|
||||
},
|
||||
"d": 1
|
||||
}
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("sorts arrays only when plain object items expose top-level type values", () => {
|
||||
expect(
|
||||
canonicalizeCodexAppServerProtocolJson({
|
||||
enum: ["z", "a"],
|
||||
mixed: [{ type: "b" }, "item", { type: "a" }],
|
||||
oneOf: [
|
||||
{ title: "Second", z: true },
|
||||
{ a: true, title: "First" },
|
||||
],
|
||||
required: ["z", "a"],
|
||||
typed: [
|
||||
{ type: "beta", z: 1 },
|
||||
{ type: "alpha", z: 2 },
|
||||
{ type: "beta", z: 3 },
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
enum: ["z", "a"],
|
||||
mixed: [{ type: "b" }, "item", { type: "a" }],
|
||||
oneOf: [
|
||||
{ title: "Second", z: true },
|
||||
{ a: true, title: "First" },
|
||||
],
|
||||
required: ["z", "a"],
|
||||
typed: [
|
||||
{ type: "alpha", z: 2 },
|
||||
{ type: "beta", z: 1 },
|
||||
{ type: "beta", z: 3 },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createProtocolSchema(codexRepo: string): void {
|
||||
fs.mkdirSync(path.join(codexRepo, "codex-rs/app-server-protocol/schema/typescript"), {
|
||||
recursive: true,
|
||||
|
||||
Reference in New Issue
Block a user