@@ -3,7 +3,13 @@ import { useDebounce, useEvent } from "react-use"
33import { Trans } from "react-i18next"
44import { LanguageModelChatSelector } from "vscode"
55import { Checkbox } from "vscrui"
6- import { VSCodeLink , VSCodeRadio , VSCodeRadioGroup , VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
6+ import {
7+ VSCodeButton ,
8+ VSCodeLink ,
9+ VSCodeRadio ,
10+ VSCodeRadioGroup ,
11+ VSCodeTextField ,
12+ } from "@vscode/webview-ui-toolkit/react"
713import { ExternalLinkIcon } from "@radix-ui/react-icons"
814
915import { ReasoningEffort as ReasoningEffortType } from "@roo/schemas"
@@ -77,7 +83,6 @@ const ApiOptions = ({
7783 const [ anthropicBaseUrlSelected , setAnthropicBaseUrlSelected ] = useState ( ! ! apiConfiguration ?. anthropicBaseUrl )
7884 const [ azureApiVersionSelected , setAzureApiVersionSelected ] = useState ( ! ! apiConfiguration ?. azureApiVersion )
7985 const [ openRouterBaseUrlSelected , setOpenRouterBaseUrlSelected ] = useState ( ! ! apiConfiguration ?. openRouterBaseUrl )
80- const [ openAiHostHeaderSelected , setOpenAiHostHeaderSelected ] = useState ( ! ! apiConfiguration ?. openAiHostHeader )
8186 const [ openAiLegacyFormatSelected , setOpenAiLegacyFormatSelected ] = useState ( ! ! apiConfiguration ?. openAiLegacyFormat )
8287 const [ googleGeminiBaseUrlSelected , setGoogleGeminiBaseUrlSelected ] = useState (
8388 ! ! apiConfiguration ?. googleGeminiBaseUrl ,
@@ -123,7 +128,8 @@ const ApiOptions = ({
123128 values : {
124129 baseUrl : apiConfiguration ?. openAiBaseUrl ,
125130 apiKey : apiConfiguration ?. openAiApiKey ,
126- hostHeader : apiConfiguration ?. openAiHostHeader ,
131+ customHeaders : { } , // Reserved for any additional headers
132+ openAiHeaders : apiConfiguration ?. openAiHeaders ,
127133 } ,
128134 } )
129135 } else if ( selectedProvider === "ollama" ) {
@@ -829,25 +835,90 @@ const ApiOptions = ({
829835 ) }
830836 </ div >
831837
832- < div >
833- < Checkbox
834- checked = { openAiHostHeaderSelected }
835- onChange = { ( checked : boolean ) => {
836- setOpenAiHostHeaderSelected ( checked )
837-
838- if ( ! checked ) {
839- setApiConfigurationField ( "openAiHostHeader" , "" )
840- }
841- } } >
842- { t ( "settings:providers.useHostHeader" ) }
843- </ Checkbox >
844- { openAiHostHeaderSelected && (
845- < VSCodeTextField
846- value = { apiConfiguration ?. openAiHostHeader || "" }
847- onInput = { handleInputChange ( "openAiHostHeader" ) }
848- placeholder = "custom-api-hostname.example.com"
849- className = "w-full mt-1"
850- />
838+ { /* Custom Headers UI */ }
839+ < div className = "mb-4" >
840+ < div className = "flex justify-between items-center mb-2" >
841+ < label className = "block font-medium" > { t ( "settings:providers.customHeaders" ) } </ label >
842+ < VSCodeButton
843+ appearance = "icon"
844+ title = { t ( "settings:common.add" ) }
845+ onClick = { ( ) => {
846+ const currentHeaders = { ...( apiConfiguration ?. openAiHeaders || { } ) }
847+ // Use an empty string as key - user will fill it in
848+ // The key will be properly set when the user types in the field
849+ currentHeaders [ "" ] = ""
850+ handleInputChange ( "openAiHeaders" ) ( {
851+ target : {
852+ value : currentHeaders ,
853+ } ,
854+ } )
855+ } } >
856+ < span className = "codicon codicon-add" > </ span >
857+ </ VSCodeButton >
858+ </ div >
859+ { Object . keys ( apiConfiguration ?. openAiHeaders || { } ) . length === 0 ? (
860+ < div className = "text-sm text-vscode-descriptionForeground" >
861+ { t ( "settings:providers.noCustomHeaders" ) }
862+ </ div >
863+ ) : (
864+ Object . entries ( apiConfiguration ?. openAiHeaders || { } ) . map ( ( [ key , value ] , index ) => (
865+ < div key = { index } className = "flex items-center mb-2" >
866+ < VSCodeTextField
867+ value = { key }
868+ className = "flex-1 mr-2"
869+ placeholder = { t ( "settings:providers.headerName" ) }
870+ onInput = { ( e : any ) => {
871+ const currentHeaders = apiConfiguration ?. openAiHeaders ?? { }
872+ const newValue = e . target . value
873+
874+ if ( newValue && newValue !== key ) {
875+ const { [ key ] : _ , ...rest } = currentHeaders
876+ handleInputChange ( "openAiHeaders" ) ( {
877+ target : {
878+ value : {
879+ ...rest ,
880+ [ newValue ] : value ,
881+ } ,
882+ } ,
883+ } )
884+ }
885+ } }
886+ />
887+ < VSCodeTextField
888+ value = { value }
889+ className = "flex-1 mr-2"
890+ placeholder = { t ( "settings:providers.headerValue" ) }
891+ onInput = { ( e : any ) => {
892+ handleInputChange ( "openAiHeaders" ) ( {
893+ target : {
894+ value : {
895+ ...( apiConfiguration ?. openAiHeaders ?? { } ) ,
896+ [ key ] : e . target . value ,
897+ } ,
898+ } ,
899+ } )
900+ } }
901+ />
902+ < VSCodeButton
903+ appearance = "icon"
904+ title = { t ( "settings:common.remove" ) }
905+ onClick = { ( ) => {
906+ const { [ key ] : _ , ...rest } = apiConfiguration ?. openAiHeaders ?? { }
907+
908+ // Ensure we always maintain an empty object even when removing the last header
909+ // This prevents the field from becoming undefined or null
910+ const newHeaders = Object . keys ( rest ) . length === 0 ? { } : rest
911+
912+ handleInputChange ( "openAiHeaders" ) ( {
913+ target : {
914+ value : newHeaders ,
915+ } ,
916+ } )
917+ } } >
918+ < span className = "codicon codicon-trash" > </ span >
919+ </ VSCodeButton >
920+ </ div >
921+ ) )
851922 ) }
852923 </ div >
853924
0 commit comments