Compare commits

...
18 changed files with 719 additions and 188 deletions
+1 -1
View File
@@ -313,7 +313,7 @@ message ApiConfiguration {
message UpdateApiConfigurationRequestNew {
Metadata metadata = 1;
ApiConfiguration updates = 2;
// Required field mask specifying which fields to update.
// Field paths use dot notation with camelCase field names:
// - "options.ulid" (for options fields)
@@ -1,6 +1,8 @@
import { anthropicModels, CLAUDE_SONNET_1M_SUFFIX } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { BaseUrlField } from "../common/BaseUrlField"
import { ContextWindowSwitcher } from "../common/ContextWindowSwitcher"
@@ -8,7 +10,6 @@ import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
// Anthropic models that support thinking/reasoning mode
export const SUPPORTED_ANTHROPIC_THINKING_MODELS = [
@@ -36,21 +37,43 @@ interface AnthropicProviderProps {
*/
export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: AnthropicProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
// Helper function for model switching
const handleModelChange = (modelId: string) => {
handleModeFieldChange({ plan: "planModeApiModelId", act: "actModeApiModelId" }, modelId, currentMode)
const handleModelChange = async (modelId: string) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: modelId } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: modelId } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
return (
<div>
<ApiKeyField
initialValue={apiConfiguration?.apiKey || ""}
onChange={(value) => handleFieldChange("apiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
apiKey: value,
},
},
updateMask: ["secrets.apiKey"],
}),
)
}}
providerName="Anthropic"
signupUrl="https://console.anthropic.com/settings/keys"
/>
@@ -58,7 +81,18 @@ export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: An
<BaseUrlField
initialValue={apiConfiguration?.anthropicBaseUrl}
label="Use custom base URL"
onChange={(value) => handleFieldChange("anthropicBaseUrl", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
anthropicBaseUrl: value,
},
},
updateMask: ["options.anthropicBaseUrl"],
}),
)
}}
placeholder="Default: https://api.anthropic.com"
/>
@@ -67,13 +101,23 @@ export const AnthropicProvider = ({ showModelOptions, isPopup, currentMode }: An
<ModelSelector
label="Model"
models={anthropicModels}
onChange={(e) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
/>
@@ -1,8 +1,9 @@
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import BasetenModelPicker from "../BasetenModelPicker"
import { ApiKeyField } from "../common/ApiKeyField"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the BasetenProvider component
@@ -18,13 +19,23 @@ interface BasetenProviderProps {
*/
export const BasetenProvider = ({ showModelOptions, isPopup, currentMode }: BasetenProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
return (
<div>
<ApiKeyField
initialValue={apiConfiguration?.basetenApiKey || ""}
onChange={(value) => handleFieldChange("basetenApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
basetenApiKey: value,
},
},
updateMask: ["secrets.basetenApiKey"],
}),
)
}}
providerName="Baseten"
signupUrl="https://app.baseten.co/settings/api_keys"
/>
@@ -1,16 +1,17 @@
import { bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import BedrockData from "@shared/providers/bedrock.json"
import { Mode } from "@shared/storage/types"
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption, VSCodeRadio, VSCodeRadioGroup } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
import { DropdownContainer } from "../common/ModelSelector"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { getModeSpecificFields, normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
const CLAUDE_MODELS = [
"anthropic.claude-3-7-sonnet-20250219-v1:0",
@@ -36,7 +37,6 @@ interface BedrockProviderProps {
export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: BedrockProviderProps) => {
const { apiConfiguration, remoteConfigSettings } = useExtensionState()
const { handleFieldChange, handleModeFieldChange, handleModeFieldsChange } = useApiConfigurationHandlers()
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
const modeFields = getModeSpecificFields(apiConfiguration, currentMode)
@@ -45,9 +45,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
return (
<div className="flex flex-col gap-1">
<VSCodeRadioGroup
onChange={(e) => {
onChange={async (e) => {
const value = (e.target as HTMLInputElement)?.value
handleFieldChange("awsAuthentication", value)
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsAuthentication: value,
},
},
updateMask: ["options.awsAuthentication"],
}),
)
}}
value={apiConfiguration?.awsAuthentication ?? (apiConfiguration?.awsProfile ? "profile" : "credentials")}>
<VSCodeRadio value="apikey">API Key</VSCodeRadio>
@@ -61,7 +70,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
className="w-full"
initialValue={apiConfiguration?.awsProfile ?? ""}
key="profile"
onChange={(value) => handleFieldChange("awsProfile", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsProfile: value,
},
},
updateMask: ["options.awsProfile"],
}),
)
}}
placeholder="Enter profile name (default if empty)">
<span className="font-medium">AWS Profile Name</span>
</DebouncedTextField>
@@ -70,7 +90,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
className="w-full"
initialValue={apiConfiguration?.awsBedrockApiKey ?? ""}
key="apikey"
onChange={(value) => handleFieldChange("awsBedrockApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
awsBedrockApiKey: value,
},
},
updateMask: ["secrets.awsBedrockApiKey"],
}),
)
}}
placeholder="Enter Bedrock Api Key"
type="password">
<span className="font-medium">AWS Bedrock Api Key</span>
@@ -81,7 +112,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
className="w-full"
initialValue={apiConfiguration?.awsAccessKey || ""}
key="accessKey"
onChange={(value) => handleFieldChange("awsAccessKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
awsAccessKey: value,
},
},
updateMask: ["secrets.awsAccessKey"],
}),
)
}}
placeholder="Enter Access Key..."
type="password">
<span className="font-medium">AWS Access Key</span>
@@ -89,7 +131,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<DebouncedTextField
className="w-full"
initialValue={apiConfiguration?.awsSecretKey || ""}
onChange={(value) => handleFieldChange("awsSecretKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
awsSecretKey: value,
},
},
updateMask: ["secrets.awsSecretKey"],
}),
)
}}
placeholder="Enter Secret Key..."
type="password">
<span className="font-medium">AWS Secret Key</span>
@@ -97,7 +150,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<DebouncedTextField
className="w-full"
initialValue={apiConfiguration?.awsSessionToken || ""}
onChange={(value) => handleFieldChange("awsSessionToken", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
awsSessionToken: value,
},
},
updateMask: ["secrets.awsSessionToken"],
}),
)
}}
placeholder="Enter Session Token..."
type="password">
<span className="font-medium">AWS Session Token</span>
@@ -123,7 +187,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
className="w-full"
disabled={remoteConfigSettings?.awsRegion !== undefined}
id="aws-region-dropdown"
onChange={(e: any) => handleFieldChange("awsRegion", e.target.value)}
onChange={async (e: any) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsRegion: e.target.value,
},
},
updateMask: ["options.awsRegion"],
}),
)
}}
value={apiConfiguration?.awsRegion || ""}>
<VSCodeOption value="">Select a region...</VSCodeOption>
{/* The user will have to choose a region that supports the model they use, but this shouldn't be a problem since they'd have to request access for it in that region in the first place. */}
@@ -147,11 +222,20 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeCheckbox
checked={awsEndpointSelected}
disabled={remoteConfigSettings?.awsBedrockEndpoint !== undefined}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
setAwsEndpointSelected(isChecked)
if (!isChecked) {
handleFieldChange("awsBedrockEndpoint", "")
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsBedrockEndpoint: "",
},
},
updateMask: ["options.awsBedrockEndpoint"],
}),
)
}
}}>
Use custom VPC endpoint
@@ -166,7 +250,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
className="mt-0.5 mb-1 text-sm text-description"
disabled={remoteConfigSettings?.awsBedrockEndpoint !== undefined}
initialValue={apiConfiguration?.awsBedrockEndpoint || ""}
onChange={(value) => handleFieldChange("awsBedrockEndpoint", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsBedrockEndpoint: value,
},
},
updateMask: ["options.awsBedrockEndpoint"],
}),
)
}}
placeholder="Enter VPC Endpoint URL (optional)"
type="text"
/>
@@ -183,10 +278,19 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeCheckbox
checked={apiConfiguration?.awsUseCrossRegionInference || false}
disabled={remoteConfigSettings?.awsUseCrossRegionInference !== undefined}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsUseCrossRegionInference", isChecked)
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsUseCrossRegionInference: isChecked,
},
},
updateMask: ["options.awsUseCrossRegionInference"],
}),
)
}}>
Use cross-region inference
</VSCodeCheckbox>
@@ -207,9 +311,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeCheckbox
checked={apiConfiguration?.awsUseGlobalInference || false}
disabled={remoteConfigSettings?.awsUseGlobalInference !== undefined}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsUseGlobalInference", isChecked)
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsUseGlobalInference: isChecked,
},
},
updateMask: ["options.awsUseGlobalInference"],
}),
)
}}>
Use global inference profile
</VSCodeCheckbox>
@@ -231,9 +344,18 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeCheckbox
checked={apiConfiguration?.awsBedrockUsePromptCache || false}
disabled={remoteConfigSettings?.awsBedrockUsePromptCache !== undefined}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
handleFieldChange("awsBedrockUsePromptCache", isChecked)
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
awsBedrockUsePromptCache: isChecked,
},
},
updateMask: ["options.awsBedrockUsePromptCache"],
}),
)
}}>
Use prompt caching
</VSCodeCheckbox>
@@ -261,27 +383,41 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeDropdown
className="w-full"
id="bedrock-model-dropdown"
onChange={(e: any) => {
onChange={async (e: any) => {
const isCustom = e.target.value === "custom"
handleModeFieldsChange(
{
apiModelId: { plan: "planModeApiModelId", act: "actModeApiModelId" },
awsBedrockCustomSelected: {
plan: "planModeAwsBedrockCustomSelected",
act: "actModeAwsBedrockCustomSelected",
},
awsBedrockCustomModelBaseId: {
plan: "planModeAwsBedrockCustomModelBaseId",
act: "actModeAwsBedrockCustomModelBaseId",
},
},
{
apiModelId: isCustom ? "" : e.target.value,
awsBedrockCustomSelected: isCustom,
awsBedrockCustomModelBaseId: bedrockDefaultModelId,
},
currentMode,
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: {
options: {
planModeApiModelId: isCustom ? "" : e.target.value,
planModeAwsBedrockCustomSelected: isCustom,
planModeAwsBedrockCustomModelBaseId: bedrockDefaultModelId,
},
},
updateMask: [
"options.planModeApiModelId",
"options.planModeAwsBedrockCustomSelected",
"options.planModeAwsBedrockCustomModelBaseId",
],
}
: {
updates: {
options: {
actModeApiModelId: isCustom ? "" : e.target.value,
actModeAwsBedrockCustomSelected: isCustom,
actModeAwsBedrockCustomModelBaseId: bedrockDefaultModelId,
},
},
updateMask: [
"options.actModeApiModelId",
"options.actModeAwsBedrockCustomSelected",
"options.actModeAwsBedrockCustomModelBaseId",
],
},
),
)
}}
value={modeFields.awsBedrockCustomSelected ? "custom" : selectedModelId}>
@@ -308,13 +444,21 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
className="w-full mt-0.5"
id="bedrock-model-input"
initialValue={modeFields.apiModelId || ""}
onChange={(value) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
value,
currentMode,
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
placeholder="Enter custom model ID...">
<span className="font-medium">Model ID</span>
</DebouncedTextField>
@@ -325,16 +469,25 @@ export const BedrockProvider = ({ showModelOptions, isPopup, currentMode }: Bedr
<VSCodeDropdown
className="w-full"
id="bedrock-base-model-dropdown"
onChange={(e: any) =>
handleModeFieldChange(
{
plan: "planModeAwsBedrockCustomModelBaseId",
act: "actModeAwsBedrockCustomModelBaseId",
},
e.target.value,
currentMode,
onChange={async (e: any) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: {
options: { planModeAwsBedrockCustomModelBaseId: e.target.value },
},
updateMask: ["options.planModeAwsBedrockCustomModelBaseId"],
}
: {
updates: {
options: { actModeAwsBedrockCustomModelBaseId: e.target.value },
},
updateMask: ["options.actModeAwsBedrockCustomModelBaseId"],
},
),
)
}
}}
value={modeFields.awsBedrockCustomModelBaseId || bedrockDefaultModelId}>
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(bedrockModels).map((modelId) => (
@@ -1,12 +1,13 @@
import { claudeCodeModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { SUPPORTED_ANTHROPIC_THINKING_MODELS } from "./AnthropicProvider"
/**
@@ -23,7 +24,6 @@ interface ClaudeCodeProviderProps {
*/
export const ClaudeCodeProvider = ({ showModelOptions, isPopup, currentMode }: ClaudeCodeProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -32,7 +32,18 @@ export const ClaudeCodeProvider = ({ showModelOptions, isPopup, currentMode }: C
<div>
<DebouncedTextField
initialValue={apiConfiguration?.claudeCodePath || ""}
onChange={(value) => handleFieldChange("claudeCodePath", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
claudeCodePath: value,
},
},
updateMask: ["options.claudeCodePath"],
}),
)
}}
placeholder="Default: claude"
style={{ width: "100%", marginTop: 3 }}
type="text">
@@ -53,13 +64,23 @@ export const ClaudeCodeProvider = ({ showModelOptions, isPopup, currentMode }: C
<ModelSelector
label="Model"
models={claudeCodeModels}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
/>
@@ -1,11 +1,12 @@
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ClineAccountInfoCard } from "../ClineAccountInfoCard"
import { DropdownContainer } from "../common/ModelSelector"
import OpenRouterModelPicker, { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../OpenRouterModelPicker"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the ClineProvider component
@@ -21,7 +22,6 @@ interface ClineProviderProps {
*/
export const ClineProvider = ({ showModelOptions, isPopup, currentMode }: ClineProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const [providerSortingSelected, setProviderSortingSelected] = useState(!!apiConfiguration?.openRouterProviderSorting)
@@ -37,11 +37,20 @@ export const ClineProvider = ({ showModelOptions, isPopup, currentMode }: ClineP
{/* Provider Sorting Options */}
<VSCodeCheckbox
checked={providerSortingSelected}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
setProviderSortingSelected(isChecked)
if (!isChecked) {
handleFieldChange("openRouterProviderSorting", "")
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
openRouterProviderSorting: "",
},
},
updateMask: ["options.openRouterProviderSorting"],
}),
)
}
}}
style={{ marginTop: -10 }}>
@@ -52,8 +61,17 @@ export const ClineProvider = ({ showModelOptions, isPopup, currentMode }: ClineP
<div style={{ marginBottom: -6 }}>
<DropdownContainer className="dropdown-container" zIndex={OPENROUTER_MODEL_PICKER_Z_INDEX + 1}>
<VSCodeDropdown
onChange={(e: any) => {
handleFieldChange("openRouterProviderSorting", e.target.value)
onChange={async (e: any) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
openRouterProviderSorting: e.target.value,
},
},
updateMask: ["options.openRouterProviderSorting"],
}),
)
}}
style={{ width: "100%", marginTop: 3 }}
value={apiConfiguration?.openRouterProviderSorting}>
@@ -1,11 +1,12 @@
import { deepSeekModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the DeepSeekProvider component
@@ -21,7 +22,6 @@ interface DeepSeekProviderProps {
*/
export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: DeepSeekProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -30,7 +30,18 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: Dee
<div>
<ApiKeyField
initialValue={apiConfiguration?.deepSeekApiKey || ""}
onChange={(value) => handleFieldChange("deepSeekApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
deepSeekApiKey: value,
},
},
updateMask: ["secrets.deepSeekApiKey"],
}),
)
}}
providerName="DeepSeek"
signupUrl="https://www.deepseek.com/"
/>
@@ -40,13 +51,23 @@ export const DeepSeekProvider = ({ showModelOptions, isPopup, currentMode }: Dee
<ModelSelector
label="Model"
models={deepSeekModels}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
/>
@@ -1,11 +1,11 @@
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
import { useDebouncedInput } from "../utils/useDebouncedInput"
interface DifyProviderProps {
showModelOptions: boolean
@@ -15,16 +15,6 @@ interface DifyProviderProps {
export const DifyProvider = ({ showModelOptions, isPopup, currentMode }: DifyProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
// Use debounced input for proper state management
const [baseUrlValue, setBaseUrlValue] = useDebouncedInput(apiConfiguration?.difyBaseUrl || "", (value) =>
handleFieldChange("difyBaseUrl", value),
)
const [apiKeyValue, setApiKeyValue] = useDebouncedInput(apiConfiguration?.difyApiKey || "", (value) =>
handleFieldChange("difyApiKey", value),
)
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -34,8 +24,17 @@ export const DifyProvider = ({ showModelOptions, isPopup, currentMode }: DifyPro
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<DebouncedTextField
initialValue={apiConfiguration?.difyBaseUrl || ""}
onChange={(value) => {
handleFieldChange("difyBaseUrl", value)
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
difyBaseUrl: value,
},
},
updateMask: ["options.difyBaseUrl"],
}),
)
}}
placeholder={"Enter base URL..."}
style={{ width: "100%", marginBottom: 10 }}
@@ -45,8 +44,17 @@ export const DifyProvider = ({ showModelOptions, isPopup, currentMode }: DifyPro
<ApiKeyField
initialValue={apiConfiguration?.difyApiKey || ""}
onChange={(value) => {
handleFieldChange("difyApiKey", value)
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
difyApiKey: value,
},
},
updateMask: ["secrets.difyApiKey"],
}),
)
}}
providerName="Dify"
/>
@@ -1,13 +1,14 @@
import { geminiModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { BaseUrlField } from "../common/BaseUrlField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
// Gemini models that support thinking/reasoning mode
const SUPPORTED_THINKING_MODELS = ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite-preview-06-17"]
@@ -26,7 +27,6 @@ interface GeminiProviderProps {
*/
export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: GeminiProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -35,7 +35,18 @@ export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: Gemin
<div>
<ApiKeyField
initialValue={apiConfiguration?.geminiApiKey || ""}
onChange={(value) => handleFieldChange("geminiApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
geminiApiKey: value,
},
},
updateMask: ["secrets.geminiApiKey"],
}),
)
}}
providerName="Gemini"
signupUrl="https://aistudio.google.com/apikey"
/>
@@ -43,7 +54,18 @@ export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: Gemin
<BaseUrlField
initialValue={apiConfiguration?.geminiBaseUrl}
label="Use custom base URL"
onChange={(value) => handleFieldChange("geminiBaseUrl", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
geminiBaseUrl: value,
},
},
updateMask: ["options.geminiBaseUrl"],
}),
)
}}
placeholder="Default: https://generativelanguage.googleapis.com"
/>
@@ -52,13 +74,23 @@ export const GeminiProvider = ({ showModelOptions, isPopup, currentMode }: Gemin
<ModelSelector
label="Model"
models={geminiModels}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
/>
@@ -1,8 +1,9 @@
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import GroqModelPicker from "../GroqModelPicker"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the GroqProvider component
@@ -18,13 +19,23 @@ interface GroqProviderProps {
*/
export const GroqProvider = ({ showModelOptions, isPopup, currentMode }: GroqProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
return (
<div>
<ApiKeyField
initialValue={apiConfiguration?.groqApiKey || ""}
onChange={(value) => handleFieldChange("groqApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
groqApiKey: value,
},
},
updateMask: ["secrets.groqApiKey"],
}),
)
}}
providerName="Groq"
signupUrl="https://console.groq.com/keys"
/>
@@ -1,9 +1,10 @@
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { HuggingFaceModelPicker } from "../HuggingFaceModelPicker"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the HuggingFaceProvider component
@@ -19,7 +20,6 @@ interface HuggingFaceProviderProps {
*/
export const HuggingFaceProvider = ({ showModelOptions, isPopup, currentMode }: HuggingFaceProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -28,7 +28,18 @@ export const HuggingFaceProvider = ({ showModelOptions, isPopup, currentMode }:
<div>
<DebouncedTextField
initialValue={apiConfiguration?.huggingFaceApiKey || ""}
onChange={(value) => handleFieldChange("huggingFaceApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
huggingFaceApiKey: value,
},
},
updateMask: ["secrets.huggingFaceApiKey"],
}),
)
}}
placeholder="Enter API Key..."
style={{ width: "100%" }}
type="password">
@@ -1,11 +1,12 @@
import { mistralModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the MistralProvider component
@@ -21,7 +22,6 @@ interface MistralProviderProps {
*/
export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: MistralProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -30,7 +30,18 @@ export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: Mist
<div>
<ApiKeyField
initialValue={apiConfiguration?.mistralApiKey || ""}
onChange={(value) => handleFieldChange("mistralApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
mistralApiKey: value,
},
},
updateMask: ["secrets.mistralApiKey"],
}),
)
}}
providerName="Mistral"
signupUrl="https://console.mistral.ai/codestral"
/>
@@ -40,13 +51,23 @@ export const MistralProvider = ({ showModelOptions, isPopup, currentMode }: Mist
<ModelSelector
label="Model"
models={mistralModels}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
/>
@@ -1,4 +1,5 @@
import { StringRequest } from "@shared/proto/cline/common"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { useCallback, useEffect, useState } from "react"
@@ -11,7 +12,6 @@ import { BaseUrlField } from "../common/BaseUrlField"
import { DebouncedTextField } from "../common/DebouncedTextField"
import OllamaModelPicker from "../OllamaModelPicker"
import { getModeSpecificFields } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the OllamaProvider component
@@ -27,7 +27,6 @@ interface OllamaProviderProps {
*/
export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: OllamaProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
const { ollamaModelId } = getModeSpecificFields(apiConfiguration, currentMode)
@@ -61,7 +60,18 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
<BaseUrlField
initialValue={apiConfiguration?.ollamaBaseUrl}
label="Use custom base URL"
onChange={(value) => handleFieldChange("ollamaBaseUrl", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
ollamaBaseUrl: value,
},
},
updateMask: ["options.ollamaBaseUrl"],
}),
)
}}
placeholder="Default: http://localhost:11434"
/>
@@ -69,7 +79,18 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
<ApiKeyField
helpText="Optional API key for authenticated Ollama instances or cloud services. Leave empty for local installations."
initialValue={apiConfiguration?.ollamaApiKey || ""}
onChange={(value) => handleFieldChange("ollamaApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
ollamaApiKey: value,
},
},
updateMask: ["secrets.ollamaApiKey"],
}),
)
}}
placeholder="Enter API Key (optional)..."
providerName="Ollama"
/>
@@ -81,8 +102,20 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
</label>
<OllamaModelPicker
ollamaModels={ollamaModels}
onModelChange={(modelId) => {
handleModeFieldChange({ plan: "planModeOllamaModelId", act: "actModeOllamaModelId" }, modelId, currentMode)
onModelChange={async (modelId) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeOllamaModelId: modelId } },
updateMask: ["options.planModeOllamaModelId"],
}
: {
updates: { options: { actModeOllamaModelId: modelId } },
updateMask: ["options.actModeOllamaModelId"],
},
),
)
}}
placeholder={ollamaModels.length > 0 ? "Search and select a model..." : "e.g. llama3.1"}
selectedModelId={ollamaModelId || ""}
@@ -98,7 +131,18 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
<DebouncedTextField
initialValue={apiConfiguration?.ollamaApiOptionsCtxNum || "32768"}
onChange={(v) => handleFieldChange("ollamaApiOptionsCtxNum", v || undefined)}
onChange={async (v) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
ollamaApiOptionsCtxNum: v || undefined,
},
},
updateMask: ["options.ollamaApiOptionsCtxNum"],
}),
)
}}
placeholder={"e.g. 32768"}
style={{ width: "100%" }}>
<span className="font-semibold">Model Context Window</span>
@@ -108,11 +152,20 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
<>
<DebouncedTextField
initialValue={apiConfiguration?.requestTimeoutMs ? apiConfiguration.requestTimeoutMs.toString() : "30000"}
onChange={(value) => {
onChange={async (value) => {
// Convert to number, with validation
const numValue = parseInt(value, 10)
if (!Number.isNaN(numValue) && numValue > 0) {
handleFieldChange("requestTimeoutMs", numValue)
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
requestTimeoutMs: numValue,
},
},
updateMask: ["options.requestTimeoutMs"],
}),
)
}
}}
placeholder="Default: 30000 (30 seconds)"
@@ -1,11 +1,12 @@
import { openAiNativeModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the OpenAINativeProvider component
@@ -21,7 +22,6 @@ interface OpenAINativeProviderProps {
*/
export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }: OpenAINativeProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -30,7 +30,18 @@ export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }:
<div>
<ApiKeyField
initialValue={apiConfiguration?.openAiNativeApiKey || ""}
onChange={(value) => handleFieldChange("openAiNativeApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
openAiNativeApiKey: value,
},
},
updateMask: ["secrets.openAiNativeApiKey"],
}),
)
}}
providerName="OpenAI"
signupUrl="https://platform.openai.com/api-keys"
/>
@@ -40,13 +51,23 @@ export const OpenAINativeProvider = ({ showModelOptions, isPopup, currentMode }:
<ModelSelector
label="Model"
models={openAiNativeModels}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
/>
@@ -1,15 +1,15 @@
import { EmptyRequest } from "@shared/proto/cline/common"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { VSCodeButton, VSCodeCheckbox, VSCodeDropdown, VSCodeLink, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient } from "@/services/grpc-client"
import { AccountServiceClient, ModelsServiceClient } from "@/services/grpc-client"
import { useOpenRouterKeyInfo } from "../../ui/hooks/useOpenRouterKeyInfo"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { DropdownContainer } from "../common/ModelSelector"
import OpenRouterModelPicker, { OPENROUTER_MODEL_PICKER_Z_INDEX } from "../OpenRouterModelPicker"
import { formatPrice } from "../utils/pricingUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Component to display OpenRouter balance information
@@ -61,7 +61,6 @@ interface OpenRouterProviderProps {
*/
export const OpenRouterProvider = ({ showModelOptions, isPopup, currentMode }: OpenRouterProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const [providerSortingSelected, setProviderSortingSelected] = useState(!!apiConfiguration?.openRouterProviderSorting)
@@ -70,7 +69,18 @@ export const OpenRouterProvider = ({ showModelOptions, isPopup, currentMode }: O
<div>
<DebouncedTextField
initialValue={apiConfiguration?.openRouterApiKey || ""}
onChange={(value) => handleFieldChange("openRouterApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
openRouterApiKey: value,
},
},
updateMask: ["secrets.openRouterApiKey"],
}),
)
}}
placeholder="Enter API Key..."
style={{ width: "100%" }}
type="password">
@@ -109,11 +119,20 @@ export const OpenRouterProvider = ({ showModelOptions, isPopup, currentMode }: O
<>
<VSCodeCheckbox
checked={providerSortingSelected}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
setProviderSortingSelected(isChecked)
if (!isChecked) {
handleFieldChange("openRouterProviderSorting", "")
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
openRouterProviderSorting: "",
},
},
updateMask: ["options.openRouterProviderSorting"],
}),
)
}
}}
style={{ marginTop: -10 }}>
@@ -124,8 +143,17 @@ export const OpenRouterProvider = ({ showModelOptions, isPopup, currentMode }: O
<div style={{ marginBottom: -6 }}>
<DropdownContainer className="dropdown-container" zIndex={OPENROUTER_MODEL_PICKER_Z_INDEX + 1}>
<VSCodeDropdown
onChange={(e: any) => {
handleFieldChange("openRouterProviderSorting", e.target.value)
onChange={async (e: any) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
openRouterProviderSorting: e.target.value,
},
},
updateMask: ["options.openRouterProviderSorting"],
}),
)
}}
style={{ width: "100%", marginTop: 3 }}
value={apiConfiguration?.openRouterProviderSorting}>
@@ -1,11 +1,12 @@
import { qwenCodeModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the QwenCodeProvider component
@@ -21,7 +22,6 @@ interface QwenCodeProviderProps {
*/
export const QwenCodeProvider = ({ showModelOptions, isPopup, currentMode }: QwenCodeProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -30,7 +30,18 @@ export const QwenCodeProvider = ({ showModelOptions, isPopup, currentMode }: Qwe
<div>
<h3 style={{ color: "var(--vscode-foreground)", margin: "8px 0" }}>Qwen Code API Configuration</h3>
<VSCodeTextField
onInput={(e: any) => handleFieldChange("qwenCodeOauthPath", e.target.value)}
onInput={async (e: any) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
qwenCodeOauthPath: e.target.value,
},
},
updateMask: ["options.qwenCodeOauthPath"],
}),
)
}}
placeholder="~/.qwen/oauth_creds.json"
style={{ width: "100%" }}
value={apiConfiguration?.qwenCodeOauthPath || ""}>
@@ -71,9 +82,20 @@ export const QwenCodeProvider = ({ showModelOptions, isPopup, currentMode }: Qwe
<ModelSelector
label="Model"
models={qwenCodeModels}
onChange={(modelId) => {
const fieldName = currentMode === "plan" ? "planModeApiModelId" : "actModeApiModelId"
handleFieldChange(fieldName, modelId)
onChange={async (modelId) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: modelId } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: modelId } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}}
selectedModelId={selectedModelId}
/>
@@ -1,14 +1,14 @@
import { toRequestyServiceUrl } from "@shared/clients/requesty"
import { StringRequest } from "@shared/proto/cline/common"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import { Mode } from "@shared/storage/types"
import { VSCodeButton, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { AccountServiceClient } from "@/services/grpc-client"
import { AccountServiceClient, ModelsServiceClient } from "@/services/grpc-client"
import { ApiKeyField } from "../common/ApiKeyField"
import { DebouncedTextField } from "../common/DebouncedTextField"
import RequestyModelPicker from "../RequestyModelPicker"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the RequestyProvider component
@@ -24,7 +24,6 @@ interface RequestyProviderProps {
*/
export const RequestyProvider = ({ showModelOptions, isPopup, currentMode }: RequestyProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange } = useApiConfigurationHandlers()
const [requestyEndpointSelected, setRequestyEndpointSelected] = useState(!!apiConfiguration?.requestyBaseUrl)
@@ -35,7 +34,18 @@ export const RequestyProvider = ({ showModelOptions, isPopup, currentMode }: Req
<div style={{ display: "flex", flexDirection: "column" }}>
<ApiKeyField
initialValue={apiConfiguration?.requestyApiKey || ""}
onChange={(value) => handleFieldChange("requestyApiKey", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
secrets: {
requestyApiKey: value,
},
},
updateMask: ["secrets.requestyApiKey"],
}),
)
}}
providerName="Requesty"
signupUrl={apiKeyUrl}
/>
@@ -59,12 +69,21 @@ export const RequestyProvider = ({ showModelOptions, isPopup, currentMode }: Req
)}
<VSCodeCheckbox
checked={requestyEndpointSelected}
onChange={(e: any) => {
onChange={async (e: any) => {
const isChecked = e.target.checked === true
setRequestyEndpointSelected(isChecked)
if (!isChecked) {
handleFieldChange("requestyBaseUrl", undefined)
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
requestyBaseUrl: undefined,
},
},
updateMask: ["options.requestyBaseUrl"],
}),
)
}
}}>
Use custom base URL
@@ -72,12 +91,17 @@ export const RequestyProvider = ({ showModelOptions, isPopup, currentMode }: Req
{requestyEndpointSelected && (
<DebouncedTextField
initialValue={apiConfiguration?.requestyBaseUrl ?? ""}
onChange={(value) => {
if (value.length === 0) {
handleFieldChange("requestyBaseUrl", undefined)
} else {
handleFieldChange("requestyBaseUrl", value)
}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
requestyBaseUrl: value.length === 0 ? undefined : value,
},
},
updateMask: ["options.requestyBaseUrl"],
}),
)
}}
placeholder="Custom base URL"
style={{ width: "100%", marginBottom: 5 }}
@@ -1,15 +1,16 @@
import { vertexGlobalModels, vertexModels } from "@shared/api"
import { UpdateApiConfigurationRequestNew } from "@shared/proto/index.cline"
import VertexData from "@shared/providers/vertex.json"
import { Mode } from "@shared/storage/types"
import { VSCodeDropdown, VSCodeLink, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { ModelsServiceClient } from "@/services/grpc-client"
import { DROPDOWN_Z_INDEX, DropdownContainer } from "../ApiOptions"
import { DebouncedTextField } from "../common/DebouncedTextField"
import { ModelInfoView } from "../common/ModelInfoView"
import { ModelSelector } from "../common/ModelSelector"
import ThinkingBudgetSlider from "../ThinkingBudgetSlider"
import { normalizeApiConfiguration } from "../utils/providerUtils"
import { useApiConfigurationHandlers } from "../utils/useApiConfigurationHandlers"
/**
* Props for the VertexProvider component
@@ -40,7 +41,6 @@ const REGIONS = VertexData.regions
*/
export const VertexProvider = ({ showModelOptions, isPopup, currentMode }: VertexProviderProps) => {
const { apiConfiguration } = useExtensionState()
const { handleFieldChange, handleModeFieldChange } = useApiConfigurationHandlers()
// Get the normalized configuration
const { selectedModelId, selectedModelInfo } = normalizeApiConfiguration(apiConfiguration, currentMode)
@@ -57,7 +57,18 @@ export const VertexProvider = ({ showModelOptions, isPopup, currentMode }: Verte
}}>
<DebouncedTextField
initialValue={apiConfiguration?.vertexProjectId || ""}
onChange={(value) => handleFieldChange("vertexProjectId", value)}
onChange={async (value) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
vertexProjectId: value,
},
},
updateMask: ["options.vertexProjectId"],
}),
)
}}
placeholder="Enter Project ID..."
style={{ width: "100%" }}>
<span style={{ fontWeight: 500 }}>Google Cloud Project ID</span>
@@ -69,7 +80,18 @@ export const VertexProvider = ({ showModelOptions, isPopup, currentMode }: Verte
</label>
<VSCodeDropdown
id="vertex-region-dropdown"
onChange={(e: any) => handleFieldChange("vertexRegion", e.target.value)}
onChange={async (e: any) => {
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create({
updates: {
options: {
vertexRegion: e.target.value,
},
},
updateMask: ["options.vertexRegion"],
}),
)
}}
style={{ width: "100%" }}
value={apiConfiguration?.vertexRegion || ""}>
<VSCodeOption value="">Select a region...</VSCodeOption>
@@ -105,13 +127,23 @@ export const VertexProvider = ({ showModelOptions, isPopup, currentMode }: Verte
<ModelSelector
label="Model"
models={modelsToUse}
onChange={(e: any) =>
handleModeFieldChange(
{ plan: "planModeApiModelId", act: "actModeApiModelId" },
e.target.value,
currentMode,
onChange={async (e: any) => {
const value = e.target.value
await ModelsServiceClient.updateApiConfiguration(
UpdateApiConfigurationRequestNew.create(
currentMode === "plan"
? {
updates: { options: { planModeApiModelId: value } },
updateMask: ["options.planModeApiModelId"],
}
: {
updates: { options: { actModeApiModelId: value } },
updateMask: ["options.actModeApiModelId"],
},
),
)
}
}}
selectedModelId={selectedModelId}
zIndex={DROPDOWN_Z_INDEX - 2}
/>