@@ -18,6 +18,7 @@ import (
1818 "os"
1919 "os/signal"
2020 "path/filepath"
21+ "runtime"
2122 "sort"
2223 "strconv"
2324 "strings"
@@ -93,6 +94,8 @@ type manifest struct {
9394 accountByID map [string ]* accountSpec
9495 accountByAuthID map [string ]* accountSpec
9596 accountByAPIKey map [string ]* accountSpec
97+ accountByChatGPT map [string ]* accountSpec
98+ accountByEmail map [string ]* accountSpec
9699 aliasToSource map [string ]string
97100 originalIndexByID map [string ]int
98101}
@@ -127,6 +130,9 @@ type accountSpec struct {
127130 ID string `json:"id"`
128131 Email string `json:"email"`
129132 AuthID string `json:"authId,omitempty"`
133+ AuthKind string `json:"authKind,omitempty"`
134+ AccessTokenOnly bool `json:"accessTokenOnly,omitempty"`
135+ ChatGPTAccountID string `json:"chatgptAccountId,omitempty"`
130136 UpstreamAPIKey string `json:"upstreamApiKey,omitempty"`
131137 PlanRank * int `json:"planRank,omitempty"`
132138 RemainingQuota * int `json:"remainingQuota,omitempty"`
@@ -493,23 +499,42 @@ func loadManifest(path string) (*manifest, error) {
493499 m .accountByID = make (map [string ]* accountSpec )
494500 m .accountByAuthID = make (map [string ]* accountSpec )
495501 m .accountByAPIKey = make (map [string ]* accountSpec )
502+ m .accountByChatGPT = make (map [string ]* accountSpec )
503+ m .accountByEmail = make (map [string ]* accountSpec )
496504 m .originalIndexByID = make (map [string ]int )
497505 for i := range m .Accounts {
498506 account := & m .Accounts [i ]
499507 account .ID = strings .TrimSpace (account .ID )
500508 if account .ID == "" {
501509 continue
502510 }
511+ account .Email = strings .TrimSpace (account .Email )
512+ account .AuthKind = strings .ToLower (strings .TrimSpace (account .AuthKind ))
513+ account .ChatGPTAccountID = strings .TrimSpace (account .ChatGPTAccountID )
503514 m .accountByID [account .ID ] = account
504515 m .originalIndexByID [account .ID ] = i
505516 if authID := strings .TrimSpace (account .AuthID ); authID != "" {
506517 account .AuthID = authID
507518 m .accountByAuthID [strings .ToLower (authID )] = account
519+ if base := filepath .Base (authID ); base != authID {
520+ m .accountByAuthID [strings .ToLower (base )] = account
521+ }
508522 }
509523 if key := strings .TrimSpace (account .UpstreamAPIKey ); key != "" {
510524 account .UpstreamAPIKey = key
511525 m .accountByAPIKey [key ] = account
512526 }
527+ if account .ChatGPTAccountID != "" {
528+ m .accountByChatGPT [strings .ToLower (account .ChatGPTAccountID )] = account
529+ }
530+ if account .Email != "" {
531+ key := strings .ToLower (account .Email )
532+ if existing , exists := m .accountByEmail [key ]; exists && existing != account {
533+ m .accountByEmail [key ] = nil
534+ } else {
535+ m .accountByEmail [key ] = account
536+ }
537+ }
513538 }
514539 m .aliasToSource = make (map [string ]string )
515540 for _ , alias := range m .ModelAliases {
@@ -2530,6 +2555,10 @@ func newSidecarRuntime(ctx context.Context, configPath string, cfg *config.Confi
25302555 cancel ()
25312556 return nil , err
25322557 }
2558+ if err := registerManifestCodexTokenAuths (runtimeCtx , service , cfg , m , manager ); err != nil {
2559+ cancel ()
2560+ return nil , err
2561+ }
25332562 for _ , auth := range manager .List () {
25342563 if auth == nil || ! strings .EqualFold (strings .TrimSpace (auth .Provider ), "codex" ) {
25352564 continue
@@ -2571,6 +2600,187 @@ func registerConfigCodexAPIKeyAuths(ctx context.Context, service *cliproxy.Servi
25712600 return nil
25722601}
25732602
2603+ func registerManifestCodexTokenAuths (
2604+ ctx context.Context ,
2605+ service * cliproxy.Service ,
2606+ cfg * config.Config ,
2607+ m * manifest ,
2608+ manager * coreauth.Manager ,
2609+ ) error {
2610+ if service == nil || cfg == nil || m == nil {
2611+ return nil
2612+ }
2613+ for i := range m .Accounts {
2614+ account := & m .Accounts [i ]
2615+ authID := strings .TrimSpace (account .AuthID )
2616+ if authID == "" || manifestAccountAuthKind (account ) == "api_key" {
2617+ continue
2618+ }
2619+ path := authID
2620+ if ! filepath .IsAbs (path ) {
2621+ path = filepath .Join (cfg .AuthDir , path )
2622+ }
2623+ auth , err := readManifestCodexTokenAuth (account , cfg .AuthDir , path )
2624+ if err != nil {
2625+ return err
2626+ }
2627+ registered , err := service .UpsertRuntimeAuth (coreauth .WithSkipPersist (ctx ), auth )
2628+ if err != nil {
2629+ return fmt .Errorf ("register codex token auth %s: %w" , auth .ID , err )
2630+ }
2631+ linkManifestAccountForAuth (m , registered )
2632+ registerManifestModelsForAuth (manager , m , registered )
2633+ }
2634+ return nil
2635+ }
2636+
2637+ func readManifestCodexTokenAuth (account * accountSpec , authDir , path string ) (* coreauth.Auth , error ) {
2638+ data , err := os .ReadFile (path )
2639+ if err != nil {
2640+ return nil , fmt .Errorf ("read codex token auth file %s: %w" , path , err )
2641+ }
2642+ metadata := make (map [string ]any )
2643+ if err = json .Unmarshal (data , & metadata ); err != nil {
2644+ return nil , fmt .Errorf ("parse codex token auth file %s: %w" , path , err )
2645+ }
2646+ provider := strings .TrimSpace (metadataString (metadata , "type" ))
2647+ if provider == "" {
2648+ provider = "codex"
2649+ }
2650+ if ! strings .EqualFold (provider , "codex" ) {
2651+ return nil , fmt .Errorf ("codex token auth file %s has unsupported provider %q" , path , provider )
2652+ }
2653+ accessToken := firstMetadataString (
2654+ metadata ,
2655+ "personal_access_token" ,
2656+ "at_token" ,
2657+ "access_token" ,
2658+ )
2659+ if accessToken == "" {
2660+ return nil , fmt .Errorf ("codex token auth file %s is missing access_token" , path )
2661+ }
2662+ metadata ["access_token" ] = accessToken
2663+ if strings .TrimSpace (metadataString (metadata , "token_type" )) == "" {
2664+ metadata ["token_type" ] = "Bearer"
2665+ }
2666+ if account != nil &&
2667+ (account .AccessTokenOnly || manifestAccountAuthKind (account ) == "access_token" ) {
2668+ if strings .TrimSpace (metadataString (metadata , "auth_mode" )) == "" {
2669+ metadata ["auth_mode" ] = "personal_access_token"
2670+ }
2671+ if strings .TrimSpace (metadataString (metadata , "openai_auth_mode" )) == "" {
2672+ metadata ["openai_auth_mode" ] = "personal_access_token"
2673+ }
2674+ }
2675+
2676+ info , err := os .Stat (path )
2677+ if err != nil {
2678+ return nil , fmt .Errorf ("stat codex token auth file %s: %w" , path , err )
2679+ }
2680+ id := manifestAuthFileID (authDir , path )
2681+ label := ""
2682+ if account != nil {
2683+ label = strings .TrimSpace (account .Email )
2684+ }
2685+ if label == "" {
2686+ label = firstMetadataString (metadata , "email" , "label" )
2687+ }
2688+ disabled , _ := metadata ["disabled" ].(bool )
2689+ status := coreauth .StatusActive
2690+ if disabled {
2691+ status = coreauth .StatusDisabled
2692+ }
2693+ auth := & coreauth.Auth {
2694+ ID : id ,
2695+ Provider : "codex" ,
2696+ FileName : id ,
2697+ Label : label ,
2698+ Status : status ,
2699+ Disabled : disabled ,
2700+ Attributes : map [string ]string {
2701+ "path" : path ,
2702+ "auth_kind" : manifestAccountAuthKind (account ),
2703+ },
2704+ Metadata : metadata ,
2705+ CreatedAt : info .ModTime (),
2706+ UpdatedAt : info .ModTime (),
2707+ LastRefreshedAt : time.Time {},
2708+ }
2709+ if account != nil {
2710+ auth .Attributes ["account_id" ] = strings .TrimSpace (account .ID )
2711+ if strings .TrimSpace (account .Email ) != "" {
2712+ auth .Attributes ["email" ] = strings .TrimSpace (account .Email )
2713+ }
2714+ if strings .TrimSpace (account .ChatGPTAccountID ) != "" {
2715+ auth .Attributes ["chatgpt_account_id" ] = strings .TrimSpace (account .ChatGPTAccountID )
2716+ }
2717+ }
2718+ if email := firstMetadataString (metadata , "email" ); email != "" {
2719+ auth .Attributes ["email" ] = email
2720+ }
2721+ if proxyURL := firstMetadataString (metadata , "proxy_url" , "proxy-url" ); proxyURL != "" {
2722+ auth .ProxyURL = proxyURL
2723+ }
2724+ coreauth .ApplyCustomHeadersFromMetadata (auth )
2725+ return auth , nil
2726+ }
2727+
2728+ func manifestAccountAuthKind (account * accountSpec ) string {
2729+ if account == nil {
2730+ return ""
2731+ }
2732+ if kind := strings .ToLower (strings .TrimSpace (account .AuthKind )); kind != "" {
2733+ switch kind {
2734+ case "api-key" , "apikey" , "api key" :
2735+ return "api_key"
2736+ case "access-token" , "accesstoken" , "access token" ,
2737+ "personal_access_token" , "pat" , "at" :
2738+ return "access_token"
2739+ default :
2740+ return kind
2741+ }
2742+ }
2743+ if strings .TrimSpace (account .UpstreamAPIKey ) != "" {
2744+ return "api_key"
2745+ }
2746+ if account .AccessTokenOnly {
2747+ return "access_token"
2748+ }
2749+ return "oauth"
2750+ }
2751+
2752+ func manifestAuthFileID (authDir , path string ) string {
2753+ id := path
2754+ if strings .TrimSpace (authDir ) != "" {
2755+ if rel , err := filepath .Rel (authDir , path ); err == nil && strings .TrimSpace (rel ) != "" {
2756+ id = rel
2757+ }
2758+ }
2759+ if runtime .GOOS == "windows" {
2760+ id = strings .ToLower (id )
2761+ }
2762+ return id
2763+ }
2764+
2765+ func metadataString (metadata map [string ]any , key string ) string {
2766+ if metadata == nil {
2767+ return ""
2768+ }
2769+ if raw , ok := metadata [key ].(string ); ok {
2770+ return strings .TrimSpace (raw )
2771+ }
2772+ return ""
2773+ }
2774+
2775+ func firstMetadataString (metadata map [string ]any , keys ... string ) string {
2776+ for _ , key := range keys {
2777+ if value := metadataString (metadata , key ); value != "" {
2778+ return value
2779+ }
2780+ }
2781+ return ""
2782+ }
2783+
25742784func (r * sidecarRuntime ) Execute (ctx context.Context , providers []string , req cliproxyexecutor.Request , opts cliproxyexecutor.Options ) (cliproxyexecutor.Response , error ) {
25752785 if r == nil || r .service == nil {
25762786 return cliproxyexecutor.Response {}, fmt .Errorf ("runtime is not initialized" )
@@ -2626,16 +2836,84 @@ func linkManifestAccountForAuth(m *manifest, auth *coreauth.Auth) {
26262836 if m .accountByAuthID == nil {
26272837 m .accountByAuthID = make (map [string ]* accountSpec )
26282838 }
2629- if _ , exists := m .accountByAuthID [strings .ToLower (auth .ID )]; exists {
2839+ authID := strings .ToLower (strings .TrimSpace (auth .ID ))
2840+ if _ , exists := m .accountByAuthID [authID ]; exists {
2841+ return
2842+ }
2843+ if account := findManifestAccountForAuth (m , auth ); account != nil {
2844+ m .accountByAuthID [authID ] = account
2845+ if base := strings .ToLower (filepath .Base (strings .TrimSpace (auth .ID ))); base != "" && base != authID {
2846+ m .accountByAuthID [base ] = account
2847+ }
26302848 return
26312849 }
2850+ }
2851+
2852+ func findManifestAccountForAuth (m * manifest , auth * coreauth.Auth ) * accountSpec {
2853+ if m == nil || auth == nil {
2854+ return nil
2855+ }
2856+ for _ , candidate := range []string {
2857+ strings .TrimSpace (auth .ID ),
2858+ filepath .Base (strings .TrimSpace (auth .ID )),
2859+ strings .TrimSpace (auth .FileName ),
2860+ filepath .Base (strings .TrimSpace (auth .FileName )),
2861+ } {
2862+ if candidate == "." || candidate == "" {
2863+ continue
2864+ }
2865+ if account := m .accountByAuthID [strings .ToLower (candidate )]; account != nil {
2866+ return account
2867+ }
2868+ }
26322869 if auth .Attributes != nil {
2870+ if path := strings .TrimSpace (auth .Attributes ["path" ]); path != "" {
2871+ if account := m .accountByAuthID [strings .ToLower (path )]; account != nil {
2872+ return account
2873+ }
2874+ if account := m .accountByAuthID [strings .ToLower (filepath .Base (path ))]; account != nil {
2875+ return account
2876+ }
2877+ }
26332878 if key := strings .TrimSpace (auth .Attributes ["api_key" ]); key != "" {
26342879 if account := m .accountByAPIKey [key ]; account != nil {
2635- m .accountByAuthID [strings .ToLower (auth .ID )] = account
2880+ return account
2881+ }
2882+ }
2883+ if accountID := strings .TrimSpace (auth .Attributes ["account_id" ]); accountID != "" {
2884+ if account := m .accountByID [accountID ]; account != nil {
2885+ return account
2886+ }
2887+ }
2888+ if chatGPTID := strings .TrimSpace (auth .Attributes ["chatgpt_account_id" ]); chatGPTID != "" {
2889+ if account := m .accountByChatGPT [strings .ToLower (chatGPTID )]; account != nil {
2890+ return account
2891+ }
2892+ }
2893+ if email := strings .TrimSpace (auth .Attributes ["email" ]); email != "" {
2894+ if account := m .accountByEmail [strings .ToLower (email )]; account != nil {
2895+ return account
26362896 }
26372897 }
26382898 }
2899+ if auth .Metadata != nil {
2900+ for _ , key := range []string {"account_id" , "chatgpt_account_id" } {
2901+ if value := metadataString (auth .Metadata , key ); value != "" {
2902+ if account := m .accountByChatGPT [strings .ToLower (value )]; account != nil {
2903+ return account
2904+ }
2905+ if account := m .accountByID [value ]; account != nil {
2906+ return account
2907+ }
2908+ }
2909+ }
2910+ if email := metadataString (auth .Metadata , "email" ); email != "" {
2911+ if account := m .accountByEmail [strings .ToLower (email )]; account != nil {
2912+ return account
2913+ }
2914+ }
2915+ }
2916+ return nil
26392917}
26402918
26412919func registerManifestModelsForAuth (manager * coreauth.Manager , m * manifest , auth * coreauth.Auth ) {
0 commit comments