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:
sam hoang
2025-01-12 19:28:25 +07:00
parent 9a2bfcce64
commit d50e075c75
5 changed files with 188 additions and 2 deletions

View File

@@ -550,6 +550,184 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
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
style={{
fontSize: "12px",
@@ -1031,7 +1209,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
return {
selectedProvider: provider,
selectedModelId: apiConfiguration?.openAiModelId || "",
selectedModelInfo: openAiModelInfoSaneDefaults,
selectedModelInfo: apiConfiguration?.openAiCusModelInfo || openAiModelInfoSaneDefaults,
}
case "ollama":
return {