1+ import * as NodeFSP from "node:fs/promises" ;
2+ import * as NodeOS from "node:os" ;
3+ import * as NodePath from "node:path" ;
14import * as DateTime from "effect/DateTime" ;
25import * as Duration from "effect/Duration" ;
36import * as Effect from "effect/Effect" ;
@@ -67,6 +70,7 @@ const CODEX_PRESENTATION = {
6770
6871export interface CodexAppServerProviderSnapshot {
6972 readonly account : CodexSchema . V2GetAccountResponse ;
73+ readonly accountId ?: string ;
7074 readonly rateLimits ?: CodexRateLimitsProbe ;
7175 readonly version : string | undefined ;
7276 readonly models : ReadonlyArray < ServerProviderModel > ;
@@ -404,6 +408,65 @@ export const withCodexAppServerClient = Effect.fn("withCodexAppServerClient")(fu
404408 return { client, initialize } ;
405409} ) ;
406410
411+ const decodeCodexAccountIdentity = Schema . decodeUnknownEffect (
412+ Schema . fromJsonString (
413+ Schema . Struct ( {
414+ tokens : Schema . Struct ( {
415+ account_id : Schema . optionalKey ( Schema . NullOr ( Schema . String ) ) ,
416+ id_token : Schema . optionalKey ( Schema . NullOr ( Schema . String ) ) ,
417+ } ) ,
418+ } ) ,
419+ ) ,
420+ ) ;
421+
422+ const decodeCodexIdTokenIdentity = Schema . decodeUnknownEffect (
423+ Schema . fromJsonString (
424+ Schema . Struct ( {
425+ "https://api.openai.com/auth" : Schema . Struct ( {
426+ chatgpt_account_id : Schema . optionalKey ( Schema . NullOr ( Schema . String ) ) ,
427+ } ) ,
428+ } ) ,
429+ ) ,
430+ ) ;
431+
432+ export function resolveCodexAuthHome (
433+ input : {
434+ readonly homePath ?: string ;
435+ readonly environment ?: NodeJS . ProcessEnv ;
436+ } ,
437+ inheritedEnvironment : NodeJS . ProcessEnv = process . env ,
438+ ) {
439+ return expandHomePath (
440+ input . homePath ?. trim ( ) ||
441+ input . environment ?. CODEX_HOME ||
442+ inheritedEnvironment . CODEX_HOME ||
443+ NodePath . join (
444+ input . environment ?. HOME || inheritedEnvironment . HOME || NodeOS . homedir ( ) ,
445+ ".codex" ,
446+ ) ,
447+ ) ;
448+ }
449+
450+ export const readCodexAccountId = Effect . fn ( "readCodexAccountId" ) ( function * ( homePath : string ) {
451+ return yield * Effect . tryPromise ( ( ) =>
452+ NodeFSP . readFile ( NodePath . join ( homePath , "auth.json" ) , "utf8" ) ,
453+ ) . pipe (
454+ Effect . flatMap ( decodeCodexAccountIdentity ) ,
455+ Effect . flatMap ( ( auth ) => {
456+ const accountId = auth . tokens . account_id ?. trim ( ) ;
457+ if ( accountId ) return Effect . succeed ( accountId ) ;
458+ const payload = auth . tokens . id_token ?. split ( "." ) [ 1 ] ;
459+ if ( ! payload ) return Effect . succeed ( undefined ) ;
460+ return decodeCodexIdTokenIdentity ( Buffer . from ( payload , "base64url" ) . toString ( "utf8" ) ) . pipe (
461+ Effect . map (
462+ ( claims ) => claims [ "https://api.openai.com/auth" ] . chatgpt_account_id ?. trim ( ) || undefined ,
463+ ) ,
464+ ) ;
465+ } ) ,
466+ Effect . orElseSucceed ( ( ) => undefined ) ,
467+ ) ;
468+ } ) ;
469+
407470const probeCodexAppServerProvider = Effect . fn ( "probeCodexAppServerProvider" ) ( function * ( input : {
408471 readonly binaryPath : string ;
409472 readonly homePath ?: string ;
@@ -419,6 +482,12 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
419482 const version = versionMatch ? versionMatch [ 1 ] : undefined ;
420483
421484 const accountResponse = yield * client . request ( "account/read" , { } ) ;
485+ // account/read omits workspace identity. File-backed logins keep it beside
486+ // the credentials in the same home used by this app-server instance.
487+ const accountId =
488+ accountResponse . account ?. type === "chatgpt"
489+ ? yield * readCodexAccountId ( resolveCodexAuthHome ( input ) )
490+ : undefined ;
422491 if ( ! accountResponse . account && accountResponse . requiresOpenaiAuth ) {
423492 return {
424493 account : accountResponse ,
@@ -460,6 +529,7 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
460529
461530 return {
462531 account : accountResponse ,
532+ ...( accountId ? { accountId } : { } ) ,
463533 rateLimits,
464534 version,
465535 models : applyPreferredCodexDefaultModel (
@@ -524,7 +594,10 @@ const makePendingCodexProvider = (
524594 } ) ;
525595 } ) ;
526596
527- function accountProbeStatus ( account : CodexAppServerProviderSnapshot [ "account" ] ) : {
597+ function accountProbeStatus (
598+ account : CodexAppServerProviderSnapshot [ "account" ] ,
599+ accountId ?: string ,
600+ ) : {
528601 readonly status : Exclude < ServerProviderState , "disabled" > ;
529602 readonly auth : ServerProvider [ "auth" ] ;
530603 readonly message ?: string ;
@@ -536,6 +609,7 @@ function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]):
536609 ...( account . account ?. type ? { type : account . account ?. type } : { } ) ,
537610 ...( authLabel ? { label : authLabel } : { } ) ,
538611 ...( authEmail ? { email : authEmail } : { } ) ,
612+ ...( accountId ? { accountId } : { } ) ,
539613 } satisfies ServerProvider [ "auth" ] ;
540614
541615 if ( account . account ) {
@@ -646,7 +720,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
646720 }
647721
648722 const snapshot = probeResult . success . value ;
649- const accountStatus = accountProbeStatus ( snapshot . account ) ;
723+ const accountStatus = accountProbeStatus ( snapshot . account , snapshot . accountId ) ;
650724 const usageLimits =
651725 snapshot . account . account ?. type === "apiKey"
652726 ? makeUnavailableUsageLimits ( { checkedAt, reason : "unsupported" } )
0 commit comments