2020// plus text-to-speech via /v1/audio/speech,
2121// with auto-detection for Azure OpenAI
2222// deployments based on the configured base URL
23- // * provider 'codex' → local Codex CLI subscription imagegen
2423// * provider 'volcengine' → Volcengine Ark async tasks API for
2524// Doubao Seedance 2.0 (video) and Seedream
2625// (image)
5150// so the CLI can exit non-zero and the agent can't silently narrate the
5251// placeholder as the final result.
5352
54- import { mkdir , mkdtemp , readdir , readFile , rm , stat , writeFile } from 'node:fs/promises' ;
53+ import { mkdir , mkdtemp , readFile , rm , stat , writeFile } from 'node:fs/promises' ;
5554import { execFile as execFileCb , spawn } from 'node:child_process' ;
5655import os from 'node:os' ;
5756import path from 'node:path' ;
@@ -69,10 +68,7 @@ import {
6968 modelsForSurface ,
7069} from './models.js' ;
7170import { assertAndFetchExternalAsset } from '../connectionTest.js' ;
72- import { normalizeCodexConfigFile } from '../codex-config-normalize.js' ;
7371import {
74- resolveCodexImagegenEnv ,
75- resolveCodexSubscriptionStatus ,
7672 resolveModelAlias ,
7773 resolveProviderConfig ,
7874} from './config.js' ;
@@ -81,7 +77,6 @@ import {
8177 type ImageGenerationRequestSummary ,
8278} from './image-generation-retry.js' ;
8379import { renderVelaImage , renderVelaVideo } from './vela.js' ;
84- import { codexNeedsDangerFullAccessSandbox } from '../runtimes/defs/codex.js' ;
8580import {
8681 ensureProject ,
8782 kindFor ,
@@ -180,8 +175,6 @@ const NANOBANANA_DEFAULT_MODEL = 'gemini-3.1-flash-image-preview';
180175const NANOBANANA_DEFAULT_IMAGE_SIZE = '1K' ;
181176const IMAGEROUTER_DEFAULT_BASE_URL = 'https://api.imagerouter.io/v1/openai' ;
182177const CUSTOM_IMAGE_MODEL_ID = 'custom-image' ;
183- const CODEX_IMAGE_ORCHESTRATOR_MODEL = 'gpt-5.5' ;
184-
185178const DEFAULT_OUTPUT_BY_SURFACE = {
186179 image : 'image.png' ,
187180 video : 'video.mp4' ,
@@ -570,17 +563,6 @@ export async function generateMedia(args: {
570563 ctx ,
571564 customImageCredentials ,
572565 ) ;
573- const codexSubscriptionModel =
574- ! customImageOverride
575- && surface === 'image'
576- && def . provider === 'openai'
577- && ctx . wireModel === ctx . model
578- ? codexSubscriptionEquivalent ( ctx . model )
579- : null ;
580- const useCodexSubscription =
581- codexSubscriptionModel
582- ? ( await resolveCodexSubscriptionStatus ( projectRoot ) ) . available
583- : false ;
584566 try {
585567 if (
586568 def . provider === 'openai'
@@ -592,18 +574,6 @@ export async function generateMedia(args: {
592574 bytes = result . bytes ;
593575 providerNote = result . providerNote ;
594576 suggestedExt = result . suggestedExt ;
595- } else if ( codexSubscriptionModel && useCodexSubscription ) {
596- providerId = 'codex' ;
597- const result = await renderCodexImage ( {
598- ...ctx ,
599- model : codexSubscriptionModel . id ,
600- wireModel : codexSubscriptionModel . id ,
601- modelDef : codexSubscriptionModel ,
602- provider : findProvider ( 'codex' ) ,
603- } ) ;
604- bytes = result . bytes ;
605- providerNote = result . providerNote ;
606- suggestedExt = result . suggestedExt ;
607577 } else if ( def . provider === 'vela' && surface === 'image' ) {
608578 const result = await renderVelaImage ( ctx ) ;
609579 bytes = result . bytes ;
@@ -622,11 +592,6 @@ export async function generateMedia(args: {
622592 bytes = result . bytes ;
623593 providerNote = result . providerNote ;
624594 suggestedExt = result . suggestedExt ;
625- } else if ( def . provider === 'codex' && surface === 'image' ) {
626- const result = await renderCodexImage ( ctx ) ;
627- bytes = result . bytes ;
628- providerNote = result . providerNote ;
629- suggestedExt = result . suggestedExt ;
630595 } else if (
631596 def . provider === 'openai'
632597 && surface === 'audio'
@@ -928,9 +893,7 @@ function withMediaRequestInit(
928893}
929894
930895const OPENAI_IMAGE_NO_CREDENTIAL_MESSAGE =
931- 'no OpenAI credential - configure an API key in Settings or set OPENAI_API_KEY. ' +
932- "If you're signed into Codex with a ChatGPT subscription, use the codex-gpt-image-2 model instead; " +
933- 'it renders through your local Codex login and needs no OpenAI API key.' ;
896+ 'no OpenAI credential - configure an API key in Settings or set OPENAI_API_KEY.' ;
934897
935898async function renderOpenAIImage ( ctx : MediaContext , credentials : ProviderConfig ) : Promise < RenderResult > {
936899 if ( ! credentials . apiKey ) {
@@ -1017,180 +980,6 @@ async function renderOpenAIImage(ctx: MediaContext, credentials: ProviderConfig)
1017980 } ;
1018981}
1019982
1020- function codexGeneratedImagesRoot ( env : NodeJS . ProcessEnv = process . env ) : string {
1021- const home = env . CODEX_HOME ?. trim ( ) || path . join ( os . homedir ( ) , '.codex' ) ;
1022- const resolvedHome = home . startsWith ( '~/' ) ? path . join ( os . homedir ( ) , home . slice ( 2 ) ) : home ;
1023- return path . resolve ( resolvedHome , 'generated_images' ) ;
1024- }
1025-
1026- function codexImageModelLabel ( model : string ) : string {
1027- return model . startsWith ( 'codex-' ) ? model . slice ( 'codex-' . length ) : model ;
1028- }
1029-
1030- function codexImagePrompt ( ctx : MediaContext ) : string {
1031- const prompt = ctx . prompt || 'A high-quality reference image.' ;
1032- const aspect = ctx . aspect ? `\nAspect ratio: ${ ctx . aspect } .` : '' ;
1033- const prefix = ctx . imageRefs . length > 0
1034- ? '$imagegen Edit the attached reference image:'
1035- : '$imagegen' ;
1036- return `${ prefix } ${ prompt } ${ aspect } ` ;
1037- }
1038-
1039- function codexImagegenArgs ( ctx : MediaContext , generatedRoot : string , env : NodeJS . ProcessEnv ) : string [ ] {
1040- const sandbox = codexNeedsDangerFullAccessSandbox ( )
1041- ? [ '--sandbox' , 'danger-full-access' ]
1042- : [ '--sandbox' , 'workspace-write' , '-c' , 'sandbox_workspace_write.network_access=true' ] ;
1043- const model = env . OD_CODEX_IMAGEGEN_MODEL ?. trim ( ) || CODEX_IMAGE_ORCHESTRATOR_MODEL ;
1044- const args = [
1045- 'exec' ,
1046- '--json' ,
1047- '--skip-git-repo-check' ,
1048- ...sandbox ,
1049- '-C' ,
1050- ctx . projectRoot ,
1051- '--add-dir' ,
1052- generatedRoot ,
1053- '--model' ,
1054- model ,
1055- ] ;
1056- if ( env . OD_CODEX_DISABLE_PLUGINS === '1' ) args . push ( '--disable' , 'plugins' ) ;
1057- for ( const ref of ctx . imageRefs ) args . push ( '-i' , ref . abs ) ;
1058- return args ;
1059- }
1060-
1061- function parseCodexThreadId ( stdout : string ) : string {
1062- for ( const line of stdout . split ( / \r ? \n / ) ) {
1063- if ( ! line . trim ( ) ) continue ;
1064- try {
1065- const obj = JSON . parse ( line ) as { thread_id ?: unknown ; type ?: unknown } ;
1066- if ( obj . type === 'thread.started' && typeof obj . thread_id === 'string' ) {
1067- return obj . thread_id ;
1068- }
1069- } catch {
1070- // Non-JSON progress belongs in stdout on some CLI builds; ignore it.
1071- }
1072- }
1073- throw new Error ( 'codex imagegen did not emit a thread.started thread_id' ) ;
1074- }
1075-
1076- function summarizeCodexImagegenStdout ( stdout : string ) : string {
1077- const messages : string [ ] = [ ] ;
1078- for ( const line of stdout . split ( / \r ? \n / ) ) {
1079- const trimmed = line . trim ( ) ;
1080- if ( ! trimmed ) continue ;
1081- try {
1082- const obj = JSON . parse ( trimmed ) as { item ?: { text ?: unknown } ; text ?: unknown } ;
1083- const text = typeof obj . item ?. text === 'string'
1084- ? obj . item . text
1085- : typeof obj . text === 'string'
1086- ? obj . text
1087- : '' ;
1088- if ( text . trim ( ) ) messages . push ( text . trim ( ) ) ;
1089- } catch {
1090- messages . push ( trimmed ) ;
1091- }
1092- }
1093- return truncate ( messages . join ( '\n' ) , 500 ) ;
1094- }
1095-
1096- function codexImagegenMissingOutputError ( threadDir : string , stdout : string ) : Error {
1097- const summary = summarizeCodexImagegenStdout ( stdout ) ;
1098- const suffix = summary ? ` Codex stdout summary: ${ summary } ` : '' ;
1099- if ( / \b p r e v i e w [ - ] o n l y \b | w i t h o u t s a v i n g | w i t h o u t w r i t i n g | d o e s n o t w r i t e | n o f i l e | b e z p r z e n o s z e n i a / i. test ( summary ) ) {
1100- return new Error (
1101- `Codex imagegen completed in preview-only mode and did not write an image file under ${ threadDir } . Use an API-backed image provider or a Codex CLI build that writes generated_images output.${ suffix } ` ,
1102- ) ;
1103- }
1104- return new Error (
1105- `Codex imagegen completed but did not write an ig_* or call_* image under ${ threadDir } . Use an API-backed image provider or a Codex CLI build that writes generated_images output.${ suffix } ` ,
1106- ) ;
1107- }
1108-
1109- async function readCodexGeneratedImage (
1110- generatedRoot : string ,
1111- threadId : string ,
1112- stdout : string ,
1113- ) : Promise < Buffer > {
1114- const threadDir = path . join ( generatedRoot , threadId ) ;
1115- let entries : string [ ] ;
1116- try {
1117- entries = await readdir ( threadDir ) ;
1118- } catch ( err ) {
1119- if ( ( err as NodeJS . ErrnoException ) ?. code === 'ENOENT' ) {
1120- throw codexImagegenMissingOutputError ( threadDir , stdout ) ;
1121- }
1122- throw err ;
1123- }
1124- const supportedImagePattern = / \. (?: p n g | j p e ? g | w e b p ) $ / i;
1125- const match = entries
1126- . filter ( ( name ) => / ^ i g _ / i. test ( name ) && supportedImagePattern . test ( name ) )
1127- . sort ( ) [ 0 ]
1128- ?? entries
1129- . filter ( ( name ) => / ^ c a l l _ / i. test ( name ) && supportedImagePattern . test ( name ) )
1130- . sort ( ) [ 0 ] ;
1131- if ( ! match ) {
1132- throw codexImagegenMissingOutputError ( threadDir , stdout ) ;
1133- }
1134- const imagePath = path . join ( threadDir , match ) ;
1135- const bytes = await readFile ( imagePath ) ;
1136- if ( process . env . OD_CODEX_KEEP_GENERATED_IMAGES !== '1' ) {
1137- await rm ( threadDir , { recursive : true , force : true } ) ;
1138- }
1139- return bytes ;
1140- }
1141-
1142- async function runCodexImagegen (
1143- ctx : MediaContext ,
1144- generatedRoot : string ,
1145- env : NodeJS . ProcessEnv ,
1146- ) : Promise < { stderr : string ; stdout : string } > {
1147- const codexBin = env . CODEX_BIN ?. trim ( ) || 'codex' ;
1148- const child = spawn ( codexBin , codexImagegenArgs ( ctx , generatedRoot , env ) , {
1149- cwd : ctx . projectRoot ,
1150- env,
1151- stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
1152- } ) ;
1153- const stdout : Buffer [ ] = [ ] ;
1154- const stderr : Buffer [ ] = [ ] ;
1155- const timeoutMs = Number ( process . env . OD_CODEX_IMAGEGEN_TIMEOUT_MS || 300_000 ) ;
1156- return await new Promise ( ( resolve , reject ) => {
1157- const timer = setTimeout ( ( ) => {
1158- child . kill ( 'SIGTERM' ) ;
1159- reject ( new Error ( `codex imagegen timed out after ${ timeoutMs } ms` ) ) ;
1160- } , timeoutMs ) ;
1161- child . stdout . on ( 'data' , ( chunk ) => stdout . push ( Buffer . from ( chunk ) ) ) ;
1162- child . stderr . on ( 'data' , ( chunk ) => stderr . push ( Buffer . from ( chunk ) ) ) ;
1163- child . on ( 'error' , ( err ) => {
1164- clearTimeout ( timer ) ;
1165- reject ( err ) ;
1166- } ) ;
1167- child . on ( 'close' , ( code ) => {
1168- clearTimeout ( timer ) ;
1169- const out = Buffer . concat ( stdout ) . toString ( 'utf8' ) ;
1170- const err = Buffer . concat ( stderr ) . toString ( 'utf8' ) ;
1171- if ( code !== 0 ) reject ( new Error ( `codex imagegen exited ${ code } : ${ truncate ( err || out , 1000 ) } ` ) ) ;
1172- else resolve ( { stdout : out , stderr : err } ) ;
1173- } ) ;
1174- child . stdin . end ( codexImagePrompt ( ctx ) ) ;
1175- } ) ;
1176- }
1177-
1178- async function renderCodexImage ( ctx : MediaContext ) : Promise < RenderResult > {
1179- const env = await resolveCodexImagegenEnv ( ctx . projectRoot ) ;
1180- await normalizeCodexConfigFile ( env ) ;
1181- const generatedRoot = codexGeneratedImagesRoot ( env ) ;
1182- await mkdir ( generatedRoot , { recursive : true } ) ;
1183- const { stdout } = await runCodexImagegen ( ctx , generatedRoot , env ) ;
1184- const threadId = parseCodexThreadId ( stdout ) ;
1185- const bytes = await readCodexGeneratedImage ( generatedRoot , threadId , stdout ) ;
1186- const imageModel = codexImageModelLabel ( ctx . model ) ;
1187- return {
1188- bytes,
1189- providerNote : `codex/${ imageModel } via ${ env . OD_CODEX_IMAGEGEN_MODEL ?. trim ( ) || CODEX_IMAGE_ORCHESTRATOR_MODEL } · ${ ctx . aspect } · ${ bytes . length } bytes` ,
1190- suggestedExt : sniffImageExt ( bytes ) ,
1191- } ;
1192- }
1193-
1194983async function renderImageRouterImage ( ctx : MediaContext , credentials : ProviderConfig ) : Promise < RenderResult > {
1195984 if ( ! credentials . apiKey ) {
1196985 throw new Error (
@@ -1333,11 +1122,6 @@ function customImageOverridesOpenAIModel(
13331122 return model === ctx . model || model === ctx . wireModel ;
13341123}
13351124
1336- function codexSubscriptionEquivalent ( modelId : string ) : MediaModel | null {
1337- const candidate = findMediaModel ( `codex-${ modelId } ` ) ;
1338- return candidate ?. provider === 'codex' ? candidate : null ;
1339- }
1340-
13411125async function parseOpenAICompatibleJson ( resp : Response , providerTag : string ) : Promise < any > {
13421126 const text = await resp . text ( ) ;
13431127 if ( ! resp . ok ) {
0 commit comments