mirror of
https://github.com/cline/cline.git
synced 2026-09-17 17:45:33 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c0e295d74 | ||
|
|
199bdedbed | ||
|
|
1b59a33880 | ||
|
|
6e65b869ae | ||
|
|
cf61a8961a |
@@ -13,6 +13,7 @@ import { ApiStream } from "./transform/stream"
|
||||
import { DeepSeekHandler } from "./providers/deepseek"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
import { ClineHandler } from "./providers/cline"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
|
||||
@@ -50,6 +51,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
return new MistralHandler(options)
|
||||
case "vscode-lm":
|
||||
return new VsCodeLmHandler(options)
|
||||
case "cline":
|
||||
return new ClineHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, ModelInfo } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
export class ClineHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = new Anthropic({
|
||||
apiKey: this.options.clineApiKey || "",
|
||||
baseURL: "https://api.cline.bot/v1",
|
||||
defaultHeaders: {
|
||||
"X-Firebase-Token": this.options.authToken || "",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
try {
|
||||
// TEST: Simulate credit limit error for testing UI
|
||||
|
||||
// const error = new Error("Credit limit reached") as any
|
||||
// error.status = 402
|
||||
// error.error = {
|
||||
// type: "credit_limit_reached",
|
||||
// message: "You have reached your credit limit. Please visit the billing page to add more credits.",
|
||||
// credits_remaining: 0,
|
||||
// credits_used: 1000,
|
||||
// credits_limit: 1000,
|
||||
// recharge_url: "https://cline.bot/billing",
|
||||
// }
|
||||
// throw error
|
||||
|
||||
const stream = await this.client.messages.create({
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages,
|
||||
stream: true,
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk.type) {
|
||||
case "message_start":
|
||||
const usage = chunk.message.usage as any
|
||||
const result: any = {
|
||||
type: "usage",
|
||||
inputTokens: usage.input_tokens || 0,
|
||||
outputTokens: usage.output_tokens || 0,
|
||||
}
|
||||
if (usage.cache_creation_input_tokens) {
|
||||
result.cacheWriteTokens = usage.cache_creation_input_tokens
|
||||
}
|
||||
if (usage.cache_read_input_tokens) {
|
||||
result.cacheReadTokens = usage.cache_read_input_tokens
|
||||
}
|
||||
yield result
|
||||
break
|
||||
case "message_delta":
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: 0,
|
||||
outputTokens: chunk.usage.output_tokens || 0,
|
||||
}
|
||||
break
|
||||
case "content_block_start":
|
||||
switch (chunk.content_block.type) {
|
||||
case "text":
|
||||
if (chunk.index > 0) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: "\n",
|
||||
}
|
||||
}
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.content_block.text,
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "text_delta":
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.delta.text,
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Handle credit limit errors
|
||||
if (error.status === 402 && error.error?.type === "credit_limit_reached") {
|
||||
// Yield a credit_limit_reached message that will be transformed into a ClineMessage
|
||||
// with say="credit_limit_reached" by the Cline class
|
||||
yield {
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
type: "credit_limit_reached",
|
||||
creditsRemaining: error.error.credits_remaining,
|
||||
creditsUsed: error.error.credits_used,
|
||||
creditsLimit: error.error.credits_limit,
|
||||
rechargeUrl: error.error.recharge_url,
|
||||
message: error.error.message,
|
||||
}),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// For other errors, yield an error message that will be transformed
|
||||
// into a ClineMessage with say="error"
|
||||
const errorMessage = error.error?.message || error.message || "An unknown error occurred"
|
||||
yield {
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
type: "error",
|
||||
message: errorMessage,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel() {
|
||||
return {
|
||||
id: "claude-3-5-sonnet",
|
||||
info: {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 3.75,
|
||||
cacheReadsPrice: 0.3,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/c
|
||||
|
||||
type SecretKey =
|
||||
| "apiKey"
|
||||
| "clineApiKey"
|
||||
| "openRouterApiKey"
|
||||
| "awsAccessKey"
|
||||
| "awsSecretKey"
|
||||
@@ -138,6 +139,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
async handleSignOut() {
|
||||
try {
|
||||
await this.authManager.signOut()
|
||||
await this.storeSecret("authToken", undefined)
|
||||
await this.storeSecret("clineApiKey", undefined)
|
||||
await this.updateGlobalState("apiProvider", "openrouter")
|
||||
await this.postStateToWebview()
|
||||
vscode.window.showInformationMessage("Successfully logged out of Cline")
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage("Logout failed")
|
||||
@@ -836,13 +841,31 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
return true
|
||||
}
|
||||
|
||||
async handleAuthCallback(token: string) {
|
||||
async handleAuthCallback(token: string, apiKey: string) {
|
||||
try {
|
||||
// First sign in with Firebase to trigger auth state change
|
||||
await this.authManager.signInWithCustomToken(token)
|
||||
|
||||
// Then store the token securely
|
||||
await this.storeSecret("authToken", token)
|
||||
await this.storeSecret("clineApiKey", apiKey)
|
||||
|
||||
const clineProvider: ApiProvider = "cline"
|
||||
await this.updateGlobalState("apiProvider", clineProvider)
|
||||
|
||||
// Update API configuration with the new provider and auth token
|
||||
const { apiConfiguration } = await this.getState()
|
||||
const updatedConfig = {
|
||||
...apiConfiguration,
|
||||
apiProvider: clineProvider,
|
||||
clineApiKey: apiKey,
|
||||
authToken: token,
|
||||
}
|
||||
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(updatedConfig)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
vscode.window.showInformationMessage("Successfully logged in to Cline")
|
||||
} catch (error) {
|
||||
@@ -1230,6 +1253,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineApiKey,
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsSessionToken,
|
||||
@@ -1261,11 +1285,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
vsCodeLmModelSelector,
|
||||
localeLanguage,
|
||||
userInfo,
|
||||
authToken,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
this.getSecret("apiKey") as Promise<string | undefined>,
|
||||
this.getSecret("openRouterApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("clineApiKey") as Promise<string | undefined>,
|
||||
this.getSecret("awsAccessKey") as Promise<string | undefined>,
|
||||
this.getSecret("awsSecretKey") as Promise<string | undefined>,
|
||||
this.getSecret("awsSessionToken") as Promise<string | undefined>,
|
||||
@@ -1297,6 +1323,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
this.getGlobalState("localeLanguage") as Promise<string | undefined>,
|
||||
this.getGlobalState("userInfo") as Promise<UserInfo | undefined>,
|
||||
this.getSecret("authToken") as Promise<string | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -1319,6 +1346,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineApiKey,
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsSessionToken,
|
||||
@@ -1342,6 +1370,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
vsCodeLmModelSelector,
|
||||
authToken,
|
||||
},
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
@@ -1428,6 +1457,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
"deepSeekApiKey",
|
||||
"mistralApiKey",
|
||||
"authToken",
|
||||
"clineApiKey",
|
||||
]
|
||||
for (const key of secretKeys) {
|
||||
await this.storeSecret(key, undefined)
|
||||
|
||||
+4
-2
@@ -158,10 +158,12 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
case "/auth": {
|
||||
const token = query.get("token")
|
||||
const state = query.get("state")
|
||||
const apiKey = query.get("apiKey")
|
||||
|
||||
console.log("Auth callback received:", {
|
||||
token: token,
|
||||
state: state,
|
||||
apiKey: apiKey,
|
||||
})
|
||||
|
||||
// Validate state parameter
|
||||
@@ -170,8 +172,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if (token) {
|
||||
await visibleProvider.handleAuthCallback(token)
|
||||
if (token && apiKey) {
|
||||
await visibleProvider.handleAuthCallback(token, apiKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ export type ClineAsk =
|
||||
export type ClineSay =
|
||||
| "task"
|
||||
| "error"
|
||||
| "credit_limit_reached"
|
||||
| "api_req_started"
|
||||
| "api_req_finished"
|
||||
| "text"
|
||||
|
||||
@@ -11,10 +11,13 @@ export type ApiProvider =
|
||||
| "deepseek"
|
||||
| "mistral"
|
||||
| "vscode-lm"
|
||||
| "cline"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
apiModelId?: string
|
||||
apiKey?: string // anthropic
|
||||
clineApiKey?: string
|
||||
authToken?: string // firebase auth token for cline provider
|
||||
anthropicBaseUrl?: string
|
||||
openRouterApiKey?: string
|
||||
openRouterModelId?: string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import CreditLimitError from "./CreditLimitError"
|
||||
import { useEvent, useSize } from "react-use"
|
||||
import styled from "styled-components"
|
||||
import {
|
||||
@@ -875,6 +876,15 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
case "credit_limit_reached":
|
||||
const errorData = JSON.parse(message.text || "{}")
|
||||
return (
|
||||
<CreditLimitError
|
||||
creditsRemaining={errorData.creditsRemaining}
|
||||
creditsUsed={errorData.creditsUsed}
|
||||
rechargeUrl={errorData.rechargeUrl}
|
||||
/>
|
||||
)
|
||||
case "error":
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import React from "react"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
|
||||
interface CreditLimitErrorProps {
|
||||
creditsRemaining: number
|
||||
creditsUsed: number
|
||||
rechargeUrl: string
|
||||
}
|
||||
|
||||
const CreditLimitError: React.FC<CreditLimitErrorProps> = ({ creditsRemaining, creditsUsed, rechargeUrl }) => {
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-errorBackground)",
|
||||
padding: "12px",
|
||||
borderRadius: "3px",
|
||||
marginBottom: "10px",
|
||||
border: "1px solid var(--vscode-errorBorder)",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", marginBottom: "8px" }}>
|
||||
<i
|
||||
className="codicon codicon-error"
|
||||
style={{ color: "var(--vscode-errorForeground)", marginRight: "8px" }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span style={{ fontWeight: "bold", color: "var(--vscode-errorForeground)" }}>Credit Limit Reached</span>
|
||||
</div>
|
||||
<div style={{ marginBottom: "12px", color: "var(--vscode-foreground)" }}>
|
||||
<div style={{ marginBottom: "4px" }}>Credits Remaining: {creditsRemaining.toLocaleString()}</div>
|
||||
<div>Credits Used: {creditsUsed.toLocaleString()}</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<VSCodeButton appearance="primary" onClick={() => window.open(rechargeUrl, "_blank")}>
|
||||
Add Credits
|
||||
</VSCodeButton>
|
||||
<VSCodeButton appearance="secondary" onClick={() => vscode.postMessage({ type: "clearTask" })}>
|
||||
Start New Task
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreditLimitError
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
VSCodeRadio,
|
||||
VSCodeRadioGroup,
|
||||
VSCodeTextField,
|
||||
VSCodeButton,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { Fragment, memo, useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useEvent, useInterval } from "react-use"
|
||||
@@ -84,6 +85,10 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
|
||||
const handleClineLogin = () => {
|
||||
vscode.postMessage({ type: "accountLoginClicked" })
|
||||
}
|
||||
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
|
||||
}
|
||||
@@ -170,6 +175,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
value={selectedProvider}
|
||||
onChange={handleInputChange("apiProvider")}
|
||||
style={{ minWidth: 130, position: "relative", zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1 }}>
|
||||
<VSCodeOption value="cline">Cline</VSCodeOption>
|
||||
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
||||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||
<VSCodeOption value="gemini">Google Gemini</VSCodeOption>
|
||||
@@ -185,6 +191,40 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
|
||||
{selectedProvider === "cline" && (
|
||||
<div>
|
||||
{apiConfiguration?.clineApiKey && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration.clineApiKey}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("clineApiKey")}
|
||||
placeholder={t("enterApiKey")}>
|
||||
<span style={{ fontWeight: 500 }}>Cline API Key</span>
|
||||
</VSCodeTextField>
|
||||
)}
|
||||
|
||||
{apiConfiguration?.clineApiKey && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{t("apiKeyInfo")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!apiConfiguration?.clineApiKey && (
|
||||
<div style={{ marginTop: 2 }}>
|
||||
<VSCodeButton appearance="primary" onClick={handleClineLogin}>
|
||||
Get Cline API Key
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "anthropic" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
@@ -963,6 +1003,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
|
||||
return { selectedProvider: provider, selectedModelId, selectedModelInfo }
|
||||
}
|
||||
switch (provider) {
|
||||
case "cline":
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
case "anthropic":
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
case "bedrock":
|
||||
|
||||
@@ -8,7 +8,7 @@ import ApiOptions from "./ApiOptions"
|
||||
import LanguageOptions from "./LanguageOptions"
|
||||
import SettingsButton from "../common/SettingsButton"
|
||||
|
||||
const IS_DEV = false // FIXME: use flags when packaging
|
||||
const IS_DEV = true // FIXME: use flags when packaging
|
||||
|
||||
type SettingsViewProps = {
|
||||
onDone: () => void
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeButton, VSCodeDivider, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { validateApiConfiguration } from "../../utils/validate"
|
||||
@@ -7,11 +7,15 @@ import ApiOptions from "../settings/ApiOptions"
|
||||
|
||||
const WelcomeView = () => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
|
||||
const [showApiOptions, setShowApiOptions] = useState(false)
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||
|
||||
const disableLetsGoButton = apiErrorMessage != null
|
||||
|
||||
const handleLogin = () => {
|
||||
vscode.postMessage({ type: "accountLoginClicked" })
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
}
|
||||
@@ -29,6 +33,8 @@ const WelcomeView = () => {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: "0 20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
<h2>Hi, I'm Cline</h2>
|
||||
<p>
|
||||
@@ -43,13 +49,34 @@ const WelcomeView = () => {
|
||||
capabilities.
|
||||
</p>
|
||||
|
||||
<b>To get started, this extension needs an API provider for Claude 3.5 Sonnet.</b>
|
||||
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<ApiOptions showModelOptions={false} />
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
|
||||
Let's go!
|
||||
<div style={{ marginTop: "20px", marginBottom: "20px" }}>
|
||||
<VSCodeButton appearance="primary" onClick={handleLogin}>
|
||||
Log in to Cline
|
||||
</VSCodeButton>
|
||||
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<ul style={{ paddingLeft: "20px", margin: "10px 0" }}>
|
||||
<li>Get 1 task worth of free tokens</li>
|
||||
<li>No credit card required - just start using Cline immediately!</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VSCodeDivider />
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<VSCodeButton appearance="secondary" onClick={() => setShowApiOptions(!showApiOptions)}>
|
||||
{showApiOptions ? "Hide API options" : "Use your own provider API key"}
|
||||
</VSCodeButton>
|
||||
|
||||
{showApiOptions && (
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<ApiOptions showModelOptions={false} />
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
|
||||
Let's go!
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -70,6 +70,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
config.deepSeekApiKey,
|
||||
config.mistralApiKey,
|
||||
config.vsCodeLmModelSelector,
|
||||
config.clineApiKey,
|
||||
].some((key) => key !== undefined)
|
||||
: false
|
||||
setShowWelcome(!hasKey)
|
||||
|
||||
@@ -43,6 +43,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
case "cline":
|
||||
if (!apiConfiguration.clineApiKey) {
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
case "openai":
|
||||
if (!apiConfiguration.openAiBaseUrl || !apiConfiguration.openAiApiKey || !apiConfiguration.openAiModelId) {
|
||||
return "You must provide a valid base URL, API key, and model ID."
|
||||
|
||||
Reference in New Issue
Block a user