Merge pull request #277 from samhvw8/feat/roo-custom-model-info-openai-compatible

feat: custom model info in openai compatible
This commit is contained in:
Matt Rubens
2025-01-21 11:44:49 -05:00
committed by GitHub
5 changed files with 448 additions and 6 deletions

View File

@@ -108,7 +108,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
getModel(): { id: string; info: ModelInfo } {
return {
id: this.options.openAiModelId ?? "",
info: openAiModelInfoSaneDefaults,
info: this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults,
}
}

View File

@@ -69,6 +69,7 @@ type GlobalStateKey =
| "taskHistory"
| "openAiBaseUrl"
| "openAiModelId"
| "openAiCustomModelInfo"
| "ollamaModelId"
| "ollamaBaseUrl"
| "lmStudioModelId"
@@ -1208,6 +1209,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
openAiBaseUrl,
openAiApiKey,
openAiModelId,
openAiCustomModelInfo,
ollamaModelId,
ollamaBaseUrl,
lmStudioModelId,
@@ -1241,6 +1243,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("openAiBaseUrl", openAiBaseUrl)
await this.storeSecret("openAiApiKey", openAiApiKey)
await this.updateGlobalState("openAiModelId", openAiModelId)
await this.updateGlobalState("openAiCustomModelInfo", openAiCustomModelInfo)
await this.updateGlobalState("ollamaModelId", ollamaModelId)
await this.updateGlobalState("ollamaBaseUrl", ollamaBaseUrl)
await this.updateGlobalState("lmStudioModelId", lmStudioModelId)
@@ -1857,6 +1860,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
openAiBaseUrl,
openAiApiKey,
openAiModelId,
openAiCustomModelInfo,
ollamaModelId,
ollamaBaseUrl,
lmStudioModelId,
@@ -1920,6 +1924,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("openAiBaseUrl") as Promise<string | undefined>,
this.getSecret("openAiApiKey") as Promise<string | undefined>,
this.getGlobalState("openAiModelId") as Promise<string | undefined>,
this.getGlobalState("openAiCustomModelInfo") as Promise<ModelInfo | undefined>,
this.getGlobalState("ollamaModelId") as Promise<string | undefined>,
this.getGlobalState("ollamaBaseUrl") as Promise<string | undefined>,
this.getGlobalState("lmStudioModelId") as Promise<string | undefined>,
@@ -2000,6 +2005,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
openAiBaseUrl,
openAiApiKey,
openAiModelId,
openAiCustomModelInfo,
ollamaModelId,
ollamaBaseUrl,
lmStudioModelId,

View File

