fix: preserve non-oneOf schema array order (#91891)

This commit is contained in:
Dallin Romney
2026-06-10 15:58:30 -07:00
committed by GitHub
parent a450ff036a
commit c7ed990769
2 changed files with 53 additions and 14 deletions

View File

@@ -385,10 +385,19 @@ export function normalizeGeneratedTypeScript(text: string): string {
.replaceAll("| null | null", "| null");
}
export function canonicalizeCodexAppServerProtocolJson(value: unknown): unknown {
// Sort typed-object arrays for schema keywords whose item order does not affect
// payload validity; preserve order everywhere else, especially prefixItems.
const typeSortedSchemaArrayKeys = new Set(["anyOf", "enum", "oneOf", "required"]);
export function canonicalizeCodexAppServerProtocolJson(
value: unknown,
parentKey?: string,
): unknown {
if (Array.isArray(value)) {
const items = value.map(canonicalizeCodexAppServerProtocolJson);
return sortCodexProtocolJsonArrayByType(items);
const items = value.map((item) => canonicalizeCodexAppServerProtocolJson(item));
return parentKey !== undefined && typeSortedSchemaArrayKeys.has(parentKey)
? sortCodexProtocolJsonArrayByType(items)
: items;
}
if (!isPlainObject(value)) {
@@ -397,7 +406,7 @@ export function canonicalizeCodexAppServerProtocolJson(value: unknown): unknown
const sorted: Record<string, unknown> = {};
const entries = Object.entries(value)
.map(([key, child]) => [key, canonicalizeCodexAppServerProtocolJson(child)] as const)
.map(([key, child]) => [key, canonicalizeCodexAppServerProtocolJson(child, key)] as const)
.toSorted(([left], [right]) => {
if (left < right) {
return -1;

View File

@@ -206,16 +206,31 @@ describe("Codex app-server protocol JSON canonicalizer", () => {
`);
});
it("sorts arrays only when plain object items expose top-level type values", () => {
it("sorts typed-object arrays only for order-insensitive schema keywords", () => {
expect(
canonicalizeCodexAppServerProtocolJson({
enum: ["z", "a"],
anyOf: [
{ z: 1, type: "string" },
{ type: "integer", a: 2 },
],
enum: [
{ z: 1, type: "z" },
{ type: "a", a: 2 },
],
mixed: [{ type: "b" }, "item", { type: "a" }],
oneOf: [
{ title: "Second", z: true },
{ a: true, title: "First" },
{ type: "object", z: true },
{ a: true, type: "array" },
{ type: "object", z: false },
],
prefixItems: [
{ z: 1, type: "string" },
{ type: "number", a: 2 },
],
required: [
{ z: 1, type: "z" },
{ type: "a", a: 2 },
],
required: ["z", "a"],
typed: [
{ type: "beta", z: 1 },
{ type: "alpha", z: 2 },
@@ -223,16 +238,31 @@ describe("Codex app-server protocol JSON canonicalizer", () => {
],
}),
).toEqual({
enum: ["z", "a"],
anyOf: [
{ a: 2, type: "integer" },
{ type: "string", z: 1 },
],
enum: [
{ a: 2, type: "a" },
{ type: "z", z: 1 },
],
mixed: [{ type: "b" }, "item", { type: "a" }],
oneOf: [
{ title: "Second", z: true },
{ a: true, title: "First" },
{ a: true, type: "array" },
{ type: "object", z: true },
{ type: "object", z: false },
],
prefixItems: [
{ type: "string", z: 1 },
{ a: 2, type: "number" },
],
required: [
{ a: 2, type: "a" },
{ type: "z", z: 1 },
],
required: ["z", "a"],
typed: [
{ type: "alpha", z: 2 },
{ type: "beta", z: 1 },
{ type: "alpha", z: 2 },
{ type: "beta", z: 3 },
],
});