11import { Checkbox , Dropdown , Pane } from "vscrui"
22import type { DropdownOption } from "vscrui"
33import { VSCodeLink , VSCodeRadio , VSCodeRadioGroup , VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
4+ import CustomProvidersSection from "./CustomProvidersSection"
45import { Fragment , memo , useCallback , useEffect , useMemo , useState } from "react"
56import { useEvent , useInterval } from "react-use"
67import {
78 ApiConfiguration ,
89 ModelInfo ,
10+ CustomProviderConfig ,
911 anthropicDefaultModelId ,
1012 anthropicModels ,
1113 azureOpenAiDefaultApiVersion ,
@@ -153,6 +155,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
153155 { value : "lmstudio" , label : "LM Studio" } ,
154156 { value : "ollama" , label : "Ollama" } ,
155157 { value : "unbound" , label : "Unbound" } ,
158+ { value : "custom" , label : "Custom" } ,
156159 ] }
157160 />
158161 </ div >
@@ -1288,14 +1291,98 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
12881291 </ p >
12891292 </ div >
12901293 ) }
1294+ { selectedProvider === "custom" && (
1295+ < div >
1296+ < CustomProvidersSection
1297+ apiConfiguration = { apiConfiguration || { } }
1298+ providers = { apiConfiguration ?. customProviders || { } }
1299+ activeProvider = { apiConfiguration ?. activeCustomProvider }
1300+ onAddProvider = { ( provider : CustomProviderConfig ) => {
1301+ if ( ! apiConfiguration ) return
1302+ // Update customProviders
1303+ handleInputChange ( "customProviders" ) ( {
1304+ target : {
1305+ value : {
1306+ ...( apiConfiguration . customProviders || { } ) ,
1307+ [ provider . name ] : provider ,
1308+ } ,
1309+ } ,
1310+ } )
1311+ // Set active provider
1312+ handleInputChange ( "activeCustomProvider" ) ( {
1313+ target : { value : provider . name } ,
1314+ } )
1315+ // Set current provider
1316+ handleInputChange ( "customProvider" ) ( {
1317+ target : { value : provider } ,
1318+ } )
1319+ } }
1320+ onDeleteProvider = { ( name ) => {
1321+ if ( ! apiConfiguration ?. customProviders ) return
1322+ const newProviders = { ...apiConfiguration . customProviders }
1323+ delete newProviders [ name ]
12911324
1325+ // Update customProviders
1326+ handleInputChange ( "customProviders" ) ( {
1327+ target : { value : newProviders } ,
1328+ } )
1329+
1330+ // Clear active provider if it was the deleted one
1331+ if ( apiConfiguration . activeCustomProvider === name ) {
1332+ handleInputChange ( "activeCustomProvider" ) ( {
1333+ target : { value : "" } ,
1334+ } )
1335+ handleInputChange ( "customProvider" ) ( {
1336+ target : { value : undefined } ,
1337+ } )
1338+ }
1339+ } }
1340+ onSelectProvider = { ( name ) => {
1341+ if ( ! apiConfiguration ?. customProviders ?. [ name ] ) return
1342+
1343+ // Set active provider
1344+ handleInputChange ( "activeCustomProvider" ) ( {
1345+ target : { value : name } ,
1346+ } )
1347+ // Set current provider
1348+ handleInputChange ( "customProvider" ) ( {
1349+ target : { value : apiConfiguration . customProviders [ name ] } ,
1350+ } )
1351+ } }
1352+ handleInputChange = { ( field ) => ( event ) => {
1353+ const value = event . target . value
1354+
1355+ // Handle setting the active custom provider
1356+ if ( field === "activeCustomProvider" ) {
1357+ handleInputChange ( "activeCustomProvider" ) ( {
1358+ target : { value } ,
1359+ } )
1360+ // Also set the customProvider if it exists
1361+ if ( value && apiConfiguration ?. customProviders ?. [ value ] ) {
1362+ handleInputChange ( "customProvider" ) ( {
1363+ target : { value : apiConfiguration . customProviders [ value ] } ,
1364+ } )
1365+ }
1366+ } else if ( field === "customProvider" ) {
1367+ handleInputChange ( "customProvider" ) ( {
1368+ target : { value } ,
1369+ } )
1370+ }
1371+ } }
1372+ isDescriptionExpanded = { false }
1373+ setIsDescriptionExpanded = { ( ) => { } }
1374+ modelIdErrorMessage = { modelIdErrorMessage }
1375+ apiErrorMessage = { apiErrorMessage }
1376+ />
1377+ </ div >
1378+ ) }
12921379 { selectedProvider === "unbound" && (
12931380 < div >
12941381 < VSCodeTextField
12951382 value = { apiConfiguration ?. unboundApiKey || "" }
12961383 style = { { width : "100%" } }
12971384 type = "password"
1298- onChange = { handleInputChange ( "unboundApiKey" ) }
1385+ onInput = { handleInputChange ( "unboundApiKey" ) }
12991386 placeholder = "Enter API Key..." >
13001387 < span style = { { fontWeight : 500 } } > Unbound API Key</ span >
13011388 </ VSCodeTextField >
@@ -1332,7 +1419,6 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
13321419 { selectedProvider === "glama" && < GlamaModelPicker /> }
13331420
13341421 { selectedProvider === "openrouter" && < OpenRouterModelPicker /> }
1335-
13361422 { selectedProvider !== "glama" &&
13371423 selectedProvider !== "openrouter" &&
13381424 selectedProvider !== "openai" &&
@@ -1520,6 +1606,17 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
15201606 const provider = apiConfiguration ?. apiProvider || "anthropic"
15211607 const modelId = apiConfiguration ?. apiModelId
15221608
1609+ const baseModelInfo : ModelInfo = {
1610+ maxTokens : - 1 ,
1611+ contextWindow : 128000 ,
1612+ supportsImages : false ,
1613+ supportsPromptCache : false ,
1614+ supportsComputerUse : false ,
1615+ inputPrice : 0 ,
1616+ outputPrice : 0 ,
1617+ description : "" ,
1618+ }
1619+
15231620 const getProviderData = ( models : Record < string , ModelInfo > , defaultId : string ) => {
15241621 let selectedModelId : string
15251622 let selectedModelInfo : ModelInfo
@@ -1532,6 +1629,40 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
15321629 }
15331630 return { selectedProvider : provider , selectedModelId, selectedModelInfo }
15341631 }
1632+
1633+ // Handle custom provider first to keep it separate from built-in providers
1634+ if ( provider === "custom" ) {
1635+ const customProvider = apiConfiguration ?. customProvider
1636+ const activeCustomProvider = apiConfiguration ?. activeCustomProvider
1637+
1638+ if ( ! customProvider || ! activeCustomProvider ) {
1639+ return {
1640+ selectedProvider : provider ,
1641+ selectedModelId : "" ,
1642+ selectedModelInfo : {
1643+ ...baseModelInfo ,
1644+ description : "No custom provider selected" ,
1645+ } ,
1646+ }
1647+ }
1648+
1649+ return {
1650+ selectedProvider : provider ,
1651+ selectedModelId : activeCustomProvider ,
1652+ selectedModelInfo : {
1653+ ...baseModelInfo ,
1654+ maxTokens : customProvider . maxTokens ?? - 1 ,
1655+ contextWindow : customProvider . contextWindow ?? 128000 ,
1656+ supportsImages : customProvider . supportsImages ?? false ,
1657+ supportsComputerUse : customProvider . supportsComputerUse ?? false ,
1658+ description : customProvider . name
1659+ ? `Custom provider: ${ customProvider . name } `
1660+ : "Unnamed custom provider" ,
1661+ } ,
1662+ }
1663+ }
1664+
1665+ // Handle other providers
15351666 switch ( provider ) {
15361667 case "anthropic" :
15371668 return getProviderData ( anthropicModels , anthropicDefaultModelId )
0 commit comments