Files
vscode/build/codex/generate-protocol.mjs
T
2026-06-01 11:48:48 +02:00

177 lines
7.1 KiB
JavaScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Regenerate vendored codex app-server protocol types.
//
// Resolution order for the codex binary:
// 1. $CODEX_BIN
// 2. node_modules/@openai/codex-<platform>-<arch>/vendor/.../bin/codex
// 3. `which codex`
//
// The locally installed binary's --version must match build/codex/codex-version.txt
// unless --no-version-check is passed.
//
// Generated files land in src/vs/platform/agentHost/node/codex/protocol/generated/
import { spawnSync } from 'node:child_process';
import { mkdtempSync, readdirSync, readFileSync, writeFileSync, statSync, rmSync, mkdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '..', '..');
const VERSION_FILE = path.join(REPO_ROOT, 'build', 'codex', 'codex-version.txt');
const OUT_DIR = path.join(REPO_ROOT, 'src', 'vs', 'platform', 'agentHost', 'node', 'codex', 'protocol', 'generated');
const FORMATTER = path.join(REPO_ROOT, 'build', 'lib', 'formatter.ts');
const args = process.argv.slice(2);
const noVersionCheck = args.includes('--no-version-check');
function resolveCodexBinary() {
if (process.env.CODEX_BIN) {
return process.env.CODEX_BIN;
}
const platformMap = {
'linux-x64': '@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/bin/codex',
'linux-arm64': '@openai/codex-linux-arm64/vendor/aarch64-unknown-linux-musl/bin/codex',
'darwin-x64': '@openai/codex-darwin-x64/vendor/x86_64-apple-darwin/bin/codex',
'darwin-arm64': '@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex',
'win32-x64': '@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc/bin/codex.exe',
'win32-arm64': '@openai/codex-win32-arm64/vendor/aarch64-pc-windows-msvc/bin/codex.exe',
};
const key = `${process.platform}-${process.arch}`;
const rel = platformMap[key];
if (rel) {
const candidate = path.join(REPO_ROOT, 'node_modules', rel);
try {
statSync(candidate);
return candidate;
} catch { /* fall through */ }
}
const which = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['codex'], { encoding: 'utf8' });
if (which.status === 0) {
return which.stdout.trim().split(/\r?\n/)[0];
}
throw new Error('Cannot locate codex binary. Install via `npm install` (vendors @openai/codex) or set $CODEX_BIN.');
}
function readBinaryVersion(bin) {
const r = spawnSync(bin, ['--version'], { encoding: 'utf8' });
if (r.status !== 0) {
throw new Error(`codex --version failed:\n${r.stderr}`);
}
// Output looks like: "codex-cli 0.134.0"
const m = r.stdout.match(/(\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?)/);
if (!m) {
throw new Error(`Could not parse codex version from: ${r.stdout}`);
}
return m[1];
}
function readPinnedVersion() {
return readFileSync(VERSION_FILE, 'utf8').trim();
}
function formatGeneratedTypes(files) {
for (let i = 0; i < files.length; i += 100) {
const batch = files.slice(i, i + 100);
const r = spawnSync(process.execPath, ['--experimental-strip-types', FORMATTER, '--replace', ...batch], { cwd: REPO_ROOT, encoding: 'utf8' });
if (r.status !== 0) {
throw new Error(`formatting generated protocol files failed:\n${r.stderr || r.stdout}`);
}
for (const file of batch) {
const formatted = readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
writeFileSync(file, formatted);
}
}
}
function generate(bin, outDir, codexVersion) {
const tmp = mkdtempSync(path.join(tmpdir(), 'codex-gen-'));
const r = spawnSync(bin, ['app-server', 'generate-ts', '--out', tmp, '--experimental'], { encoding: 'utf8' });
if (r.status !== 0) {
throw new Error(`codex app-server generate-ts failed:\n${r.stderr}`);
}
// Wipe out existing generated content (excluding README.md).
for (const entry of readdirSync(outDir, { withFileTypes: true })) {
if (entry.name === 'README.md') { continue; }
rmSync(path.join(outDir, entry.name), { recursive: true, force: true });
}
const generatedFiles = [];
const HEADER = sourcePath =>
`// AUTOGENERATED - do not edit.\n` +
`// Source: \`codex app-server generate-ts --experimental\`\n` +
`// Generated from @openai/codex ${codexVersion}, licensed Apache-2.0.\n` +
`// Modified by VS Code to rewrite imports and apply repository formatting.\n` +
`// See README.md in this directory for provenance and licensing notes.\n` +
`// Regenerate: npm run codex:gen-protocol\n` +
`// Source file: ${sourcePath}\n\n`;
function copyRecursive(srcDir, dstDir, rel = '') {
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
const src = path.join(srcDir, entry.name);
const dst = path.join(dstDir, entry.name);
const relPath = path.posix.join(rel, entry.name);
if (entry.isDirectory()) {
mkdirSync(dst, { recursive: true });
copyRecursive(src, dst, relPath);
} else if (entry.isFile() && entry.name.endsWith('.ts')) {
const body = readFileSync(src, 'utf8')
// Strip ts-rs's own header so we don't double up
.replace(/^\/\/ GENERATED CODE!.*$\n/m, '')
.replace(/^\/\/ This file was generated by.*$\n/m, '')
.replace(/^\s*\n/, '')
// Rewrite relative imports to use .js extension (VS Code uses node16 module resolution).
// Directory specifiers (no trailing file part) become `<dir>/index.js`.
.replace(/(from\s+["'])(\.\.?\/[^"']+?)(["'])/g, (_m, p, spec, q) => {
// If the spec resolves to a directory in our generated tree, point at its index.
const abs = path.resolve(path.dirname(src), spec);
let suffix = '.js';
try {
if (statSync(abs).isDirectory()) {
suffix = '/index.js';
}
} catch { /* not a directory; assume .ts file */ }
return `${p}${spec}${suffix}${q}`;
});
writeFileSync(dst, HEADER(relPath) + body);
generatedFiles.push(dst);
}
}
}
mkdirSync(outDir, { recursive: true });
copyRecursive(tmp, outDir);
formatGeneratedTypes(generatedFiles);
rmSync(tmp, { recursive: true, force: true });
}
function main() {
const bin = resolveCodexBinary();
const binVersion = readBinaryVersion(bin);
const pinnedVersion = readPinnedVersion();
console.log(`codex binary: ${bin}`);
console.log(`codex --version: ${binVersion}`);
console.log(`pinned version: ${pinnedVersion}`);
if (!noVersionCheck && binVersion !== pinnedVersion) {
console.error(`\nVersion mismatch: binary is ${binVersion}, pinned is ${pinnedVersion}.`);
console.error(`Either update ${path.relative(REPO_ROOT, VERSION_FILE)} or install the matching codex version.`);
console.error(`Use --no-version-check to override.`);
process.exit(1);
}
mkdirSync(OUT_DIR, { recursive: true });
generate(bin, OUT_DIR, binVersion);
const count = readdirSync(OUT_DIR, { recursive: true }).filter(f => typeof f === 'string' && f.endsWith('.ts')).length;
console.log(`\nWrote ${count} files to ${path.relative(REPO_ROOT, OUT_DIR)}`);
}
main();