mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
feat(openai): add custom model info configuration
Adds support for configuring custom OpenAI-compatible model capabilities and pricing, including: Max output tokens Context window size Image/computer use support Input/output token pricing Cache read/write pricing
This commit is contained in:
@@ -108,7 +108,7 @@ export class OpenAiHandler implements ApiHandler, SingleCompletionHandler {
|
|||||||
getModel(): { id: string; info: ModelInfo } {
|
getModel(): { id: string; info: ModelInfo } {
|
||||||
return {
|
return {
|
||||||
id: this.options.openAiModelId ?? "",
|
id: this.options.openAiModelId ?? "",
|
||||||
info: openAiModelInfoSaneDefaults,
|
info: this.options.openAiCusModelInfo ?? openAiModelInfoSaneDefaults,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ type GlobalStateKey =
|
|||||||
| "taskHistory"
|
| "taskHistory"
|
||||||
| "openAiBaseUrl"
|
| "openAiBaseUrl"
|
||||||
| "openAiModelId"
|
| "openAiModelId"
|
||||||
|
| "openAiCusModelInfo"
|
||||||
| "ollamaModelId"
|
| "ollamaModelId"
|
||||||
| "ollamaBaseUrl"
|
| "ollamaBaseUrl"
|
||||||
| "lmStudioModelId"
|
| "lmStudioModelId"
|
||||||
@@ -1198,6 +1199,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
openAiBaseUrl,
|
openAiBaseUrl,
|
||||||
openAiApiKey,
|
openAiApiKey,
|
||||||
openAiModelId,
|
openAiModelId,
|
||||||
|
openAiCusModelInfo,
|
||||||
ollamaModelId,
|
ollamaModelId,
|
||||||
ollamaBaseUrl,
|
ollamaBaseUrl,
|
||||||
lmStudioModelId,
|
lmStudioModelId,
|
||||||
@@ -1231,6 +1233,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
await this.updateGlobalState("openAiBaseUrl", openAiBaseUrl)
|
await this.updateGlobalState("openAiBaseUrl", openAiBaseUrl)
|
||||||
await this.storeSecret("openAiApiKey", openAiApiKey)
|
await this.storeSecret("openAiApiKey", openAiApiKey)
|
||||||
await this.updateGlobalState("openAiModelId", openAiModelId)
|
await this.updateGlobalState("openAiModelId", openAiModelId)
|
||||||
|
await this.updateGlobalState("openAiCusModelInfo", openAiCusModelInfo)
|
||||||
await this.updateGlobalState("ollamaModelId", ollamaModelId)
|
await this.updateGlobalState("ollamaModelId", ollamaModelId)
|
||||||
await this.updateGlobalState("ollamaBaseUrl", ollamaBaseUrl)
|
await this.updateGlobalState("ollamaBaseUrl", ollamaBaseUrl)
|
||||||
await this.updateGlobalState("lmStudioModelId", lmStudioModelId)
|
await this.updateGlobalState("lmStudioModelId", lmStudioModelId)
|
||||||
@@ -1847,6 +1850,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
openAiBaseUrl,
|
openAiBaseUrl,
|
||||||
openAiApiKey,
|
openAiApiKey,
|
||||||
openAiModelId,
|
openAiModelId,
|
||||||
|
openAiCusModelInfo,
|
||||||
ollamaModelId,
|
ollamaModelId,
|
||||||
ollamaBaseUrl,
|
ollamaBaseUrl,
|
||||||
lmStudioModelId,
|
lmStudioModelId,
|
||||||
@@ -1910,6 +1914,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
this.getGlobalState("openAiBaseUrl") as Promise<string | undefined>,
|
this.getGlobalState("openAiBaseUrl") as Promise<string | undefined>,
|
||||||
this.getSecret("openAiApiKey") as Promise<string | undefined>,
|
this.getSecret("openAiApiKey") as Promise<string | undefined>,
|
||||||
this.getGlobalState("openAiModelId") as Promise<string | undefined>,
|
this.getGlobalState("openAiModelId") as Promise<string | undefined>,
|
||||||
|
this.getGlobalState("openAiCusModelInfo") as Promise<ModelInfo | undefined>,
|
||||||
this.getGlobalState("ollamaModelId") as Promise<string | undefined>,
|
this.getGlobalState("ollamaModelId") as Promise<string | undefined>,
|
||||||
this.getGlobalState("ollamaBaseUrl") as Promise<string | undefined>,
|
this.getGlobalState("ollamaBaseUrl") as Promise<string | undefined>,
|
||||||
this.getGlobalState("lmStudioModelId") as Promise<string | undefined>,
|
this.getGlobalState("lmStudioModelId") as Promise<string | undefined>,
|
||||||
@@ -1990,6 +1995,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
openAiBaseUrl,
|
openAiBaseUrl,
|
||||||
openAiApiKey,
|
openAiApiKey,
|
||||||
openAiModelId,
|
openAiModelId,
|
||||||
|
openAiCusModelInfo,
|
||||||
ollamaModelId,
|
ollamaModelId,
|
||||||
ollamaBaseUrl,
|
ollamaBaseUrl,
|
||||||
lmStudioModelId,
|
lmStudioModelId,
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export interface WebviewMessage {
|
|||||||
| "autoApprovalEnabled"
|
| "autoApprovalEnabled"
|
||||||
| "updateCustomMode"
|
| "updateCustomMode"
|
||||||
| "deleteCustomMode"
|
| "deleteCustomMode"
|
||||||
|
| "setOpenAiCusModelInfo"
|
||||||
text?: string
|
text?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
askResponse?: ClineAskResponse
|
askResponse?: ClineAskResponse
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface ApiHandlerOptions {
|
|||||||
openAiBaseUrl?: string
|
openAiBaseUrl?: string
|
||||||
openAiApiKey?: string
|
openAiApiKey?: string
|
||||||
openAiModelId?: string
|
openAiModelId?: string
|
||||||
|
openAiCusModelInfo?: ModelInfo
|
||||||
ollamaModelId?: string
|
ollamaModelId?: string
|
||||||
ollamaBaseUrl?: string
|
ollamaBaseUrl?: string
|
||||||
lmStudioModelId?: string
|
lmStudioModelId?: string
|
||||||
|
|||||||
@@ -550,6 +550,184 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
|||||||
placeholder={`Default: ${azureOpenAiDefaultApiVersion}`}
|
placeholder={`Default: ${azureOpenAiDefaultApiVersion}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Model Info Configuration */}
|
||||||
|
<div style={{ marginTop: 15, padding: 10, border: '1px solid var(--vscode-input-border)', borderRadius: 4 }}>
|
||||||
|
<div style={{ marginBottom: 10 }}>
|
||||||
|
<span style={{ fontWeight: 500, fontSize: '14px' }}>Model Configuration</span>
|
||||||
|
<p style={{ fontSize: '12px', color: 'var(--vscode-descriptionForeground)', margin: '5px 0' }}>
|
||||||
|
Configure the capabilities and pricing for your custom OpenAI-compatible model
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Capabilities Section */}
|
||||||
|
<div style={{ marginBottom: 15 }}>
|
||||||
|
<span style={{ fontWeight: 500, fontSize: '12px', color: 'var(--vscode-descriptionForeground)' }}>Capabilities</span>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 5 }}>
|
||||||
|
<VSCodeTextField
|
||||||
|
value={apiConfiguration?.openAiCusModelInfo?.maxTokens?.toString() || openAiModelInfoSaneDefaults.maxTokens?.toString() || ""}
|
||||||
|
type="text"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
title="Maximum number of tokens the model can generate in a single response"
|
||||||
|
onInput={(e: any) => {
|
||||||
|
const value = parseInt(e.target.value)
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
maxTokens: isNaN(value) ? undefined : value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
placeholder="e.g. 4096">
|
||||||
|
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
|
||||||
|
</VSCodeTextField>
|
||||||
|
|
||||||
|
<VSCodeTextField
|
||||||
|
value={apiConfiguration?.openAiCusModelInfo?.contextWindow?.toString() || openAiModelInfoSaneDefaults.contextWindow?.toString() || ""}
|
||||||
|
type="text"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
title="Total number of tokens (input + output) the model can process in a single request"
|
||||||
|
onInput={(e: any) => {
|
||||||
|
const parsed = parseInt(e.target.value)
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || 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={{ display: "flex", gap: 20, marginTop: 5 }}>
|
||||||
|
<VSCodeCheckbox
|
||||||
|
checked={apiConfiguration?.openAiCusModelInfo?.supportsImages ?? openAiModelInfoSaneDefaults.supportsImages}
|
||||||
|
title="Enable if the model can process and understand images in the input"
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
supportsImages: e.target.checked
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
Supports Images
|
||||||
|
</VSCodeCheckbox>
|
||||||
|
|
||||||
|
<VSCodeCheckbox
|
||||||
|
checked={apiConfiguration?.openAiCusModelInfo?.supportsComputerUse ?? false}
|
||||||
|
title="Enable if the model can interact with the computer (execute commands, modify files, etc.)"
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
supportsComputerUse: e.target.checked
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
Supports Computer Use
|
||||||
|
</VSCodeCheckbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pricing Section */}
|
||||||
|
<div>
|
||||||
|
<span style={{ fontWeight: 500, fontSize: '12px', color: 'var(--vscode-descriptionForeground)' }}>Pricing (USD per million tokens)</span>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 5 }}>
|
||||||
|
{/* Input/Output Prices */}
|
||||||
|
<div style={{ display: "flex", gap: 10 }}>
|
||||||
|
<VSCodeTextField
|
||||||
|
value={apiConfiguration?.openAiCusModelInfo?.inputPrice?.toString() || openAiModelInfoSaneDefaults.inputPrice?.toString() || ""}
|
||||||
|
type="text"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
title="Cost per million tokens in the input/prompt"
|
||||||
|
onChange={(e: any) => {
|
||||||
|
const parsed = parseFloat(e.target.value)
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
inputPrice: e.target.value === "" ? undefined : (isNaN(parsed) ? openAiModelInfoSaneDefaults.inputPrice : parsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
placeholder="e.g. 0.0001">
|
||||||
|
<span style={{ fontWeight: 500 }}>Input Price</span>
|
||||||
|
</VSCodeTextField>
|
||||||
|
|
||||||
|
<VSCodeTextField
|
||||||
|
value={apiConfiguration?.openAiCusModelInfo?.outputPrice?.toString() || openAiModelInfoSaneDefaults.outputPrice?.toString() || ""}
|
||||||
|
type="text"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
title="Cost per million tokens in the model's response"
|
||||||
|
onChange={(e: any) => {
|
||||||
|
const parsed = parseFloat(e.target.value)
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
outputPrice: e.target.value === "" ? undefined : (isNaN(parsed) ? openAiModelInfoSaneDefaults.outputPrice : parsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
placeholder="e.g. 0.0002">
|
||||||
|
<span style={{ fontWeight: 500 }}>Output Price</span>
|
||||||
|
</VSCodeTextField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cache Prices */}
|
||||||
|
<div style={{ display: "flex", gap: 10 }}>
|
||||||
|
<VSCodeTextField
|
||||||
|
value={apiConfiguration?.openAiCusModelInfo?.cacheWritesPrice?.toString() || openAiModelInfoSaneDefaults.cacheWritesPrice?.toString() || ""}
|
||||||
|
type="text"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
title="Cost per million tokens when writing to the prompt cache"
|
||||||
|
onChange={(e: any) => {
|
||||||
|
const parsed = parseFloat(e.target.value)
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
cacheWritesPrice: e.target.value === "" ? undefined : (isNaN(parsed) ? openAiModelInfoSaneDefaults.cacheWritesPrice : parsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
placeholder="e.g. 0.0001">
|
||||||
|
<span style={{ fontWeight: 500 }}>Cache Write Price</span>
|
||||||
|
</VSCodeTextField>
|
||||||
|
|
||||||
|
<VSCodeTextField
|
||||||
|
value={apiConfiguration?.openAiCusModelInfo?.cacheReadsPrice?.toString() || openAiModelInfoSaneDefaults.cacheReadsPrice?.toString() || ""}
|
||||||
|
type="text"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
title="Cost per million tokens when reading from the prompt cache"
|
||||||
|
onChange={(e: any) => {
|
||||||
|
const parsed = parseFloat(e.target.value)
|
||||||
|
setApiConfiguration({
|
||||||
|
...apiConfiguration,
|
||||||
|
openAiCusModelInfo: {
|
||||||
|
...(apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults),
|
||||||
|
cacheReadsPrice: e.target.value === "" ? undefined : (isNaN(parsed) ? openAiModelInfoSaneDefaults.cacheReadsPrice : parsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
placeholder="e.g. 0.00001">
|
||||||
|
<span style={{ fontWeight: 500 }}>Cache Read Price</span>
|
||||||
|
</VSCodeTextField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ /* TODO: model info here */}
|
||||||
|
|
||||||
|
|
||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
@@ -1031,7 +1209,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
|
|||||||
return {
|
return {
|
||||||
selectedProvider: provider,
|
selectedProvider: provider,
|
||||||
selectedModelId: apiConfiguration?.openAiModelId || "",
|
selectedModelId: apiConfiguration?.openAiModelId || "",
|
||||||
selectedModelInfo: openAiModelInfoSaneDefaults,
|
selectedModelInfo: apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults,
|
||||||
}
|
}
|
||||||
case "ollama":
|
case "ollama":
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user