@@ -52,7 +52,6 @@ import {
5252 type CodexRateLimitSnapshot ,
5353 type CodexResetCreditsSummary ,
5454} from "./codexUsageLimits.ts" ;
55- import { CODEX_AUTOMATIC_CYBER_ACCESS_PROGRAM_ID } from "../codexModelOptions.ts" ;
5655import * as HostProcess from "@t3tools/shared/HostProcess" ;
5756import packageJson from "../../package.json" with { type : "json" } ;
5857const isCodexAppServerSpawnError = Schema . is ( CodexErrors . CodexAppServerSpawnError ) ;
@@ -94,6 +93,8 @@ export interface CodexAppServerProviderSnapshot {
9493 readonly version : string | undefined ;
9594 readonly models : ReadonlyArray < ServerProviderModel > | undefined ;
9695 readonly serviceTier ?: string | undefined ;
96+ readonly daybreakEnabled ?: boolean | undefined ;
97+ readonly apiKeyCyberAccessProgramsDisabled ?: boolean | undefined ;
9798 readonly skills : ReadonlyArray < ServerProviderSkill > ;
9899}
99100
@@ -244,24 +245,21 @@ export function mapCodexModelCapabilities(
244245 } ) ;
245246 }
246247 const cyberAccessPrograms = model . availableAccessPrograms ?. cyber ?? [ ] ;
247- // Only Daybreak programmes need a choice; standard-only models keep their usual controls.
248- if ( cyberAccessPrograms . some ( ( program ) => program !== "standard" ) ) {
248+ if ( cyberAccessPrograms . length > 0 ) {
249+ // Codex's own clients request Standard unless Daybreak is on; omitting the field does not.
250+ const defaultProgram = cyberAccessPrograms . includes ( "standard" )
251+ ? "standard"
252+ : cyberAccessPrograms [ 0 ] ;
249253 optionDescriptors . push ( {
250254 id : "cyberAccessProgram" ,
251255 label : "Cyber Access" ,
252256 type : "select" ,
253- options : [
254- {
255- id : CODEX_AUTOMATIC_CYBER_ACCESS_PROGRAM_ID ,
256- label : "Automatic" ,
257- isDefault : true ,
258- } ,
259- ...cyberAccessPrograms . map ( ( program ) => ( {
260- id : program ,
261- label : CYBER_ACCESS_PROGRAM_LABELS [ program ] ?? program ,
262- } ) ) ,
263- ] ,
264- currentValue : CODEX_AUTOMATIC_CYBER_ACCESS_PROGRAM_ID ,
257+ options : cyberAccessPrograms . map ( ( program ) => ( {
258+ id : program ,
259+ label : CYBER_ACCESS_PROGRAM_LABELS [ program ] ?? program ,
260+ ...( program === defaultProgram ? { isDefault : true } : { } ) ,
261+ } ) ) ,
262+ ...( defaultProgram === undefined ? { } : { currentValue : defaultProgram } ) ,
265263 } ) ;
266264 }
267265
@@ -303,6 +301,58 @@ export function applyPreferredCodexDefaultModel(
303301 } ) ;
304302}
305303
304+ /** Mirrors Codex: `daybreak = true` prefers a Daybreak program, Blue before Red. */
305+ export function applyConfiguredCodexCyberAccess (
306+ models : ReadonlyArray < ServerProviderModel > ,
307+ input : {
308+ readonly daybreakEnabled : boolean ;
309+ readonly apiKeyProgramsDisabled : boolean ;
310+ } ,
311+ ) : ReadonlyArray < ServerProviderModel > {
312+ if ( ! input . daybreakEnabled && ! input . apiKeyProgramsDisabled ) return models ;
313+ return models . map ( ( model ) => {
314+ const descriptors = model . capabilities ?. optionDescriptors ;
315+ if ( ! model . capabilities || ! descriptors ) return model ;
316+ return {
317+ ...model ,
318+ capabilities : {
319+ ...model . capabilities ,
320+ // Codex rejects explicit programs in this session, so none can be offered or sent.
321+ optionDescriptors : ( input . apiKeyProgramsDisabled
322+ ? descriptors . filter ( ( descriptor ) => descriptor . id !== "cyberAccessProgram" )
323+ : descriptors
324+ ) . map ( ( descriptor ) => {
325+ if ( descriptor . id !== "cyberAccessProgram" || descriptor . type !== "select" ) {
326+ return descriptor ;
327+ }
328+ const value = [ "daybreakBlue" , "daybreakRed" ] . find ( ( program ) =>
329+ descriptor . options . some ( ( option ) => option . id === program ) ,
330+ ) ;
331+ if ( ! value ) return descriptor ;
332+ return {
333+ ...descriptor ,
334+ currentValue : value ,
335+ options : descriptor . options . map ( ( { isDefault : _isDefault , ...option } ) =>
336+ option . id === value ? { ...option , isDefault : true } : option ,
337+ ) ,
338+ } ;
339+ } ) ,
340+ } ,
341+ } ;
342+ } ) ;
343+ }
344+
345+ /** Applies when a selection names no program, such as older threads and agent-started turns. */
346+ export function codexDefaultCyberAccessProgram (
347+ models : ReadonlyArray < ServerProviderModel > ,
348+ slug : string ,
349+ ) : string | undefined {
350+ const descriptor = models
351+ . find ( ( model ) => model . slug === slug )
352+ ?. capabilities ?. optionDescriptors ?. find ( ( { id } ) => id === "cyberAccessProgram" ) ;
353+ return descriptor ?. type === "select" ? descriptor . currentValue : undefined ;
354+ }
355+
306356function applyConfiguredCodexServiceTier (
307357 models : ReadonlyArray < ServerProviderModel > ,
308358 serviceTier : string | undefined ,
@@ -580,7 +630,7 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
580630
581631 // Optional discovery must finish before the outer timeout can discard the account result.
582632 const deadlineMs = spawnedAtMs + CODEX_AUTH_PROBE_TIMEOUT_MS - CODEX_DISCOVERY_MARGIN_MS ;
583- const [ skillsResponse , models , serviceTier , rateLimits ] = yield * Effect . all (
633+ const [ skillsResponse , models , config , rateLimits ] = yield * Effect . all (
584634 [
585635 withCodexDiscoveryDeadline ( client . request ( "skills/list" , { cwds : [ input . cwd ] } ) , deadlineMs ) ,
586636 accountResponse . account ?. type === "chatgpt"
@@ -597,7 +647,7 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
597647 deadlineMs ,
598648 CODEX_CONFIG_PROBE_TIMEOUT_MS ,
599649 ) . pipe (
600- Effect . map ( ( response ) => response ?. config . service_tier ?? undefined ) ,
650+ Effect . map ( ( response ) => response ?. config ) ,
601651 Effect . catch ( ( ) => Effect . logDebug ( "Codex config read failed." ) . pipe ( Effect . as ( undefined ) ) ) ,
602652 ) ,
603653 // Usage is an enrichment: a failure or a slow answer degrades to "no
@@ -635,7 +685,12 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
635685 ...( rateLimits ? { rateLimits } : { } ) ,
636686 version,
637687 models,
638- serviceTier,
688+ serviceTier : config ?. service_tier ?? undefined ,
689+ daybreakEnabled : config ?. [ "daybreak" ] === true ,
690+ apiKeyCyberAccessProgramsDisabled :
691+ accountResponse . account ?. type === "apiKey" &&
692+ ( config ?. [ "features" ] as { readonly api_key_cyber_access_programs ?: unknown } | undefined )
693+ ?. api_key_cyber_access_programs === false ,
639694 skills : skillsResponse ? parseCodexSkillsListResponse ( skillsResponse , input . cwd ) : [ ] ,
640695 } satisfies CodexAppServerProviderSnapshot ;
641696} ) ;
@@ -893,6 +948,11 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
893948 appendCustomCodexModels ( snapshot . models ?? [ ] , codexSettings . customModels ) ,
894949 applyPreferredCodexDefaultModel ,
895950 ( models ) => applyConfiguredCodexServiceTier ( models , snapshot . serviceTier ) ,
951+ ( models ) =>
952+ applyConfiguredCodexCyberAccess ( models , {
953+ daybreakEnabled : snapshot . daybreakEnabled === true ,
954+ apiKeyProgramsDisabled : snapshot . apiKeyCyberAccessProgramsDisabled === true ,
955+ } ) ,
896956 ) ,
897957 skills : snapshot . skills ,
898958 slashCommands : [
0 commit comments