/*--------------------------------------------------------------------------------------------- * 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--/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 `/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();