scripts/generate-codex-rpc.mjs
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
// Deterministic, dependency-free bindings for the stable protocol subset we use.
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import {execFileSync} from 'node:child_process';
const root = path.resolve(import.meta.dirname, '../pkg/adapters/codexrpc');
const version = '0.153.0';
const inputs = ['v1/InitializeParams', 'v1/InitializeResponse', 'v2/ThreadStartParams', 'v2/ThreadStartResponse', 'v2/ThreadResumeParams', 'v2/ThreadResumeResponse', 'v2/TurnStartParams', 'v2/TurnStartResponse', 'v2/TurnInterruptParams', 'v2/TurnInterruptResponse', 'v2/TurnStartedNotification', 'v2/TurnCompletedNotification', 'v2/ItemStartedNotification', 'v2/ItemCompletedNotification', 'v2/ErrorNotification', 'v2/PluginInstallParams', 'v2/PluginInstallResponse'];
const schemaFile = path.join(root, 'schema/protocol.json');
if (process.argv[2] && process.argv[2] !== '--check') {
const installed = execFileSync(process.env.CODEX_BIN || 'codex', ['--version'], {encoding:'utf8'}).trim();
if (installed !== 'codex-cli ' + version) throw new Error('Schema refresh requires pinned Codex ' + version);
const all = {}, selected = {};
for (const name of inputs) {
const schema = JSON.parse(fs.readFileSync(path.join(process.argv[2], name + '.json')));
Object.assign(all, schema.definitions);
const {$schema, definitions, ...body} = schema;
all[schema.title] = body;
}
function visit(name) {
if (selected[name]) return;
if (!all[name]) throw new Error('Unresolved definition ' + name);
selected[name] = all[name];
function walk(value) {
if (Array.isArray(value)) return value.forEach(walk);
if (!value || typeof value !== 'object') return;
if (value.$ref) visit(value.$ref.split('/').at(-1));
Object.values(value).forEach(walk);
}
walk(all[name]);
}
inputs.forEach(name => visit(name.split('/').at(-1)));
const ordered = Object.fromEntries(Object.keys(selected).sort().map(name => [name, selected[name]]));
fs.writeFileSync(schemaFile, JSON.stringify({$schema:'http://json-schema.org/draft-07/schema#', codexVersion:version, sourceCommit:'41e22fee981a63b3698df7ed36bad393cda24715', generatedBy:'codex app-server generate-json-schema (stable)', definitions:ordered}, null, 2) + '\n');
}
const raw = fs.readFileSync(schemaFile);
const schema = JSON.parse(raw);
if (schema.codexVersion !== version || schema.sourceCommit !== '41e22fee981a63b3698df7ed36bad393cda24715') throw new Error('Unexpected Codex schema provenance');
const {definitions} = schema;
const names = s => s.replace(/(^|[^a-zA-Z0-9])([a-zA-Z0-9])/g, (_, a, b) => b.toUpperCase()).replace(/Id(s)?$/, (_, s) => 'ID' + (s || ''));
function nullable(s) {return Array.isArray(s.type) && s.type.includes('null') || (s.anyOf || []).some(v => v.type === 'null');}
function type(s) {
if (s.$ref) return s.$ref.split('/').at(-1);
if (s.anyOf) {
const variants = s.anyOf.filter(v => v.type !== 'null');
if (variants.length === 1) return type(variants[0]);
return 'json.RawMessage';
}
if (s.oneOf) return s.oneOf.every(v => v.type === 'string') ? 'string' : 'json.RawMessage';
const t = Array.isArray(s.type) ? s.type.find(t => t !== 'null') : s.type;
switch (t) {
case 'string': return 'string';
case 'integer': return 'int64';
case 'number': return 'float64';
case 'boolean': return 'bool';
case 'array': return '[]' + type(s.items);
case 'object': {
if (!s.properties) return 'map[string]' + (s.additionalProperties && typeof s.additionalProperties === 'object' ? type(s.additionalProperties) : 'json.RawMessage');
return object(s);
}
default: return 'json.RawMessage';
}
}
function object(s) {
const required = new Set(s.required || []);
return 'struct {\n' + Object.keys(s.properties).sort().map(key => {
const v = s.properties[key];
let t = type(v);
const optional = !required.has(key) || nullable(v);
// Pointers preserve explicit false/zero versus omitted override values.
if (optional && !t.startsWith('[]') && !t.startsWith('map[') && t !== 'json.RawMessage') t = '*' + t;
return names(key) + ' ' + t + ' `json:"' + key + (optional ? ',omitempty' : '') + '"`';
}).join('\n') + '\n}';
}
let go = '// Code generated by scripts/generate-codex-rpc.mjs; DO NOT EDIT.\n// Stable Codex ' + version + '; schema SHA-256 ' + crypto.createHash('sha256').update(raw).digest('hex') + '\npackage codexrpc\n\nimport "encoding/json"\n\nconst ProtocolVersion = "' + version + '"\n';
for (const [name, original] of Object.entries(definitions)) {
let s = original;
// Tagged item/input variants share one envelope, retaining all schema fields.
// Consumers MUST dispatch on Type before reading variant-specific fields.
if (['ThreadItem', 'UserInput'].includes(name)) {
const properties = {};
for (const branch of s.oneOf) for (const [field, shape] of Object.entries(branch.properties)) {
if (!(field in properties)) properties[field] = shape;
else if (type(properties[field]) !== type(shape)) properties[field] = {};
}
properties.type = {type:'string'};
s = {type:'object', properties, required:['type']};
}
const target = type(s);
go += '\ntype ' + name + (target === 'json.RawMessage' ? ' = ' : ' ') + target + '\n';
}
const formatted = execFileSync('gofmt', [], {input:go,encoding:'utf8'});
const output = path.join(root, 'protocol.gen.go');
if (process.argv[2] === '--check') {
if (fs.readFileSync(output,'utf8') !== formatted) throw new Error('Codex RPC bindings differ from the pinned schema; regenerate them');
} else fs.writeFileSync(output, formatted);