From b624ae2b3847b684e4b77ad8f32da1f026947ac0 Mon Sep 17 00:00:00 2001 From: ndycode Date: Sun, 1 Mar 2026 07:09:51 +0800 Subject: [PATCH 1/3] perf(request): reduce request-path allocation overhead Avoid extra array passes in filterInput and clone only tool schema trees during cleanupToolDefinitions, preserving behavior while reducing hot-path work. Co-authored-by: Codex --- lib/request/helpers/tool-utils.ts | 43 +++++++++++++++++++++++++----- lib/request/request-transformer.ts | 34 +++++++++++------------ 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/lib/request/helpers/tool-utils.ts b/lib/request/helpers/tool-utils.ts index 14212e44..a7349ec0 100644 --- a/lib/request/helpers/tool-utils.ts +++ b/lib/request/helpers/tool-utils.ts @@ -14,6 +14,14 @@ export interface Tool { function: ToolFunction; } +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object"; +} + +function cloneRecord(value: Record): Record { + return JSON.parse(JSON.stringify(value)) as Record; +} + /** * Cleans up tool definitions to ensure strict JSON Schema compliance. * @@ -31,17 +39,29 @@ export function cleanupToolDefinitions(tools: unknown): unknown { if (!Array.isArray(tools)) return tools; return tools.map((tool) => { - if (tool?.type !== "function" || !tool.function) { + if (!isRecord(tool) || tool.type !== "function") { return tool; } - - // Clone to avoid mutating original - const cleanedTool = JSON.parse(JSON.stringify(tool)); - if (cleanedTool.function.parameters) { - cleanupSchema(cleanedTool.function.parameters); + const functionDef = tool.function; + if (!isRecord(functionDef)) { + return tool; + } + const parameters = functionDef.parameters; + if (!isRecord(parameters)) { + return tool; } - return cleanedTool; + // Clone only the schema tree we mutate to avoid heavy deep cloning of entire tools. + const cleanedParameters = cloneRecord(parameters); + cleanupSchema(cleanedParameters); + + return { + ...tool, + function: { + ...functionDef, + parameters: cleanedParameters, + }, + }; }); } @@ -51,6 +71,15 @@ export function cleanupToolDefinitions(tools: unknown): unknown { function cleanupSchema(schema: Record): void { if (!schema || typeof schema !== "object") return; + if (schema.properties && typeof schema.properties === "object") { + const properties = schema.properties as Record; + for (const key of Object.keys(properties)) { + if (properties[key] === undefined) { + delete properties[key]; + } + } + } + // 1. Flatten Unions (anyOf -> enum) if (Array.isArray(schema.anyOf)) { const anyOf = schema.anyOf as Record[]; diff --git a/lib/request/request-transformer.ts b/lib/request/request-transformer.ts index f668b5c4..dc4969e9 100644 --- a/lib/request/request-transformer.ts +++ b/lib/request/request-transformer.ts @@ -543,24 +543,22 @@ export function filterInput( input: InputItem[] | undefined, ): InputItem[] | undefined { if (!Array.isArray(input)) return input; - - return input - .filter((item) => { - // Remove AI SDK constructs not supported by Codex API - if (item.type === "item_reference") { - return false; // AI SDK only - references server state - } - return true; // Keep all other items - }) - .map((item) => { - // Strip IDs from all items (Codex API stateless mode) - if (item.id) { - const { id: _omit, ...itemWithoutId } = item; - void _omit; - return itemWithoutId as InputItem; - } - return item; - }); + const filtered: InputItem[] = []; + for (const item of input) { + // Remove AI SDK constructs not supported by Codex API. + if (item.type === "item_reference") { + continue; + } + // Strip IDs from all items (Codex API stateless mode). + if (item.id) { + const { id: _omit, ...itemWithoutId } = item; + void _omit; + filtered.push(itemWithoutId as InputItem); + continue; + } + filtered.push(item); + } + return filtered; } /** From d1c3596661f8242968f0473349762e840f2e0b18 Mon Sep 17 00:00:00 2001 From: ndycode Date: Sun, 1 Mar 2026 07:10:07 +0800 Subject: [PATCH 2/3] chore(bench): add runtime request-path benchmark harness Add a reusable benchmark script and npm commands to measure request-path hotspots (filterInput, cleanupToolDefinitions, and account selection) for before/after performance tracking. Co-authored-by: Codex --- package.json | 2 + scripts/benchmark-runtime-path.mjs | 166 +++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 scripts/benchmark-runtime-path.mjs diff --git a/package.json b/package.json index 9cc8f5e0..d6e8f919 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "bench:edit-formats": "node scripts/benchmark-edit-formats.mjs --preset=codex-core", "bench:edit-formats:smoke": "node scripts/benchmark-edit-formats.mjs --smoke --preset=codex-core", "bench:edit-formats:render": "node scripts/benchmark-render-dashboard.mjs", + "bench:runtime-path": "npm run build && node scripts/benchmark-runtime-path.mjs", + "bench:runtime-path:quick": "node scripts/benchmark-runtime-path.mjs", "test:coverage": "vitest run --coverage", "coverage": "vitest run --coverage", "audit:prod": "npm audit --omit=dev --audit-level=high", diff --git a/scripts/benchmark-runtime-path.mjs b/scripts/benchmark-runtime-path.mjs new file mode 100644 index 00000000..2fc857ec --- /dev/null +++ b/scripts/benchmark-runtime-path.mjs @@ -0,0 +1,166 @@ +#!/usr/bin/env node + +import { mkdir, writeFile } from "node:fs/promises"; +import { performance } from "node:perf_hooks"; +import process from "node:process"; +import { dirname, resolve } from "node:path"; +import { filterInput } from "../dist/lib/request/request-transformer.js"; +import { cleanupToolDefinitions } from "../dist/lib/request/helpers/tool-utils.js"; +import { AccountManager } from "../dist/lib/accounts.js"; + +function argValue(args, name) { + const prefix = `${name}=`; + const match = args.find((arg) => arg.startsWith(prefix)); + return match ? match.slice(prefix.length) : undefined; +} + +function parsePositiveInt(value, fallback) { + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed <= 0) return fallback; + return parsed; +} + +function benchmarkCase(name, iterations, fn) { + for (let i = 0; i < 5; i += 1) { + fn(); + } + const start = performance.now(); + for (let i = 0; i < iterations; i += 1) { + fn(); + } + const end = performance.now(); + return { + name, + iterations, + avgMs: Number(((end - start) / iterations).toFixed(6)), + }; +} + +function buildInputItems(size) { + const items = []; + for (let i = 0; i < size; i += 1) { + items.push({ + type: "message", + role: i % 2 === 0 ? "user" : "assistant", + id: `msg_${i}`, + content: [{ type: "input_text", text: `payload-${i}` }], + }); + if (i % 40 === 0) { + items.push({ type: "item_reference", id: `ref_${i}` }); + } + } + return items; +} + +function buildTools(toolCount, propertyCount) { + const tools = []; + for (let i = 0; i < toolCount; i += 1) { + const properties = {}; + const required = []; + for (let j = 0; j < propertyCount; j += 1) { + const key = `field_${j}`; + properties[key] = { type: ["string", "null"], description: `property-${j}` }; + required.push(key); + } + required.push("ghost_field"); + tools.push({ + type: "function", + function: { + name: `tool_${i}`, + parameters: { + type: "object", + properties, + required, + additionalProperties: false, + }, + }, + }); + } + return tools; +} + +function buildManager(accountCount) { + const now = Date.now(); + const accounts = []; + for (let i = 0; i < accountCount; i += 1) { + accounts.push({ + refreshToken: `rt_${i}`, + accessToken: `at_${i}`, + expiresAt: now + 3_600_000, + accountId: `acct_${i}`, + email: `user${i}@example.com`, + enabled: true, + addedAt: now, + lastUsed: 0, + rateLimitResetTimes: {}, + }); + } + return new AccountManager(undefined, { + version: 3, + accounts, + activeIndex: 0, + activeIndexByFamily: {}, + }); +} + +function run() { + const args = process.argv.slice(2); + const iterations = parsePositiveInt(argValue(args, "--iterations"), 30); + const outputPath = argValue(args, "--output"); + + const inputSmall = buildInputItems(400); + const inputLarge = buildInputItems(2000); + const toolsMedium = buildTools(40, 12); + const toolsLarge = buildTools(140, 25); + + const results = [ + benchmarkCase("filterInput_small", iterations, () => { + const out = filterInput(inputSmall); + if (!Array.isArray(out)) throw new Error("filterInput_small failed"); + }), + benchmarkCase("filterInput_large", iterations, () => { + const out = filterInput(inputLarge); + if (!Array.isArray(out)) throw new Error("filterInput_large failed"); + }), + benchmarkCase("cleanupToolDefinitions_medium", iterations, () => { + const out = cleanupToolDefinitions(toolsMedium); + if (!Array.isArray(out)) throw new Error("cleanupToolDefinitions_medium failed"); + }), + benchmarkCase("cleanupToolDefinitions_large", iterations, () => { + const out = cleanupToolDefinitions(toolsLarge); + if (!Array.isArray(out)) throw new Error("cleanupToolDefinitions_large failed"); + }), + benchmarkCase("accountHybridSelection_200", iterations, () => { + const manager = buildManager(200); + for (let i = 0; i < 200; i += 1) { + manager.getCurrentOrNextForFamilyHybrid("codex", "gpt-5-codex", { pidOffsetEnabled: false }); + } + }), + ]; + + const payload = { + generatedAt: new Date().toISOString(), + node: process.version, + iterations, + results, + }; + + if (outputPath) { + const resolved = resolve(outputPath); + return mkdir(dirname(resolved), { recursive: true }).then(() => + writeFile(resolved, `${JSON.stringify(payload, null, 2)}\n`, "utf8"), + ).then(() => { + console.log(`Runtime benchmark written: ${resolved}`); + console.log(JSON.stringify(payload, null, 2)); + }); + } + + console.log(JSON.stringify(payload, null, 2)); + return Promise.resolve(); +} + +run().catch((error) => { + console.error(`Runtime benchmark failed: ${error instanceof Error ? error.message : String(error)}`); + process.exit(1); +}); From 9cd71c44e884db03ef447f044ab0e83dbd1e0a0b Mon Sep 17 00:00:00 2001 From: ndycode Date: Sun, 1 Mar 2026 07:18:25 +0800 Subject: [PATCH 3/3] fix(request): guard sparse inputs in filterInput Prevent sparse-array holes from causing runtime errors in the single-pass filterInput loop and add regression coverage for sparse entries. Co-authored-by: Codex --- lib/request/request-transformer.ts | 3 +++ test/request-transformer.test.ts | 38 ++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/request/request-transformer.ts b/lib/request/request-transformer.ts index dc4969e9..67539195 100644 --- a/lib/request/request-transformer.ts +++ b/lib/request/request-transformer.ts @@ -545,6 +545,9 @@ export function filterInput( if (!Array.isArray(input)) return input; const filtered: InputItem[] = []; for (const item of input) { + if (!item || typeof item !== "object") { + continue; + } // Remove AI SDK constructs not supported by Codex API. if (item.type === "item_reference") { continue; diff --git a/test/request-transformer.test.ts b/test/request-transformer.test.ts index 0dc0ef85..e6b41e87 100644 --- a/test/request-transformer.test.ts +++ b/test/request-transformer.test.ts @@ -369,21 +369,33 @@ describe('Request Transformer Module', () => { expect(result![2].content).toBe('3'); }); - it('should handle custom ID formats (future-proof)', async () => { - const input: InputItem[] = [ - { id: 'custom_id_format', type: 'message', role: 'user', content: 'test' }, - { id: 'another-format-123', type: 'message', role: 'user', content: 'test2' }, - ]; - const result = filterInput(input); + it('should handle custom ID formats (future-proof)', async () => { + const input: InputItem[] = [ + { id: 'custom_id_format', type: 'message', role: 'user', content: 'test' }, + { id: 'another-format-123', type: 'message', role: 'user', content: 'test2' }, + ]; + const result = filterInput(input); + + expect(result).toHaveLength(2); + expect(result![0]).not.toHaveProperty('id'); + expect(result![1]).not.toHaveProperty('id'); + }); - expect(result).toHaveLength(2); - expect(result![0]).not.toHaveProperty('id'); - expect(result![1]).not.toHaveProperty('id'); - }); + it('should skip sparse entries without throwing', async () => { + const sparse = new Array(3); + sparse[0] = { id: 'msg_1', type: 'message', role: 'user', content: 'test' }; + sparse[2] = { id: 'msg_2', type: 'message', role: 'assistant', content: 'reply' }; - it('should return undefined for undefined input', async () => { - expect(filterInput(undefined)).toBeUndefined(); - }); + const result = filterInput(sparse as InputItem[]); + + expect(result).toHaveLength(2); + expect(result![0]).not.toHaveProperty('id'); + expect(result![1]).not.toHaveProperty('id'); + }); + + it('should return undefined for undefined input', async () => { + expect(filterInput(undefined)).toBeUndefined(); + }); it('should return non-array input as-is', async () => { const notArray = { notAnArray: true };