aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
201 lines (175 loc) • 5.28 kB
JavaScript
/**
* Codex CLI Provider Adapter for External Ralph Loop
*
* Provides support for OpenAI's Codex CLI as a provider for
* autonomous task execution in Ralph loops.
*
* Codex CLI differences from Claude:
* - Binary: `codex` instead of `claude`
* - Headless flag: `--full-auto` instead of `--dangerously-skip-permissions`
* - No stream-json output format (text only)
* - No session resume capability
* - No --agent flag
* - No --mcp-config flag
* - No --append-system-prompt flag (inject into main prompt)
* - No --max-budget-usd flag
* - Model names differ (gpt-5.3-codex, codex-mini-latest, gpt-5-codex-mini)
*
* @implements Plan: Multi-Provider Support for External Ralph Loop
*/
import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
/** Model mapping from generic names to Codex-specific models */
const MODEL_MAP = {
'opus': 'gpt-5.3-codex',
'sonnet': 'codex-mini-latest',
'haiku': 'gpt-5-codex-mini',
};
export class CodexAdapter extends ProviderAdapter {
/** @returns {string} */
getBinary() {
return 'codex';
}
/** @returns {string} */
getName() {
return 'codex';
}
/**
* Codex has limited capabilities compared to Claude.
* @returns {import('./provider-adapter.mjs').ProviderCapabilities}
*/
getCapabilities() {
return {
streamJson: false,
sessionResume: false,
budgetControl: false,
systemPrompt: false,
agentMode: false,
mcpConfig: false,
maxTurns: false,
};
}
/**
* Build args for the main headless session.
*
* Codex uses `--full-auto` for headless operation and has a different
* argument structure than Claude.
*
* @param {import('./provider-adapter.mjs').SessionArgs} options
* @returns {string[]}
*/
buildSessionArgs(options) {
const args = [
// SECURITY: --full-auto bypasses ALL permission prompts in Codex
// Equivalent to Claude's --dangerously-skip-permissions
'--full-auto',
];
// Model selection (map generic to Codex-specific)
if (options.model) {
args.push('--model', this.mapModel(options.model));
}
// Budget control not supported - warn if requested
if (options.budget) {
this.warnUnsupported('budgetControl', 'Budget control (--max-budget-usd)');
}
// Max turns not supported
if (options.maxTurns) {
this.warnUnsupported('maxTurns', 'Max turns (--max-turns)');
}
// Session ID not supported - each iteration is a fresh session
if (options.sessionId) {
this.warnUnsupported('sessionResume', 'Session resume (--session-id)');
}
// MCP configuration not supported
if (options.mcpConfig) {
this.warnUnsupported('mcpConfig', 'MCP configuration (--mcp-config)');
}
// System prompt: Codex doesn't support --append-system-prompt,
// so we prepend it to the main prompt
let prompt = options.prompt;
if (options.systemPrompt) {
prompt = `[System Context]\n${options.systemPrompt}\n\n[Task]\n${prompt}`;
}
// The prompt itself (must be last)
args.push(prompt);
return args;
}
/**
* Build args for short analysis calls (spawnSync).
*
* Codex analysis calls use the same basic pattern but with
* Codex-specific flags.
*
* @param {import('./provider-adapter.mjs').AnalysisArgs} options
* @returns {string[]}
*/
buildAnalysisArgs(options) {
const args = [
'--full-auto',
'--quiet',
];
// Model selection
if (options.model) {
args.push('--model', this.mapModel(options.model));
}
// Agent flag not supported by Codex - inject agent context into prompt
if (options.agent) {
// Silently skip - agent context will be in the prompt itself
}
// The analysis prompt (must be last)
args.push(options.prompt);
return args;
}
/**
* Map generic model names to Codex-specific models.
*
* Uses the same mapping as tools/agents/providers/codex.mjs:
* opus → gpt-5.3-codex
* sonnet → codex-mini-latest
* haiku → gpt-5-codex-mini
*
* @param {string} genericModel
* @returns {string}
*/
mapModel(genericModel) {
const mapped = MODEL_MAP[genericModel.toLowerCase()];
if (mapped) return mapped;
// Pass through if already a Codex model name or unknown
return genericModel;
}
/**
* Environment overrides for headless Codex sessions.
* @returns {Object<string, string>}
*/
getEnvOverrides() {
return {
CI: 'true',
};
}
/**
* Codex does not store session transcripts in a known location.
* @returns {null}
*/
getTranscriptPath() {
return null;
}
/**
* Parse Codex output - Codex returns plain text, not stream-json.
* We still try to extract JSON from the output for analysis results.
*
* @param {string} stdout
* @returns {Object|null}
*/
parseOutput(stdout) {
// Codex output is plain text. Try to find embedded JSON.
try {
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
if (!jsonMatch) return null;
return JSON.parse(jsonMatch[0]);
} catch {
return null;
}
}
}
// Self-register on import
registerProvider('codex', () => new CodexAdapter());
export default CodexAdapter;