Compare commits

...
Author SHA1 Message Date
0xtoshii b8770cee90 reusable components 2025-06-20 18:15:28 -07:00
0xtoshii 61a431867d order 2025-06-20 15:59:27 -07:00
0xtoshii 616aa75a34 Sambanova 2025-06-20 15:57:25 -07:00
0xtoshiiandStvLz 7cb8f9a4ee open ai compatible
Co-authored-by: StvLz <lizarazo.steven@gmail.com>
2025-06-20 15:45:26 -07:00
3 changed files with 456 additions and 401 deletions
+21 -401
View File
@@ -6,7 +6,6 @@ import {
ApiConfiguration,
askSageDefaultURL,
askSageModels,
azureOpenAiDefaultApiVersion,
bedrockDefaultModelId,
bedrockModels,
cerebrasModels,
@@ -18,9 +17,7 @@ import {
mainlandQwenModels,
ModelInfo,
nebiusModels,
openAiModelInfoSaneDefaults,
openAiNativeModels,
sambanovaModels,
vertexGlobalModels,
vertexModels,
xaiModels,
@@ -55,6 +52,8 @@ import { OpenRouterProvider } from "./providers/OpenRouterProvider"
import { MistralProvider } from "./providers/MistralProvider"
import { DeepSeekProvider } from "./providers/DeepSeekProvider"
import { TogetherProvider } from "./providers/TogetherProvider"
import { OpenAICompatibleProvider } from "./providers/OpenAICompatible"
import { SambanovaProvider } from "./providers/SambanovaProvider"
interface ApiOptionsProps {
showModelOptions: boolean
@@ -128,7 +127,6 @@ const ApiOptions = ({
const [vsCodeLmModels, setVsCodeLmModels] = useState<vscodemodels.LanguageModelChatSelector[]>([])
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
const [geminiBaseUrlSelected, setGeminiBaseUrlSelected] = useState(!!apiConfiguration?.geminiBaseUrl)
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpoint)
const [modelConfigurationSelected, setModelConfigurationSelected] = useState(false)
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
@@ -253,36 +251,6 @@ const ApiOptions = ({
)
}
// Debounced function to refresh OpenAI models (prevents excessive API calls while typing)
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
}
}, [])
const debouncedRefreshOpenAiModels = useCallback((baseUrl?: string, apiKey?: string) => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
if (baseUrl && apiKey) {
debounceTimerRef.current = setTimeout(() => {
ModelsServiceClient.refreshOpenAiModels(
OpenAiModelsRequest.create({
baseUrl,
apiKey,
}),
).catch((error) => {
console.error("Failed to refresh OpenAI models:", error)
})
}, 500)
}
}, [])
return (
<div style={{ display: "flex", flexDirection: "column", gap: 5, marginBottom: isPopup ? -10 : 0 }}>
<DropdownContainer className="dropdown-container">
@@ -594,6 +562,24 @@ const ApiOptions = ({
/>
)}
{apiConfiguration && selectedProvider === "openai" && (
<OpenAICompatibleProvider
apiConfiguration={apiConfiguration}
handleInputChange={handleInputChange}
showModelOptions={showModelOptions}
isPopup={isPopup}
/>
)}
{apiConfiguration && selectedProvider === "sambanova" && (
<SambanovaProvider
apiConfiguration={apiConfiguration}
handleInputChange={handleInputChange}
showModelOptions={showModelOptions}
isPopup={isPopup}
/>
)}
{selectedProvider === "bedrock" && (
<div
style={{
@@ -973,341 +959,6 @@ const ApiOptions = ({
</div>
)}
{selectedProvider === "openai" && (
<div>
<VSCodeTextField
value={apiConfiguration?.openAiBaseUrl || ""}
style={{ width: "100%", marginBottom: 10 }}
type="url"
onInput={(e: any) => {
const baseUrl = e.target.value
handleInputChange("openAiBaseUrl")({ target: { value: baseUrl } })
debouncedRefreshOpenAiModels(baseUrl, apiConfiguration?.openAiApiKey)
}}
placeholder={"Enter base URL..."}>
<span style={{ fontWeight: 500 }}>Base URL</span>
</VSCodeTextField>
<VSCodeTextField
value={apiConfiguration?.openAiApiKey || ""}
style={{ width: "100%", marginBottom: 10 }}
type="password"
onInput={(e: any) => {
const apiKey = e.target.value
handleInputChange("openAiApiKey")({ target: { value: apiKey } })
debouncedRefreshOpenAiModels(apiConfiguration?.openAiBaseUrl, apiKey)
}}
placeholder="Enter API Key...">
<span style={{ fontWeight: 500 }}>API Key</span>
</VSCodeTextField>
<VSCodeTextField
value={apiConfiguration?.openAiModelId || ""}
style={{ width: "100%", marginBottom: 10 }}
onInput={handleInputChange("openAiModelId")}
placeholder={"Enter Model ID..."}>
<span style={{ fontWeight: 500 }}>Model ID</span>
</VSCodeTextField>
{/* OpenAI Compatible Custom Headers */}
{(() => {
const headerEntries = Object.entries(apiConfiguration?.openAiHeaders ?? {})
return (
<div style={{ marginBottom: 10 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontWeight: 500 }}>Custom Headers</span>
<VSCodeButton
onClick={() => {
const currentHeaders = { ...(apiConfiguration?.openAiHeaders || {}) }
const headerCount = Object.keys(currentHeaders).length
const newKey = `header${headerCount + 1}`
currentHeaders[newKey] = ""
handleInputChange("openAiHeaders")({
target: {
value: currentHeaders,
},
})
}}>
Add Header
</VSCodeButton>
</div>
<div>
{headerEntries.map(([key, value], index) => (
<div key={index} style={{ display: "flex", gap: 5, marginTop: 5 }}>
<VSCodeTextField
value={key}
style={{ width: "40%" }}
placeholder="Header name"
onInput={(e: any) => {
const currentHeaders = apiConfiguration?.openAiHeaders ?? {}
const newValue = e.target.value
if (newValue && newValue !== key) {
const { [key]: _, ...rest } = currentHeaders
handleInputChange("openAiHeaders")({
target: {
value: {
...rest,
[newValue]: value,
},
},
})
}
}}
/>
<VSCodeTextField
value={value}
style={{ width: "40%" }}
placeholder="Header value"
onInput={(e: any) => {
handleInputChange("openAiHeaders")({
target: {
value: {
...(apiConfiguration?.openAiHeaders ?? {}),
[key]: e.target.value,
},
},
})
}}
/>
<VSCodeButton
appearance="secondary"
onClick={() => {
const { [key]: _, ...rest } = apiConfiguration?.openAiHeaders ?? {}
handleInputChange("openAiHeaders")({
target: {
value: rest,
},
})
}}>
Remove
</VSCodeButton>
</div>
))}
</div>
</div>
)
})()}
<VSCodeCheckbox
checked={azureApiVersionSelected}
onChange={(e: any) => {
const isChecked = e.target.checked === true
setAzureApiVersionSelected(isChecked)
if (!isChecked) {
setApiConfiguration({
...apiConfiguration,
azureApiVersion: "",
})
}
}}>
Set Azure API version
</VSCodeCheckbox>
{azureApiVersionSelected && (
<VSCodeTextField
value={apiConfiguration?.azureApiVersion || ""}
style={{ width: "100%", marginTop: 3 }}
onInput={handleInputChange("azureApiVersion")}
placeholder={`Default: ${azureOpenAiDefaultApiVersion}`}
/>
)}
<div
style={{
color: getAsVar(VSC_DESCRIPTION_FOREGROUND),
display: "flex",
margin: "10px 0",
cursor: "pointer",
alignItems: "center",
}}
onClick={() => setModelConfigurationSelected((val) => !val)}>
<span
className={`codicon ${modelConfigurationSelected ? "codicon-chevron-down" : "codicon-chevron-right"}`}
style={{
marginRight: "4px",
}}></span>
<span
style={{
fontWeight: 700,
textTransform: "uppercase",
}}>
Model Configuration
</span>
</div>
{modelConfigurationSelected && (
<>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
Supports Images
</VSCodeCheckbox>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
Supports browser use
</VSCodeCheckbox>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.isR1FormatRequired}
onChange={(e: any) => {
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo = { ...modelInfo, isR1FormatRequired: isChecked }
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
Enable R1 messages format
</VSCodeCheckbox>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.contextWindow
? apiConfiguration.openAiModelInfo.contextWindow.toString()
: openAiModelInfoSaneDefaults.contextWindow?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.contextWindow = Number(input.target.value)
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
<span style={{ fontWeight: 500 }}>Context Window Size</span>
</VSCodeTextField>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.maxTokens
? apiConfiguration.openAiModelInfo.maxTokens.toString()
: openAiModelInfoSaneDefaults.maxTokens?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.maxTokens = input.target.value
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
</VSCodeTextField>
</div>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.inputPrice
? apiConfiguration.openAiModelInfo.inputPrice.toString()
: openAiModelInfoSaneDefaults.inputPrice?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.inputPrice = input.target.value
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
<span style={{ fontWeight: 500 }}>Input Price / 1M tokens</span>
</VSCodeTextField>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.outputPrice
? apiConfiguration.openAiModelInfo.outputPrice.toString()
: openAiModelInfoSaneDefaults.outputPrice?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.outputPrice = input.target.value
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
<span style={{ fontWeight: 500 }}>Output Price / 1M tokens</span>
</VSCodeTextField>
</div>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.temperature
? apiConfiguration.openAiModelInfo.temperature.toString()
: openAiModelInfoSaneDefaults.temperature?.toString()
}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
// Check if the input ends with a decimal point or has trailing zeros after decimal
const value = input.target.value
const shouldPreserveFormat =
value.endsWith(".") || (value.includes(".") && value.endsWith("0"))
modelInfo.temperature =
value === ""
? openAiModelInfoSaneDefaults.temperature
: shouldPreserveFormat
? value // Keep as string to preserve decimal format
: parseFloat(value)
setApiConfiguration({
...apiConfiguration,
openAiModelInfo: modelInfo,
})
}}>
<span style={{ fontWeight: 500 }}>Temperature</span>
</VSCodeTextField>
</div>
</>
)}
<p
style={{
fontSize: "12px",
marginTop: 3,
color: "var(--vscode-descriptionForeground)",
}}>
<span style={{ color: "var(--vscode-errorForeground)" }}>
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
models. Less capable models may not work as expected.)
</span>
</p>
</div>
)}
{selectedProvider === "requesty" && (
<div>
<VSCodeTextField
@@ -1893,37 +1544,6 @@ const ApiOptions = ({
</div>
)}
{selectedProvider === "sambanova" && (
<div>
<VSCodeTextField
value={apiConfiguration?.sambanovaApiKey || ""}
style={{ width: "100%" }}
type="password"
onInput={handleInputChange("sambanovaApiKey")}
placeholder="Enter API Key...">
<span style={{ fontWeight: 500 }}>SambaNova API Key</span>
</VSCodeTextField>
<p
style={{
fontSize: "12px",
marginTop: 3,
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.
{!apiConfiguration?.sambanovaApiKey && (
<VSCodeLink
href="https://docs.sambanova.ai/cloud/docs/get-started/overview"
style={{
display: "inline",
fontSize: "inherit",
}}>
You can get a SambaNova API key by signing up here.
</VSCodeLink>
)}
</p>
</div>
)}
{selectedProvider === "cerebras" && (
<div>
<VSCodeTextField
@@ -2118,6 +1738,7 @@ const ApiOptions = ({
selectedProvider !== "bedrock" &&
selectedProvider !== "mistral" &&
selectedProvider !== "deepseek" &&
selectedProvider !== "sambanova" &&
showModelOptions && (
<>
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 2} className="dropdown-container">
@@ -2137,7 +1758,6 @@ const ApiOptions = ({
{selectedProvider === "doubao" && createDropdown(doubaoModels)}
{selectedProvider === "asksage" && createDropdown(askSageModels)}
{selectedProvider === "xai" && createDropdown(xaiModels)}
{selectedProvider === "sambanova" && createDropdown(sambanovaModels)}
{selectedProvider === "cerebras" && createDropdown(cerebrasModels)}
{selectedProvider === "nebius" && createDropdown(nebiusModels)}
{selectedProvider === "sapaicore" && createDropdown(sapAiCoreModels)}
@@ -0,0 +1,388 @@
import { ApiConfiguration, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults } from "@shared/api"
import { OpenAiModelsRequest } from "@shared/proto/models"
import { ModelsServiceClient } from "@/services/grpc-client"
import { getAsVar, VSC_DESCRIPTION_FOREGROUND } from "@/utils/vscStyles"
import { VSCodeTextField, VSCodeButton, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { useCallback, useEffect, useRef, useState } from "react"
import { ModelInfoView } from "../common/ModelInfoView"
import { ApiKeyField } from "../common/ApiKeyField"
import { BaseUrlField } from "../common/BaseUrlField"
import { normalizeApiConfiguration } from "../utils/providerUtils"
/**
* Props for the OpenAICompatibleProvider component
*/
interface OpenAICompatibleProviderProps {
apiConfiguration: ApiConfiguration
handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void
showModelOptions: boolean
isPopup?: boolean
}
/**
* The OpenAI Compatible provider configuration component
*/
export const OpenAICompatibleProvider = ({
apiConfiguration,
handleInputChange,
showModelOptions,
isPopup,
}: OpenAICompatibleProviderProps) => {
const [modelConfigurationSelected, setModelConfigurationSelected] = useState(false)
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
// Debounced function to refresh OpenAI models (prevents excessive API calls while typing)
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
}
}, [])
const debouncedRefreshOpenAiModels = useCallback((baseUrl?: string, apiKey?: string) => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
if (baseUrl && apiKey) {
debounceTimerRef.current = setTimeout(() => {
ModelsServiceClient.refreshOpenAiModels(
OpenAiModelsRequest.create({
baseUrl,
apiKey,
}),
).catch((error) => {
console.error("Failed to refresh OpenAI models:", error)
})
}, 500)
}
}, [])
return (
<div>
<VSCodeTextField
value={apiConfiguration?.openAiBaseUrl || ""}
style={{ width: "100%", marginBottom: 10 }}
type="url"
onInput={(e: any) => {
const baseUrl = e.target.value
handleInputChange("openAiBaseUrl")({ target: { value: baseUrl } })
debouncedRefreshOpenAiModels(baseUrl, apiConfiguration?.openAiApiKey)
}}
placeholder={"Enter base URL..."}>
<span style={{ fontWeight: 500 }}>Base URL</span>
</VSCodeTextField>
<ApiKeyField
value={apiConfiguration?.openAiApiKey || ""}
onChange={(e: any) => {
const apiKey = e.target.value
handleInputChange("openAiApiKey")({ target: { value: apiKey } })
debouncedRefreshOpenAiModels(apiConfiguration?.openAiBaseUrl, apiKey)
}}
providerName="OpenAI Compatible"
/>
<VSCodeTextField
value={apiConfiguration?.openAiModelId || ""}
style={{ width: "100%", marginBottom: 10 }}
onInput={handleInputChange("openAiModelId")}
placeholder={"Enter Model ID..."}>
<span style={{ fontWeight: 500 }}>Model ID</span>
</VSCodeTextField>
{/* OpenAI Compatible Custom Headers */}
{(() => {
const headerEntries = Object.entries(apiConfiguration?.openAiHeaders ?? {})
return (
<div style={{ marginBottom: 10 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontWeight: 500 }}>Custom Headers</span>
<VSCodeButton
onClick={() => {
const currentHeaders = { ...(apiConfiguration?.openAiHeaders || {}) }
const headerCount = Object.keys(currentHeaders).length
const newKey = `header${headerCount + 1}`
currentHeaders[newKey] = ""
handleInputChange("openAiHeaders")({
target: {
value: currentHeaders,
},
})
}}>
Add Header
</VSCodeButton>
</div>
<div>
{headerEntries.map(([key, value], index) => (
<div key={index} style={{ display: "flex", gap: 5, marginTop: 5 }}>
<VSCodeTextField
value={key}
style={{ width: "40%" }}
placeholder="Header name"
onInput={(e: any) => {
const currentHeaders = apiConfiguration?.openAiHeaders ?? {}
const newValue = e.target.value
if (newValue && newValue !== key) {
const { [key]: _, ...rest } = currentHeaders
handleInputChange("openAiHeaders")({
target: {
value: {
...rest,
[newValue]: value,
},
},
})
}
}}
/>
<VSCodeTextField
value={value}
style={{ width: "40%" }}
placeholder="Header value"
onInput={(e: any) => {
handleInputChange("openAiHeaders")({
target: {
value: {
...(apiConfiguration?.openAiHeaders ?? {}),
[key]: e.target.value,
},
},
})
}}
/>
<VSCodeButton
appearance="secondary"
onClick={() => {
const { [key]: _, ...rest } = apiConfiguration?.openAiHeaders ?? {}
handleInputChange("openAiHeaders")({
target: {
value: rest,
},
})
}}>
Remove
</VSCodeButton>
</div>
))}
</div>
</div>
)
})()}
<BaseUrlField
value={apiConfiguration?.azureApiVersion}
onChange={(value) => handleInputChange("azureApiVersion")({ target: { value } })}
label="Set Azure API version"
placeholder={`Default: ${azureOpenAiDefaultApiVersion}`}
/>
<div
style={{
color: getAsVar(VSC_DESCRIPTION_FOREGROUND),
display: "flex",
margin: "10px 0",
cursor: "pointer",
alignItems: "center",
}}
onClick={() => setModelConfigurationSelected((val) => !val)}>
<span
className={`codicon ${modelConfigurationSelected ? "codicon-chevron-down" : "codicon-chevron-right"}`}
style={{
marginRight: "4px",
}}></span>
<span
style={{
fontWeight: 700,
textTransform: "uppercase",
}}>
Model Configuration
</span>
</div>
{modelConfigurationSelected && (
<>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
Supports Images
</VSCodeCheckbox>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.supportsImages}
onChange={(e: any) => {
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.supportsImages = isChecked
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
Supports browser use
</VSCodeCheckbox>
<VSCodeCheckbox
checked={!!apiConfiguration?.openAiModelInfo?.isR1FormatRequired}
onChange={(e: any) => {
const isChecked = e.target.checked === true
let modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo = { ...modelInfo, isR1FormatRequired: isChecked }
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
Enable R1 messages format
</VSCodeCheckbox>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.contextWindow
? apiConfiguration.openAiModelInfo.contextWindow.toString()
: openAiModelInfoSaneDefaults.contextWindow?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.contextWindow = Number(input.target.value)
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
<span style={{ fontWeight: 500 }}>Context Window Size</span>
</VSCodeTextField>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.maxTokens
? apiConfiguration.openAiModelInfo.maxTokens.toString()
: openAiModelInfoSaneDefaults.maxTokens?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.maxTokens = input.target.value
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
<span style={{ fontWeight: 500 }}>Max Output Tokens</span>
</VSCodeTextField>
</div>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.inputPrice
? apiConfiguration.openAiModelInfo.inputPrice.toString()
: openAiModelInfoSaneDefaults.inputPrice?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.inputPrice = input.target.value
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
<span style={{ fontWeight: 500 }}>Input Price / 1M tokens</span>
</VSCodeTextField>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.outputPrice
? apiConfiguration.openAiModelInfo.outputPrice.toString()
: openAiModelInfoSaneDefaults.outputPrice?.toString()
}
style={{ flex: 1 }}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
modelInfo.outputPrice = input.target.value
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
<span style={{ fontWeight: 500 }}>Output Price / 1M tokens</span>
</VSCodeTextField>
</div>
<div style={{ display: "flex", gap: 10, marginTop: "5px" }}>
<VSCodeTextField
value={
apiConfiguration?.openAiModelInfo?.temperature
? apiConfiguration.openAiModelInfo.temperature.toString()
: openAiModelInfoSaneDefaults.temperature?.toString()
}
onInput={(input: any) => {
const modelInfo = apiConfiguration?.openAiModelInfo
? apiConfiguration.openAiModelInfo
: { ...openAiModelInfoSaneDefaults }
// Check if the input ends with a decimal point or has trailing zeros after decimal
const value = input.target.value
const shouldPreserveFormat = value.endsWith(".") || (value.includes(".") && value.endsWith("0"))
modelInfo.temperature =
value === ""
? openAiModelInfoSaneDefaults.temperature
: shouldPreserveFormat
? value // Keep as string to preserve decimal format
: parseFloat(value)
handleInputChange("openAiModelInfo")({
target: { value: modelInfo },
})
}}>
<span style={{ fontWeight: 500 }}>Temperature</span>
</VSCodeTextField>
</div>
</>
)}
<p
style={{
fontSize: "12px",
marginTop: 3,
color: "var(--vscode-descriptionForeground)",
}}>
<span style={{ color: "var(--vscode-errorForeground)" }}>
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude models.
Less capable models may not work as expected.)
</span>
</p>
{showModelOptions && (
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
)}
</div>
)
}
@@ -0,0 +1,47 @@
import { ApiConfiguration, sambanovaModels } from "@shared/api"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelSelector } from "../common/ModelSelector"
import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
/**
* Props for the SambanovaProvider component
*/
interface SambanovaProviderProps {
apiConfiguration: ApiConfiguration
handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void
showModelOptions: boolean
isPopup?: boolean
}
/**
* The SambaNova provider configuration component
*/
export const SambanovaProvider = ({ apiConfiguration, handleInputChange, showModelOptions, isPopup }: SambanovaProviderProps) => {
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration)
return (
<div>
<ApiKeyField
value={apiConfiguration?.sambanovaApiKey || ""}
onChange={handleInputChange("sambanovaApiKey")}
providerName="SambaNova"
signupUrl="https://docs.sambanova.ai/cloud/docs/get-started/overview"
/>
{showModelOptions && (
<>
<ModelSelector
models={sambanovaModels}
selectedModelId={selectedModelId}
onChange={handleInputChange("apiModelId")}
label="Model"
/>
<ModelInfoView selectedModelId={selectedModelId} modelInfo={selectedModelInfo} isPopup={isPopup} />
</>
)}
</div>
)
}