@@ -29,6 +29,7 @@ import useAccountsQuotaState from '../accounts/hooks/useAccountsQuotaState';
2929import useAccountsRateLimitState from '../accounts/hooks/useAccountsRateLimitState' ;
3030import useAccountsUsageState from '../accounts/hooks/useAccountsUsageState' ;
3131import { getAccountsPreviewCodexAccounts } from '../accounts/previewData' ;
32+ import type { ApiKeyConfigDraft } from '../accounts/model/accountDetailConfig' ;
3233import {
3334 publishAccountDisabledChange ,
3435 readAccountDisabledOverrides ,
@@ -113,7 +114,7 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
113114 const suppressNextDetailClickRef = useRef ( false ) ;
114115 const { codexQuotaByName, loadCodexQuotas } = useAccountsQuotaState ( trackRequest ) ;
115116 const { accountUsageByID, loadAccountUsage } = useAccountsUsageState ( trackRequest ) ;
116- const { accountRateLimitByID, loadAccountRateLimits } = useAccountsRateLimitState ( trackRequest ) ;
117+ const { accountRateLimitByID, rateLimitStrategies , loadAccountRateLimits } = useAccountsRateLimitState ( trackRequest ) ;
117118
118119 const detailRow = useMemo (
119120 ( ) => orderedRows . find ( ( row ) => row . id === detailRowID ) || null ,
@@ -774,6 +775,90 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
774775 }
775776 }
776777
778+ async function saveDetailConfig ( row : CodexAccountRow , draft : ApiKeyConfigDraft , mappings : CodexModelMappingRow [ ] ) {
779+ if ( ! canEditCodexModelMappings ( row . sourceKind ) || row . sourceKind === 'codex-auth-file' ) {
780+ return ;
781+ }
782+
783+ const normalizedModels = normalizeCodexModelMappingsForProvider ( mappings ) ;
784+ const nextMappings = buildOpenAICompatibleModelMappings ( { models : normalizedModels } ) ;
785+ const nextAPIKey = draft . apiKey . trim ( ) ;
786+ const nextBaseURL = draft . baseUrl . trim ( ) ;
787+ const nextPrefix = draft . prefix . trim ( ) ;
788+ const nextProxyURL = draft . proxyUrl . trim ( ) ;
789+ const nextQuotaCurl = draft . quotaCurl . trim ( ) ;
790+ const nextBillingCurl = draft . billingCurl . trim ( ) ;
791+
792+ if ( browserMode ) {
793+ setOrderedRows ( ( prev ) =>
794+ prev . map ( ( item ) =>
795+ item . id === row . id
796+ ? {
797+ ...item ,
798+ apiKey : nextAPIKey ,
799+ apiKeys : nextAPIKey ? [ nextAPIKey ] : item . apiKeys ,
800+ baseUrl : nextBaseURL ,
801+ prefix : nextPrefix ,
802+ proxyUrl : nextProxyURL ,
803+ quotaCurl : nextQuotaCurl ,
804+ quotaEnabled : Boolean ( draft . quotaEnabled && nextQuotaCurl ) ,
805+ billingCurl : nextBillingCurl ,
806+ billingEnabled : Boolean ( draft . billingEnabled && nextBillingCurl ) ,
807+ modelMappings : nextMappings ,
808+ }
809+ : item ,
810+ ) ,
811+ ) ;
812+ setMessage ( t ( 'codex.account_list_model_mapping_saved' ) ) ;
813+ return ;
814+ }
815+
816+ setPendingMappingID ( row . id ) ;
817+ try {
818+ if ( row . sourceKind === 'openai-compatible' ) {
819+ await trackRequest ( 'UpdateOpenAICompatibleProvider' , { id : row . id , baseUrl : nextBaseURL , models : normalizedModels } , ( ) =>
820+ UpdateOpenAICompatibleProvider (
821+ main . UpdateOpenAICompatibleProviderInput . createFrom ( {
822+ currentName : row . provider ,
823+ name : row . provider ,
824+ baseUrl : nextBaseURL ,
825+ prefix : nextPrefix ,
826+ apiKey : nextAPIKey ,
827+ apiKeys : nextAPIKey ? [ nextAPIKey ] : [ ] ,
828+ proxyUrl : nextProxyURL ,
829+ headers : row . headers || { } ,
830+ models : normalizedModels ,
831+ } ) ,
832+ ) ,
833+ ) ;
834+ } else {
835+ await trackRequest ( 'UpdateCodexAPIKeyConfig' , { id : row . id , baseUrl : nextBaseURL , models : normalizedModels } , ( ) =>
836+ UpdateCodexAPIKeyConfig (
837+ main . UpdateCodexAPIKeyConfigInput . createFrom ( {
838+ id : row . id ,
839+ apiKey : nextAPIKey ,
840+ baseUrl : nextBaseURL ,
841+ prefix : nextPrefix ,
842+ proxyUrl : nextProxyURL ,
843+ models : normalizedModels ,
844+ quotaCurl : nextQuotaCurl ,
845+ quotaEnabled : Boolean ( draft . quotaEnabled && nextQuotaCurl ) ,
846+ billingCurl : nextBillingCurl ,
847+ billingEnabled : Boolean ( draft . billingEnabled && nextBillingCurl ) ,
848+ } ) ,
849+ ) ,
850+ ) ;
851+ }
852+ await reload ( t ( 'codex.account_list_model_mapping_saved' ) ) ;
853+ } catch ( error ) {
854+ console . error ( error ) ;
855+ setMessage ( `${ t ( 'codex.account_list_model_mapping_save_failed' ) } : ${ toErrorMessage ( error ) } ` ) ;
856+ throw error ;
857+ } finally {
858+ setPendingMappingID ( null ) ;
859+ }
860+ }
861+
777862 async function saveModelMappings ( row : CodexAccountRow , mappings : CodexModelMappingRow [ ] ) {
778863 if ( ! canEditCodexModelMappings ( row . sourceKind ) ) {
779864 return ;
@@ -968,6 +1053,8 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
9681053 t = { t }
9691054 quotaState = { detailRowWithModels . quotaKey ? codexQuotaByName [ detailRowWithModels . quotaKey ] : undefined }
9701055 usageSummary = { accountUsageByID [ detailRowWithModels . id ] }
1056+ rateLimitStatus = { accountRateLimitByID [ detailRowWithModels . id ] }
1057+ rateLimitStrategies = { rateLimitStrategies }
9711058 savingMappings = { pendingMappingID === detailRowWithModels . id }
9721059 loadingModelMappings = { loadingAuthFileModelID === detailRowWithModels . id }
9731060 modelMappingError = { authFileModelErrors [ detailRowWithModels . id ] || '' }
@@ -982,6 +1069,8 @@ export default function CodexAccountListFeature({ sidecarStatus }: CodexAccountL
9821069 loadingModelOptions = { loadingOpenAICompatibleModelID === detailRowWithModels . id }
9831070 modelOptionError = { openAICompatibleModelErrors [ detailRowWithModels . id ] || '' }
9841071 onClose = { closeDetail }
1072+ onSaveConfig = { ( draft , mappings ) => saveDetailConfig ( detailRowWithModels , draft , mappings ) }
1073+ onRateLimitRulesChanged = { ( ) => void loadAccountRateLimits ( orderedRows . map ( buildCodexQuotaSummaryAccount ) ) }
9851074 onSaveModelMappings = { ( mappings ) => saveModelMappings ( detailRowWithModels , mappings ) }
9861075 />
9871076 ) : null }
0 commit comments