Skip to content

Commit 54ef05f

Browse files
authored
Support for listing models from OpenAI-compatible providers (RooCodeInc#1197)
* feat: Support for listing models from OpenAI-compatible providers * PR-related change: remove includeStreamOptions * prettier * remove unnecessary code * german + translation tweak
1 parent bda1a0d commit 54ef05f

File tree

7 files changed

+440
-70
lines changed

7 files changed

+440
-70
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,14 @@ export class ClineProvider implements vscode.WebviewViewProvider {
580580
case "refreshOpenRouterModels":
581581
await this.refreshOpenRouterModels()
582582
break
583+
case "refreshOpenAiModels":
584+
const { apiConfiguration } = await this.getState()
585+
const openAiModels = await this.getOpenAiModels(
586+
apiConfiguration.openAiBaseUrl,
587+
apiConfiguration.openAiApiKey,
588+
)
589+
this.postMessageToWebview({ type: "openAiModels", openAiModels })
590+
break
583591
case "openImage":
584592
openImage(message.text!)
585593
break
@@ -839,6 +847,32 @@ export class ClineProvider implements vscode.WebviewViewProvider {
839847
}
840848
}
841849

850+
// OpenAi
851+
852+
async getOpenAiModels(baseUrl?: string, apiKey?: string) {
853+
try {
854+
if (!baseUrl) {
855+
return []
856+
}
857+
858+
if (!URL.canParse(baseUrl)) {
859+
return []
860+
}
861+
862+
const config: Record<string, any> = {}
863+
if (apiKey) {
864+
config["headers"] = { Authorization: `Bearer ${apiKey}` }
865+
}
866+
867+
const response = await axios.get(`${baseUrl}/models`, config)
868+
const modelsArray = response.data?.data?.map((model: any) => model.id) || []
869+
const models = [...new Set<string>(modelsArray)]
870+
return models
871+
} catch (error) {
872+
return []
873+
}
874+
}
875+
842876
// OpenRouter
843877

844878
async handleOpenRouterCallback(code: string) {

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface ExtensionMessage {
2020
| "invoke"
2121
| "partialMessage"
2222
| "openRouterModels"
23+
| "openAiModels"
2324
| "mcpServers"
2425
| "relinquishControl"
2526
| "vsCodeLmModels"
@@ -42,6 +43,7 @@ export interface ExtensionMessage {
4243
filePaths?: string[]
4344
partialMessage?: ClineMessage
4445
openRouterModels?: Record<string, ModelInfo>
46+
openAiModels?: string[]
4547
mcpServers?: McpServer[]
4648
}
4749

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface WebviewMessage {
2525
| "openMention"
2626
| "cancelTask"
2727
| "refreshOpenRouterModels"
28+
| "refreshOpenAiModels"
2829
| "openMcpSettings"
2930
| "restartMcpServer"
3031
| "autoApprovalSettings"

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
3838
import { useExtensionState } from "../../context/ExtensionStateContext"
3939
import { vscode } from "../../utils/vscode"
4040
import VSCodeButtonLink from "../common/VSCodeButtonLink"
41-
import OpenRouterModelPicker, { ModelDescriptionMarkdown } from "./OpenRouterModelPicker"
4241
import styled from "styled-components"
4342
import * as vscodemodels from "vscode"
43+
import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
44+
import OpenAiModelPicker from "./OpenAiModelPicker"
4445

4546
interface ApiOptionsProps {
4647
showModelOptions: boolean
@@ -84,10 +85,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
8485
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
8586

8687
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
87-
setApiConfiguration({
88-
...apiConfiguration,
89-
[field]: event.target.value,
90-
})
88+
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
9189
}
9290

9391
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
@@ -97,10 +95,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
9795
// Poll ollama/lmstudio models
9896
const requestLocalModels = useCallback(() => {
9997
if (selectedProvider === "ollama") {
100-
vscode.postMessage({
101-
type: "requestOllamaModels",
102-
text: apiConfiguration?.ollamaBaseUrl,
103-
})
98+
vscode.postMessage({ type: "requestOllamaModels", text: apiConfiguration?.ollamaBaseUrl })
10499
} else if (selectedProvider === "lmstudio") {
105100
vscode.postMessage({
106101
type: "requestLmStudioModels",
@@ -174,10 +169,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
174169
id="api-provider"
175170
value={selectedProvider}
176171
onChange={handleInputChange("apiProvider")}
177-
style={{
178-
minWidth: 130,
179-
position: "relative",
180-
}}>
172+
style={{ minWidth: 130, position: "relative", zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1 }}>
181173
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
182174
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
183175
<VSCodeOption value="gemini">Google Gemini</VSCodeOption>
@@ -210,10 +202,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
210202
const isChecked = e.target.checked === true
211203
setAnthropicBaseUrlSelected(isChecked)
212204
if (!isChecked) {
213-
setApiConfiguration({
214-
...apiConfiguration,
215-
anthropicBaseUrl: "",
216-
})
205+
setApiConfiguration({ ...apiConfiguration, anthropicBaseUrl: "" })
217206
}
218207
}}>
219208
{t("useCustomBaseUrl")}
@@ -373,12 +362,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
373362
)}
374363

375364
{selectedProvider === "bedrock" && (
376-
<div
377-
style={{
378-
display: "flex",
379-
flexDirection: "column",
380-
gap: 5,
381-
}}>
365+
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
382366
<VSCodeTextField
383367
value={apiConfiguration?.awsAccessKey || ""}
384368
style={{ width: "100%" }}
@@ -444,10 +428,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
444428
checked={apiConfiguration?.awsUseCrossRegionInference || false}
445429
onChange={(e: any) => {
446430
const isChecked = e.target.checked === true
447-
setApiConfiguration({
448-
...apiConfiguration,
449-
awsUseCrossRegionInference: isChecked,
450-
})
431+
setApiConfiguration({ ...apiConfiguration, awsUseCrossRegionInference: isChecked })
451432
}}>
452433
{t("useCrossRegionInference")}
453434
</VSCodeCheckbox>
@@ -463,12 +444,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
463444
)}
464445

465446
{apiConfiguration?.apiProvider === "vertex" && (
466-
<div
467-
style={{
468-
display: "flex",
469-
flexDirection: "column",
470-
gap: 5,
471-
}}>
447+
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
472448
<VSCodeTextField
473449
value={apiConfiguration?.vertexProjectId || ""}
474450
style={{ width: "100%" }}
@@ -558,26 +534,18 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
558534
placeholder={t("enterApiKey")}>
559535
<span style={{ fontWeight: 500 }}>{t("apiKey")}</span>
560536
</VSCodeTextField>
561-
<VSCodeTextField
562-
value={apiConfiguration?.openAiModelId || ""}
563-
style={{ width: "100%" }}
564-
onInput={handleInputChange("openAiModelId")}
565-
placeholder={t("enterModelId")}>
566-
<span style={{ fontWeight: 500 }}>{t("modelId")}</span>
567-
</VSCodeTextField>
537+
<span style={{ fontWeight: 500 }}>{t("model")}</span>
538+
<OpenAiModelPicker />
568539
<VSCodeCheckbox
569540
checked={azureApiVersionSelected}
570541
onChange={(e: any) => {
571542
const isChecked = e.target.checked === true
572543
setAzureApiVersionSelected(isChecked)
573544
if (!isChecked) {
574-
setApiConfiguration({
575-
...apiConfiguration,
576-
azureApiVersion: "",
577-
})
545+
setApiConfiguration({ ...apiConfiguration, azureApiVersion: "" })
578546
}
579547
}}>
580-
{t("useAzureApiVersion")}
548+
{t("setAzureApiVersion")}
581549
</VSCodeCheckbox>
582550
{azureApiVersionSelected && (
583551
<VSCodeTextField
@@ -935,12 +903,7 @@ export const ModelInfoView = ({
935903
].filter(Boolean)
936904

937905
return (
938-
<p
939-
style={{
940-
fontSize: "12px",
941-
marginTop: "2px",
942-
color: "var(--vscode-descriptionForeground)",
943-
}}>
906+
<p style={{ fontSize: "12px", marginTop: "2px", color: "var(--vscode-descriptionForeground)" }}>
944907
{infoItems.map((item, index) => (
945908
<Fragment key={index}>
946909
{item}
@@ -997,11 +960,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
997960
selectedModelId = defaultId
998961
selectedModelInfo = models[defaultId]
999962
}
1000-
return {
1001-
selectedProvider: provider,
1002-
selectedModelId,
1003-
selectedModelInfo,
1004-
}
963+
return { selectedProvider: provider, selectedModelId, selectedModelInfo }
1005964
}
1006965
switch (provider) {
1007966
case "anthropic":

0 commit comments

Comments
 (0)