@@ -76,6 +76,7 @@ export interface WebviewMessage {
| "autoApprovalEnabled"
| "updateCustomMode"
| "deleteCustomMode"
| "setopenAiCustomModelInfo"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse

View File

@@ -38,6 +38,7 @@ export interface ApiHandlerOptions {
openAiBaseUrl?: string
openAiApiKey?: string
openAiModelId?: string
openAiCustomModelInfo?: ModelInfo
ollamaModelId?: string
ollamaBaseUrl?: string
lmStudioModelId?: string

View File

@@ -1,4 +1,4 @@
import { Checkbox, Dropdown } from "vscrui"
import { Checkbox, Dropdown, Pane } from "vscrui"
import type { DropdownOption } from "vscrui"
import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react"
@@ -45,7 +45,7 @@ interface ApiOptionsProps {
}
const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) => {
const { apiConfiguration, setApiConfiguration, uriScheme, handleInputChange } = useExtensionState()
const { apiConfiguration, uriScheme, handleInputChange } = useExtensionState()
const [ollamaModels, setOllamaModels] = useState<string[]>([])
const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
const [vsCodeLmModels, setVsCodeLmModels] = useState<vscodemodels.LanguageModelChatSelector[]>([])
@@ -166,7 +166,11 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
onChange={(checked: boolean) => {
setAnthropicBaseUrlSelected(checked)
if (!checked) {
setApiConfiguration({ ...apiConfiguration, anthropicBaseUrl: "" })
handleInputChange("anthropicBaseUrl")({
target: {
value: "",
},
})
}
}}>
Use custom base URL
@@ -537,7 +541,11 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
onChange={(checked: boolean) => {
setAzureApiVersionSelected(checked)
if (!checked) {
setApiConfiguration({ ...apiConfiguration, azureApiVersion: "" })
handleInputChange("azureApiVersion")({
target: {
value: "",
},
})
}
}}>
Set Azure API version
@@ -550,6 +558,432 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
placeholder={`Default: ${azureOpenAiDefaultApiVersion}`}
/>
)}
<div
style={{
marginTop: 15,
}}
/>
<Pane
title="Model Configuration"
open={false}
actions={[
{
iconName: "refresh",
onClick: () =>
handleInputChange("openAiCustomModelInfo")({
target: { value: openAiModelInfoSaneDefaults },
}),
},
]}>
<div
style={{
padding: 15,
backgroundColor: "var(--vscode-editor-background)",
}}>
<p
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: "0 0 15px 0",
lineHeight: "1.4",
}}>
Configure the capabilities and pricing for your custom OpenAI-compatible model. <br />
Be careful for the model capabilities, as they can affect how Roo Code can work.
</p>
{/* Capabilities Section */}
<div
style={{
marginBottom: 20,
padding: 12,
backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
borderRadius: 4,
}}>
<span
style={{
fontWeight: 500,
fontSize: "12px",
display: "block",
marginBottom: 12,
color: "var(--vscode-editor-foreground)",
}}>
Model Capabilities
</span>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div className="token-config-field">
<VSCodeTextField
value={
apiConfiguration?.openAiCustomModelInfo?.maxTokens?.toString() ||
openAiModelInfoSaneDefaults.maxTokens?.toString() ||
""
}
type="text"
style={{
width: "100%",
borderColor: (() => {
const value = apiConfiguration?.openAiCustomModelInfo?.maxTokens
if (!value) return "var(--vscode-input-border)"
return value > 0
? "var(--vscode-charts-green)"
: "var(--vscode-errorForeground)"
})(),
}}
title="Maximum number of tokens the model can generate in a single response"
onChange={(e: any) => {
const value = parseInt(e.target.value)
handleInputChange("openAiCustomModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCustomModelInfo ||
openAiModelInfoSaneDefaults),
maxTokens: isNaN(value) ? undefined : value,
},
},
})
}}
placeholder="e.g. 4096">
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
</VSCodeTextField>
<div
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginTop: 4,
display: "flex",
alignItems: "center",
gap: 4,
}}>
<i className="codicon codicon-info" style={{ fontSize: "12px" }}></i>
<span>
Maximum number of tokens the model can generate in a response. <br />
(-1 is depend on server)
</span>
</div>
</div>
<div className="token-config-field">
<VSCodeTextField
value={
apiConfiguration?.openAiCustomModelInfo?.contextWindow?.toString() ||
openAiModelInfoSaneDefaults.contextWindow?.toString() ||
""
}
type="text"
style={{
width: "100%",
borderColor: (() => {
const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow
if (!value) return "var(--vscode-input-border)"
return value > 0
? "var(--vscode-charts-green)"
: "var(--vscode-errorForeground)"
})(),
}}
title="Total number of tokens (input + output) the model can process in a single request"
onChange={(e: any) => {
const parsed = parseInt(e.target.value)
handleInputChange("openAiCustomModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCustomModelInfo ||
openAiModelInfoSaneDefaults),
contextWindow:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.contextWindow
: parsed,
},
},
})
}}
placeholder="e.g. 128000">
<span style={{ fontWeight: 500 }}>Context Window Size</span>
</VSCodeTextField>
<div
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginTop: 4,
display: "flex",
alignItems: "center",
gap: 4,
}}>
<i className="codicon codicon-info" style={{ fontSize: "12px" }}></i>
<span>
Total tokens (input + output) the model can process. This will help Roo
Code run correctly.
</span>
</div>
</div>
<div
style={{
backgroundColor: "var(--vscode-editor-background)",
padding: "12px",
borderRadius: "4px",
marginTop: "8px",
border: "1px solid var(--vscode-input-border)",
transition: "background-color 0.2s ease",
}}>
<span
style={{
fontSize: "11px",
fontWeight: 500,
color: "var(--vscode-editor-foreground)",
display: "block",
marginBottom: "10px",
}}>
Model Features
</span>
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
<div className="feature-toggle">
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Checkbox
checked={
apiConfiguration?.openAiCustomModelInfo?.supportsImages ??
openAiModelInfoSaneDefaults.supportsImages
}
onChange={(checked: boolean) => {
handleInputChange("openAiCustomModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCustomModelInfo ||
openAiModelInfoSaneDefaults),
supportsImages: checked,
},
},
})
}}>
<span style={{ fontWeight: 500 }}>Image Support</span>
</Checkbox>
<i
className="codicon codicon-info"
title="Enable if the model can process and understand images in the input. Required for image-based assistance and visual code understanding."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
<p
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginLeft: "24px",
marginTop: "4px",
lineHeight: "1.4",
}}>
Allows the model to analyze and understand images, essential for
visual code assistance
</p>
</div>
<div
className="feature-toggle"
style={{
borderTop: "1px solid var(--vscode-input-border)",
paddingTop: "12px",
}}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Checkbox
checked={
apiConfiguration?.openAiCustomModelInfo
?.supportsComputerUse ?? false
}
onChange={(checked: boolean) => {
handleInputChange("openAiCustomModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCustomModelInfo ||
openAiModelInfoSaneDefaults),
supportsComputerUse: checked,
},
},
})
}}>
<span style={{ fontWeight: 500 }}>Computer Use</span>
</Checkbox>
<i
className="codicon codicon-info"
title="Enable if the model can interact with your computer through commands and file operations. Required for automated tasks and file modifications."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
<p
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
marginLeft: "24px",
marginTop: "4px",
lineHeight: "1.4",
}}>
This model feature is for computer use like sonnet 3.5 support
</p>
</div>
</div>
</div>
</div>
</div>
{/* Pricing Section */}
<div
style={{
backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
padding: "12px",
borderRadius: "4px",
marginTop: "15px",
}}>
<div style={{ marginBottom: "12px" }}>
<span
style={{
fontWeight: 500,
fontSize: "12px",
color: "var(--vscode-editor-foreground)",
display: "block",
marginBottom: "4px",
}}>
Model Pricing
</span>
<span
style={{
fontSize: "11px",
color: "var(--vscode-descriptionForeground)",
display: "block",
}}>
Configure token-based pricing in USD per million tokens
</span>
</div>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
backgroundColor: "var(--vscode-editor-background)",
padding: "12px",
borderRadius: "4px",
}}>
<div className="price-input">
<VSCodeTextField
value={
apiConfiguration?.openAiCustomModelInfo?.inputPrice?.toString() ??
openAiModelInfoSaneDefaults.inputPrice?.toString() ??
""
}
type="text"
style={{
width: "100%",
borderColor: (() => {
const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice
if (!value && value !== 0) return "var(--vscode-input-border)"
return value >= 0
? "var(--vscode-charts-green)"
: "var(--vscode-errorForeground)"
})(),
}}
onChange={(e: any) => {
const parsed = parseFloat(e.target.value)
handleInputChange("openAiCustomModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCustomModelInfo ??
openAiModelInfoSaneDefaults),
inputPrice:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.inputPrice
: parsed,
},
},
})
}}
placeholder="e.g. 0.0001">
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<span style={{ fontWeight: 500 }}>Input Price</span>
<i
className="codicon codicon-info"
title="Cost per million tokens in the input/prompt. This affects the cost of sending context and instructions to the model."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
</VSCodeTextField>
</div>
<div className="price-input">
<VSCodeTextField
value={
apiConfiguration?.openAiCustomModelInfo?.outputPrice?.toString() ||
openAiModelInfoSaneDefaults.outputPrice?.toString() ||
""
}
type="text"
style={{
width: "100%",
borderColor: (() => {
const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice
if (!value && value !== 0) return "var(--vscode-input-border)"
return value >= 0
? "var(--vscode-charts-green)"
: "var(--vscode-errorForeground)"
})(),
}}
onChange={(e: any) => {
const parsed = parseFloat(e.target.value)
handleInputChange("openAiCustomModelInfo")({
target: {
value: {
...(apiConfiguration?.openAiCustomModelInfo ||
openAiModelInfoSaneDefaults),
outputPrice:
e.target.value === ""
? undefined
: isNaN(parsed)
? openAiModelInfoSaneDefaults.outputPrice
: parsed,
},
},
})
}}
placeholder="e.g. 0.0002">
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<span style={{ fontWeight: 500 }}>Output Price</span>
<i
className="codicon codicon-info"
title="Cost per million tokens in the model's response. This affects the cost of generated content and completions."
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
cursor: "help",
}}
/>
</div>
</VSCodeTextField>
</div>
</div>
</div>
</div>
</Pane>
<div
style={{
marginTop: 15,
}}
/>
{/* end Model Info Configuration */}
<p
style={{
fontSize: "12px",
@@ -1031,7 +1465,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.openAiModelId || "",
selectedModelInfo: openAiModelInfoSaneDefaults,
selectedModelInfo: apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults,
}
case "ollama":
return {