Run prettier

This commit is contained in:
Matt Rubens 2025-01-07 00:54:29 -05:00
parent 4544a3ad67
commit b0396a6083
44 changed files with 234 additions and 648 deletions

View file

@ -1,9 +1,5 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"connor4312.esbuild-problem-matchers",
"ms-vscode.extension-test-runner"
]
"recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"]
}

View file

@ -1,12 +1,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
import {
anthropicDefaultModelId,
AnthropicModelId,
anthropicModels,
ApiHandlerOptions,
ModelInfo,
} from "../../shared/api"
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "../../shared/api"
import { ApiHandler } from "../index"
import { ApiStream } from "../transform/stream"

View file

@ -1,11 +1,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI, { AzureOpenAI } from "openai"
import {
ApiHandlerOptions,
azureOpenAiDefaultApiVersion,
ModelInfo,
openAiModelInfoSaneDefaults,
} from "../../shared/api"
import { ApiHandlerOptions, azureOpenAiDefaultApiVersion, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
import { ApiHandler } from "../index"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"

View file

@ -124,17 +124,10 @@ export function convertAnthropicToolToGemini(tool: Anthropic.Messages.Tool): Fun
It looks like gemini likes to double escape certain characters when writing file contents: https://discuss.ai.google.dev/t/function-call-string-property-is-double-escaped/37867
*/
export function unescapeGeminiContent(content: string) {
return content
.replace(/\\n/g, "\n")
.replace(/\\'/g, "'")
.replace(/\\"/g, '"')
.replace(/\\r/g, "\r")
.replace(/\\t/g, "\t")
return content.replace(/\\n/g, "\n").replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\r/g, "\r").replace(/\\t/g, "\t")
}
export function convertGeminiResponseToAnthropic(
response: EnhancedGenerateContentResponse,
): Anthropic.Messages.Message {
export function convertGeminiResponseToAnthropic(response: EnhancedGenerateContentResponse): Anthropic.Messages.Message {
const content: Anthropic.Messages.ContentBlock[] = []
// Add the main text response

View file

@ -146,9 +146,7 @@ export function convertToOpenAiMessages(
}
// Convert OpenAI response to Anthropic format
export function convertToAnthropicMessage(
completion: OpenAI.Chat.Completions.ChatCompletion,
): Anthropic.Messages.Message {
export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.ChatCompletion): Anthropic.Messages.Message {
const openAiMessage = completion.choices[0].message
const anthropicMessage: Anthropic.Messages.Message = {
id: completion.id,

View file

@ -54,8 +54,7 @@ import { removeInvalidChars } from "../utils/string"
import { fixModelHtmlEscaping } from "../utils/string"
import { OpenAiHandler } from "../api/providers/openai"
const cwd =
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
type UserContent = Array<
@ -200,10 +199,7 @@ export class Cline {
const taskMessage = this.clineMessages[0] // first message is always the task say
const lastRelevantMessage =
this.clineMessages[
findLastIndex(
this.clineMessages,
(m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task"),
)
findLastIndex(this.clineMessages, (m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task"))
]
await this.providerRef.deref()?.updateTaskHistory({
id: this.taskId,
@ -245,9 +241,7 @@ export class Cline {
// todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener
// await this.saveClineMessages()
// await this.providerRef.deref()?.postStateToWebview()
await this.providerRef
.deref()
?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
await this.providerRef.deref()?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
throw new Error("Current ask promise was ignored 1")
} else {
// this is a new partial message, so add it with partial state
@ -281,9 +275,7 @@ export class Cline {
lastMessage.partial = false
await this.saveClineMessages()
// await this.providerRef.deref()?.postStateToWebview()
await this.providerRef
.deref()
?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
await this.providerRef.deref()?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
} else {
// this is a new partial=false message, so add it like normal
this.askResponse = undefined
@ -339,9 +331,7 @@ export class Cline {
lastMessage.text = text
lastMessage.images = images
lastMessage.partial = partial
await this.providerRef
.deref()
?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
await this.providerRef.deref()?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
} else {
// this is a new partial message, so add it with partial state
const sayTs = Date.now()
@ -362,9 +352,7 @@ export class Cline {
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
await this.saveClineMessages()
// await this.providerRef.deref()?.postStateToWebview()
await this.providerRef
.deref()
?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage }) // more performant than an entire postStateToWebview
await this.providerRef.deref()?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage }) // more performant than an entire postStateToWebview
} else {
// this is a new partial=false message, so add it like normal
const sayTs = Date.now()
@ -394,11 +382,7 @@ export class Cline {
async removeLastPartialMessageIfExistsWithType(type: "ask" | "say", askOrSay: ClineAsk | ClineSay) {
const lastMessage = this.clineMessages.at(-1)
if (
lastMessage?.partial &&
lastMessage.type === type &&
(lastMessage.ask === askOrSay || lastMessage.say === askOrSay)
) {
if (lastMessage?.partial && lastMessage.type === type && (lastMessage.ask === askOrSay || lastMessage.say === askOrSay)) {
this.clineMessages.pop()
await this.saveClineMessages()
await this.providerRef.deref()?.postStateToWebview()
@ -489,8 +473,7 @@ export class Cline {
// need to make sure that the api conversation history can be resumed by the api, even if it goes out of sync with cline messages
let existingApiConversationHistory: Anthropic.Messages.MessageParam[] =
await this.getSavedApiConversationHistory()
let existingApiConversationHistory: Anthropic.Messages.MessageParam[] = await this.getSavedApiConversationHistory()
// v2.0 xml tags refactor caveat: since we don't use tools anymore, we need to replace all tool use blocks with a text block since the API disallows conversations with tool uses and no tool schema
const conversationWithoutToolBlocks = existingApiConversationHistory.map((message) => {
@ -581,9 +564,7 @@ export class Cline {
) as Anthropic.ToolResultBlockParam[]
const missingToolResponses: Anthropic.ToolResultBlockParam[] = toolUseBlocks
.filter(
(toolUse) => !existingToolResults.some((result) => result.tool_use_id === toolUse.id),
)
.filter((toolUse) => !existingToolResults.some((result) => result.tool_use_id === toolUse.id))
.map((toolUse) => ({
type: "tool_result",
tool_use_id: toolUse.id,
@ -828,9 +809,7 @@ export class Cline {
if (previousApiReqIndex >= 0) {
const previousRequest = this.clineMessages[previousApiReqIndex]
if (previousRequest && previousRequest.text) {
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(
previousRequest.text,
)
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequest.text)
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
let contextWindow = this.api.getModel().info.contextWindow || 128_000
// FIXME: hack to get anyone using openai compatible with deepseek to have the proper context window instead of the default 128k. We need a way for the user to specify the context window for models they input through openai compatible
@ -868,10 +847,7 @@ export class Cline {
yield firstChunk.value
} catch (error) {
// note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely.
const { response } = await this.ask(
"api_req_failed",
error.message ?? JSON.stringify(serializeError(error), null, 2),
)
const { response } = await this.ask("api_req_failed", error.message ?? JSON.stringify(serializeError(error), null, 2))
if (response !== "yesButtonClicked") {
// this will never happen since if noButtonClicked, we will clear current task, aborting this instance
throw new Error("API request failed")
@ -1049,9 +1025,7 @@ export class Cline {
if (response !== "yesButtonClicked") {
if (response === "messageResponse") {
await this.say("user_feedback", text, images)
pushToolResult(
formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images),
)
pushToolResult(formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images))
// this.userMessageContent.push({
// type: "text",
// text: `${toolDescription()}`,
@ -1503,9 +1477,7 @@ export class Cline {
} else {
if (!relDirPath) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("list_code_definition_names", "path"),
)
pushToolResult(await this.sayAndCreateMissingParamError("list_code_definition_names", "path"))
break
}
this.consecutiveMistakeCount = 0
@ -1654,9 +1626,7 @@ export class Cline {
if (action === "launch") {
if (!url) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("browser_action", "url"),
)
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "url"))
await this.browserSession.closeBrowser()
break
}
@ -1688,10 +1658,7 @@ export class Cline {
if (!coordinate) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError(
"browser_action",
"coordinate",
),
await this.sayAndCreateMissingParamError("browser_action", "coordinate"),
)
await this.browserSession.closeBrowser()
break // can't be within an inner switch
@ -1700,9 +1667,7 @@ export class Cline {
if (action === "type") {
if (!text) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("browser_action", "text"),
)
pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "text"))
await this.browserSession.closeBrowser()
break
}
@ -1786,28 +1751,19 @@ export class Cline {
// ).catch(() => {})
} else {
// don't need to remove last partial since we couldn't have streamed a say
await this.ask(
"command",
removeClosingTag("command", command),
block.partial,
).catch(() => {})
await this.ask("command", removeClosingTag("command", command), block.partial).catch(() => {})
}
break
} else {
if (!command) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("execute_command", "command"),
)
pushToolResult(await this.sayAndCreateMissingParamError("execute_command", "command"))
break
}
if (!requiresApprovalRaw) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError(
"execute_command",
"requires_approval",
),
await this.sayAndCreateMissingParamError("execute_command", "requires_approval"),
)
break
}
@ -1887,16 +1843,12 @@ export class Cline {
} else {
if (!server_name) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"),
)
pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"))
break
}
if (!tool_name) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"),
)
pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"))
break
}
// arguments are optional, but if they are provided they must be valid JSON
@ -2000,16 +1952,12 @@ export class Cline {
} else {
if (!server_name) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"),
)
pushToolResult(await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"))
break
}
if (!uri) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"),
)
pushToolResult(await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"))
break
}
this.consecutiveMistakeCount = 0
@ -2036,9 +1984,7 @@ export class Cline {
// now execute the tool
await this.say("mcp_server_request_started")
const resourceResult = await this.providerRef
.deref()
?.mcpHub?.readResource(server_name, uri)
const resourceResult = await this.providerRef.deref()?.mcpHub?.readResource(server_name, uri)
const resourceResultPretty =
resourceResult?.contents
.map((item) => {
@ -2062,24 +2008,17 @@ export class Cline {
const question: string | undefined = block.params.question
try {
if (block.partial) {
await this.ask("followup", removeClosingTag("question", question), block.partial).catch(
() => {},
)
await this.ask("followup", removeClosingTag("question", question), block.partial).catch(() => {})
break
} else {
if (!question) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("ask_followup_question", "question"),
)
pushToolResult(await this.sayAndCreateMissingParamError("ask_followup_question", "question"))
break
}
this.consecutiveMistakeCount = 0
if (
this.autoApprovalSettings.enabled &&
this.autoApprovalSettings.enableNotifications
) {
if (this.autoApprovalSettings.enabled && this.autoApprovalSettings.enableNotifications) {
showSystemNotification({
subtitle: "Cline has a question...",
message: question.replace(/\n/g, " "),
@ -2130,25 +2069,16 @@ export class Cline {
// NOTE: we do not want to auto approve a command run as part of the attempt_completion tool
if (lastMessage && lastMessage.ask === "command") {
// update command
await this.ask(
"command",
removeClosingTag("command", command),
block.partial,
).catch(() => {})
await this.ask("command", removeClosingTag("command", command), block.partial).catch(
() => {},
)
} else {
// last message is completion_result
// we have command string, which means we have the result as well, so finish it (doesnt have to exist yet)
await this.say(
"completion_result",
removeClosingTag("result", result),
undefined,
false,
await this.say("completion_result", removeClosingTag("result", result), undefined, false)
await this.ask("command", removeClosingTag("command", command), block.partial).catch(
() => {},
)
await this.ask(
"command",
removeClosingTag("command", command),
block.partial,
).catch(() => {})
}
} else {
// no command, still outputting partial result
@ -2163,17 +2093,12 @@ export class Cline {
} else {
if (!result) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("attempt_completion", "result"),
)
pushToolResult(await this.sayAndCreateMissingParamError("attempt_completion", "result"))
break
}
this.consecutiveMistakeCount = 0
if (
this.autoApprovalSettings.enabled &&
this.autoApprovalSettings.enableNotifications
) {
if (this.autoApprovalSettings.enabled && this.autoApprovalSettings.enableNotifications) {
showSystemNotification({
subtitle: "Task Completed",
message: result.replace(/\n/g, " "),
@ -2273,10 +2198,7 @@ export class Cline {
}
}
async recursivelyMakeClineRequests(
userContent: UserContent,
includeFileDetails: boolean = false,
): Promise<boolean> {
async recursivelyMakeClineRequests(userContent: UserContent, includeFileDetails: boolean = false): Promise<boolean> {
if (this.abort) {
throw new Error("Cline instance aborted")
}
@ -2334,8 +2256,7 @@ export class Cline {
await this.say(
"api_req_started",
JSON.stringify({
request:
userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...",
request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...",
}),
)
@ -2373,13 +2294,7 @@ export class Cline {
cacheReads: cacheReadTokens,
cost:
totalCost ??
calculateApiCost(
this.api.getModel().info,
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
),
calculateApiCost(this.api.getModel().info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens),
cancelReason,
streamingFailedMessage,
} satisfies ClineApiReqInfo)
@ -2490,10 +2405,7 @@ export class Cline {
// abandoned happens when extension is no longer waiting for the cline instance to finish aborting (error is thrown here when any function in the for loop throws due to this.abort)
if (!this.abandoned) {
this.abortTask() // if the stream failed, there's various states the task could be in (i.e. could have streamed some tools the user may have executed), so we just resort to replicating a cancel task
await abortStream(
"streaming_failed",
error.message ?? JSON.stringify(serializeError(error), null, 2),
)
await abortStream("streaming_failed", error.message ?? JSON.stringify(serializeError(error), null, 2))
const history = await this.providerRef.deref()?.getTaskWithId(this.taskId)
if (history) {
await this.providerRef.deref()?.initClineWithHistoryItem(history.historyItem)

View file

@ -6,11 +6,7 @@
*
* Returns [matchIndexStart, matchIndexEnd] if found, or false if not found.
*/
function lineTrimmedFallbackMatch(
originalContent: string,
searchContent: string,
startIndex: number,
): [number, number] | false {
function lineTrimmedFallbackMatch(originalContent: string, searchContent: string, startIndex: number): [number, number] | false {
// Split both contents into lines
const originalLines = originalContent.split("\n")
const searchLines = searchContent.split("\n")
@ -91,11 +87,7 @@ function lineTrimmedFallbackMatch(
* @param startIndex - The character index in originalContent where to start searching
* @returns A tuple of [startIndex, endIndex] if a match is found, false otherwise
*/
function blockAnchorFallbackMatch(
originalContent: string,
searchContent: string,
startIndex: number,
): [number, number] | false {
function blockAnchorFallbackMatch(originalContent: string, searchContent: string, startIndex: number): [number, number] | false {
const originalLines = originalContent.split("\n")
const searchLines = searchContent.split("\n")
@ -208,11 +200,7 @@ function blockAnchorFallbackMatch(
* - If the search block cannot be matched using any of the available matching strategies,
* an error is thrown.
*/
export async function constructNewFileContent(
diffContent: string,
originalContent: string,
isFinal: boolean,
): Promise<string> {
export async function constructNewFileContent(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
let result = ""
let lastProcessedIndex = 0
@ -286,20 +274,12 @@ export async function constructNewFileContent(
searchEndIndex = exactIndex + currentSearchContent.length
} else {
// Attempt fallback line-trimmed matching
const lineMatch = lineTrimmedFallbackMatch(
originalContent,
currentSearchContent,
lastProcessedIndex,
)
const lineMatch = lineTrimmedFallbackMatch(originalContent, currentSearchContent, lastProcessedIndex)
if (lineMatch) {
;[searchMatchIndex, searchEndIndex] = lineMatch
} else {
// Try block anchor fallback for larger blocks
const blockMatch = blockAnchorFallbackMatch(
originalContent,
currentSearchContent,
lastProcessedIndex,
)
const blockMatch = blockAnchorFallbackMatch(originalContent, currentSearchContent, lastProcessedIndex)
if (blockMatch) {
;[searchMatchIndex, searchEndIndex] = blockMatch
} else {

View file

@ -1,12 +1,4 @@
import {
AssistantMessageContent,
TextContent,
ToolUse,
ToolParamName,
toolParamNames,
toolUseNames,
ToolUseName,
} from "."
import { AssistantMessageContent, TextContent, ToolUse, ToolParamName, toolParamNames, toolUseNames, ToolUseName } from "."
export function parseAssistantMessage(assistantMessage: string) {
let contentBlocks: AssistantMessageContent[] = []
@ -70,9 +62,7 @@ export function parseAssistantMessage(assistantMessage: string) {
const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length
const contentEndIndex = toolContent.lastIndexOf(contentEndTag)
if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) {
currentToolUse.params[contentParamName] = toolContent
.slice(contentStartIndex, contentEndIndex)
.trim()
currentToolUse.params[contentParamName] = toolContent.slice(contentStartIndex, contentEndIndex).trim()
}
}

View file

@ -31,10 +31,7 @@ Otherwise, if you have not completed the task and do not need additional informa
invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
`Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`,
toolResult: (
text: string,
images?: string[],
): string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam> => {
toolResult: (text: string, images?: string[]): string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam> => {
if (images && images.length > 0) {
const textBlock: Anthropic.TextBlockParam = { type: "text", text }
const imageBlocks: Anthropic.ImageBlockParam[] = formatImagesIntoBlocks(images)

View file

@ -8,9 +8,7 @@ a 200k context, we can assume that the first half is likely irrelevant to their
Therefore, this function should only be called when absolutely necessary to fit within
context limits, not as a continuous process.
*/
export function truncateHalfConversation(
messages: Anthropic.Messages.MessageParam[],
): Anthropic.Messages.MessageParam[] {
export function truncateHalfConversation(messages: Anthropic.Messages.MessageParam[]): Anthropic.Messages.MessageParam[] {
// API expects messages to be in user-assistant order, and tool use messages must be followed by tool results. We need to maintain this structure while truncating.
// Always keep the first Task message (this includes the project's file structure in environment_details)

View file

@ -240,13 +240,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// then convert it to a uri we can use in the webview.
// The CSS file from the React build output
const stylesUri = getUri(webview, this.context.extensionUri, [
"webview-ui",
"build",
"static",
"css",
"main.css",
])
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "static", "css", "main.css"])
// The JS file from the React build output
const scriptUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "static", "js", "main.js"])
@ -318,9 +312,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
case "webviewDidLaunch":
this.postStateToWebview()
this.workspaceTracker?.initializeFilePaths() // don't await
getTheme().then((theme) =>
this.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }),
)
getTheme().then((theme) => this.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }))
// post last cached models in case the call to endpoint fails
this.readOpenRouterModels().then((openRouterModels) => {
if (openRouterModels) {
@ -621,10 +613,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
async readOpenRouterModels(): Promise<Record<string, ModelInfo> | undefined> {
const openRouterModelsFilePath = path.join(
await this.ensureCacheDirectoryExists(),
GlobalFileNames.openRouterModels,
)
const openRouterModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
const fileExists = await fileExistsAtPath(openRouterModelsFilePath)
if (fileExists) {
const fileContents = await fs.readFile(openRouterModelsFilePath, "utf8")
@ -634,10 +623,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
async refreshOpenRouterModels() {
const openRouterModelsFilePath = path.join(
await this.ensureCacheDirectoryExists(),
GlobalFileNames.openRouterModels,
)
const openRouterModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
let models: Record<string, ModelInfo> = {}
try {

View file

@ -110,9 +110,7 @@ export function activate(context: vscode.ExtensionContext) {
return Buffer.from(uri.query, "base64").toString("utf-8")
}
})()
context.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider(DIFF_VIEW_URI_SCHEME, diffContentProvider),
)
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DIFF_VIEW_URI_SCHEME, diffContentProvider))
// URI Handler
const handleUri = async (uri: vscode.Uri) => {

View file

@ -63,10 +63,7 @@ export class DecorationController {
// Add a new range for all lines after the current line
if (line < totalLines - 1) {
this.ranges.push(
new vscode.Range(
new vscode.Position(line + 1, 0),
new vscode.Position(totalLines - 1, Number.MAX_SAFE_INTEGER),
),
new vscode.Range(new vscode.Position(line + 1, 0), new vscode.Position(totalLines - 1, Number.MAX_SAFE_INTEGER)),
)
}

View file

@ -33,9 +33,7 @@ export class DiffViewProvider {
this.isEditing = true
// if the file is already open, ensure it's not dirty before getting its contents
if (fileExists) {
const existingDocument = vscode.workspace.textDocuments.find((doc) =>
arePathsEqual(doc.uri.fsPath, absolutePath),
)
const existingDocument = vscode.workspace.textDocuments.find((doc) => arePathsEqual(doc.uri.fsPath, absolutePath))
if (existingDocument && existingDocument.isDirty) {
await existingDocument.save()
}
@ -61,9 +59,7 @@ export class DiffViewProvider {
const tabs = vscode.window.tabGroups.all
.map((tg) => tg.tabs)
.flat()
.filter(
(tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath),
)
.filter((tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath))
for (const tab of tabs) {
if (!tab.isDirty) {
await vscode.window.tabGroups.close(tab)
@ -207,11 +203,7 @@ export class DiffViewProvider {
let userEdits: string | undefined
if (normalizedPreSaveContent !== normalizedNewContent) {
// user made changes before approving edit. let the model know about user made changes (not including post-save auto-formatting changes)
userEdits = formatResponse.createPrettyPatch(
this.relPath.toPosix(),
normalizedNewContent,
normalizedPreSaveContent,
)
userEdits = formatResponse.createPrettyPatch(this.relPath.toPosix(), normalizedNewContent, normalizedPreSaveContent)
// return { newProblemsMessage, userEdits, finalContent: normalizedPostSaveContent }
} else {
// no changes to cline's edits
@ -277,11 +269,7 @@ export class DiffViewProvider {
private async closeAllDiffViews() {
const tabs = vscode.window.tabGroups.all
.flatMap((tg) => tg.tabs)
.filter(
(tab) =>
tab.input instanceof vscode.TabInputTextDiff &&
tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME,
)
.filter((tab) => tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME)
for (const tab of tabs) {
// trying to close dirty views results in save popup
if (!tab.isDirty) {

View file

@ -42,11 +42,7 @@ export async function downloadTask(dateTs: number, conversationHistory: Anthropi
}
export function formatContentBlockToMarkdown(
block:
| Anthropic.TextBlockParam
| Anthropic.ImageBlockParam
| Anthropic.ToolUseBlockParam
| Anthropic.ToolResultBlockParam,
block: Anthropic.TextBlockParam | Anthropic.ImageBlockParam | Anthropic.ToolUseBlockParam | Anthropic.ToolResultBlockParam,
// messages: Anthropic.MessageParam[]
): string {
switch (block.type) {

View file

@ -28,14 +28,11 @@ export async function openFile(absolutePath: string) {
try {
for (const group of vscode.window.tabGroups.all) {
const existingTab = group.tabs.find(
(tab) =>
tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath),
(tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath),
)
if (existingTab) {
const activeColumn = vscode.window.activeTextEditor?.viewColumn
const tabColumn = vscode.window.tabGroups.all.find((group) =>
group.tabs.includes(existingTab),
)?.viewColumn
const tabColumn = vscode.window.tabGroups.all.find((group) => group.tabs.includes(existingTab))?.viewColumn
if (activeColumn && activeColumn !== tabColumn && !existingTab.isDirty) {
await vscode.window.tabGroups.close(existingTab)
}

View file

@ -364,11 +364,7 @@
},
{
"name": "coloring of the Java import and package identifiers",
"scope": [
"storage.modifier.import.java",
"variable.language.wildcard.java",
"storage.modifier.package.java"
],
"scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
"settings": {
"foreground": "#d4d4d4"
}

View file

@ -57,12 +57,7 @@
}
},
{
"scope": [
"constant.numeric",
"constant.other.color.rgb-value",
"constant.other.rgb-value",
"support.constant.color"
],
"scope": ["constant.numeric", "constant.other.color.rgb-value", "constant.other.rgb-value", "support.constant.color"],
"settings": {
"foreground": "#b5cea8"
}
@ -320,11 +315,7 @@
},
{
"name": "coloring of the Java import and package identifiers",
"scope": [
"storage.modifier.import.java",
"variable.language.wildcard.java",
"storage.modifier.package.java"
],
"scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
"settings": {
"foreground": "#d4d4d4"
}

View file

@ -346,11 +346,7 @@
}
},
{
"scope": [
"storage.modifier.import.java",
"variable.language.wildcard.java",
"storage.modifier.package.java"
],
"scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
"settings": {
"foreground": "#000000"
}

View file

@ -389,11 +389,7 @@
},
{
"name": "coloring of the Java import and package identifiers",
"scope": [
"storage.modifier.import.java",
"variable.language.wildcard.java",
"storage.modifier.package.java"
],
"scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
"settings": {
"foreground": "#000000"
}

View file

@ -74,11 +74,7 @@ export async function getTheme() {
const converted = convertTheme(parsed)
converted.base = (
["vs", "hc-black"].includes(converted.base)
? converted.base
: colorTheme.includes("Light")
? "vs"
: "vs-dark"
["vs", "hc-black"].includes(converted.base) ? converted.base : colorTheme.includes("Light") ? "vs" : "vs-dark"
) as any
return converted

View file

@ -15,14 +15,7 @@ import * as path from "path"
import * as vscode from "vscode"
import { z } from "zod"
import { ClineProvider, GlobalFileNames } from "../../core/webview/ClineProvider"
import {
McpResource,
McpResourceResponse,
McpResourceTemplate,
McpServer,
McpTool,
McpToolCallResponse,
} from "../../shared/mcp"
import { McpResource, McpResourceResponse, McpResourceTemplate, McpServer, McpTool, McpToolCallResponse } from "../../shared/mcp"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual } from "../../utils/path"
@ -75,10 +68,7 @@ export class McpHub {
if (!provider) {
throw new Error("Provider not available")
}
const mcpSettingsFilePath = path.join(
await provider.ensureSettingsDirectoryExists(),
GlobalFileNames.mcpSettings,
)
const mcpSettingsFilePath = path.join(await provider.ensureSettingsDirectoryExists(), GlobalFileNames.mcpSettings)
const fileExists = await fileExistsAtPath(mcpSettingsFilePath)
if (!fileExists) {
await fs.writeFile(
@ -467,11 +457,7 @@ export class McpHub {
)
}
async callTool(
serverName: string,
toolName: string,
toolArguments?: Record<string, unknown>,
): Promise<McpToolCallResponse> {
async callTool(serverName: string, toolName: string, toolArguments?: Record<string, unknown>): Promise<McpToolCallResponse> {
const connection = this.connections.find((conn) => conn.server.name === serverName)
if (!connection) {
throw new Error(

View file

@ -122,12 +122,7 @@ async function execRipgrep(bin: string, args: string[]): Promise<string> {
})
}
export async function regexSearchFiles(
cwd: string,
directoryPath: string,
regex: string,
filePattern?: string,
): Promise<string> {
export async function regexSearchFiles(cwd: string, directoryPath: string, regex: string, filePattern?: string): Promise<string> {
const vscodeAppRoot = vscode.env.appRoot
const rgPath = await getBinPath(vscodeAppRoot)

View file

@ -20,12 +20,7 @@ export interface ExtensionMessage {
| "openRouterModels"
| "mcpServers"
text?: string
action?:
| "chatButtonClicked"
| "mcpButtonClicked"
| "settingsButtonClicked"
| "historyButtonClicked"
| "didBecomeVisible"
action?: "chatButtonClicked" | "mcpButtonClicked" | "settingsButtonClicked" | "historyButtonClicked" | "didBecomeVisible"
invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick"
state?: ExtensionState
images?: string[]

View file

@ -23,10 +23,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
position: "relative",
flexShrink: 0,
}}>
<VSCodeButton
appearance="icon"
onClick={hideAnnouncement}
style={{ position: "absolute", top: "8px", right: "8px" }}>
<VSCodeButton appearance="icon" onClick={hideAnnouncement} style={{ position: "absolute", top: "8px", right: "8px" }}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
<h3 style={{ margin: "0 0 8px" }}>
@ -34,17 +31,16 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
</h3>
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
<li>
<b>Auto-approve menu:</b> You can now specify which tools require approval, set a max # of
auto-approved API requests, and enable system notifications for when Cline completes a task.
<b>Auto-approve menu:</b> You can now specify which tools require approval, set a max # of auto-approved API
requests, and enable system notifications for when Cline completes a task.
</li>
<li>
<b>New diff editing for large files:</b> Cline now uses an efficient search & replace approach when
modifying large files for faster, more reliable edits (no more "
<code>{"// rest of code here"}</code>" deletions).
<b>New diff editing for large files:</b> Cline now uses an efficient search & replace approach when modifying
large files for faster, more reliable edits (no more "<code>{"// rest of code here"}</code>" deletions).
</li>
<li>
<b>.clinerules:</b> Add a root-level <code>.clinerules</code> file to specify custom instructions
for the project.
<b>.clinerules:</b> Add a root-level <code>.clinerules</code> file to specify custom instructions for the
project.
</li>
</ul>
<p style={{ margin: "5px 0px", fontWeight: "bold" }}>v2.2 Updates:</p>
@ -54,12 +50,12 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
MCP servers
</VSCodeLink>
by clicking the new <span className="codicon codicon-server" style={{ fontSize: "10px" }}></span>{" "}
icon in the menu bar.
by clicking the new <span className="codicon codicon-server" style={{ fontSize: "10px" }}></span> icon in the
menu bar.
</li>
<li>
Cline can also create custom tools–just say "add a tool that...", and watch him create the MCP
server and install it in the extension, ready to use in future tasks.
Cline can also create custom tools–just say "add a tool that...", and watch him create the MCP server and
install it in the extension, ready to use in future tasks.
</li>
<li>
Try it yourself by asking Cline to "add a tool that gets the latest npm docs", or

View file

@ -208,8 +208,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
color: "var(--vscode-descriptionForeground)",
fontSize: "12px",
}}>
Auto-approve allows Cline to perform the following actions without asking for permission. Please
use with caution and only enable if you understand the risks.
Auto-approve allows Cline to perform the following actions without asking for permission. Please use with
caution and only enable if you understand the risks.
</div>
{ACTION_METADATA.map((action) => (
<div key={action.id} style={{ margin: "6px 0" }}>
@ -263,10 +263,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
}}
onKeyDown={(e) => {
// Prevent non-numeric keys (except for backspace, delete, arrows)
if (
!/^\d$/.test(e.key) &&
!["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(e.key)
) {
if (!/^\d$/.test(e.key) && !["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(e.key)) {
e.preventDefault()
}
}}
@ -279,8 +276,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
fontSize: "12px",
marginBottom: "10px",
}}>
Cline will automatically make this many API requests before asking for approval to proceed with
the task.
Cline will automatically make this many API requests before asking for approval to proceed with the task.
</div>
<div style={{ margin: "6px 0" }}>
<VSCodeCheckbox
@ -297,8 +293,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
color: "var(--vscode-descriptionForeground)",
fontSize: "12px",
}}>
Receive system notifications when Cline requires approval to proceed or when a task is
completed.
Receive system notifications when Cline requires approval to proceed or when a task is completed.
</div>
</div>
</div>

View file

@ -1,12 +1,7 @@
import deepEqual from "fast-deep-equal"
import React, { memo, useEffect, useMemo, useRef, useState } from "react"
import { useSize } from "react-use"
import {
BrowserAction,
BrowserActionResult,
ClineMessage,
ClineSayBrowserAction,
} from "../../../../src/shared/ExtensionMessage"
import { BrowserAction, BrowserActionResult, ClineMessage, ClineSayBrowserAction } from "../../../../src/shared/ExtensionMessage"
import { vscode } from "../../utils/vscode"
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import { ChatRowContent, ProgressIndicator } from "./ChatRow"
@ -98,11 +93,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
// Reset for next page
currentStateMessages = []
nextActionMessages = []
} else if (
message.say === "api_req_started" ||
message.say === "text" ||
message.say === "browser_action"
) {
} else if (message.say === "api_req_started" || message.say === "text" || message.say === "browser_action") {
// These messages lead to the next result, so they should always go in nextActionMessages
nextActionMessages.push(message)
} else {
@ -137,16 +128,12 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
// Get initial URL from launch message
const initialUrl = useMemo(() => {
const launchMessage = messages.find(
(m) => m.ask === "browser_action_launch" || m.say === "browser_action_launch",
)
const launchMessage = messages.find((m) => m.ask === "browser_action_launch" || m.say === "browser_action_launch")
return launchMessage?.text || ""
}, [messages])
const isAutoApproved = useMemo(() => {
const launchMessage = messages.find(
(m) => m.ask === "browser_action_launch" || m.say === "browser_action_launch",
)
const launchMessage = messages.find((m) => m.ask === "browser_action_launch" || m.say === "browser_action_launch")
return launchMessage?.say === "browser_action_launch"
}, [messages])
@ -187,12 +174,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
const [actionContent, { height: actionHeight }] = useSize(
<div>
{currentPage?.nextAction?.messages.map((message) => (
<BrowserSessionRowContent
key={message.ts}
{...props}
message={message}
setMaxActionHeight={setMaxActionHeight}
/>
<BrowserSessionRowContent key={message.ts} {...props} message={message} setMaxActionHeight={setMaxActionHeight} />
))}
{!isBrowsing && messages.some((m) => m.say === "browser_action_result") && currentPageIndex === 0 && (
<BrowserActionBox action={"launch"} text={initialUrl} />
@ -265,9 +247,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
display: "flex",
alignItems: "center",
justifyContent: "center",
color: displayState.url
? "var(--vscode-input-foreground)"
: "var(--vscode-descriptionForeground)",
color: displayState.url ? "var(--vscode-input-foreground)" : "var(--vscode-descriptionForeground)",
fontSize: "12px",
}}>
<div
@ -490,15 +470,7 @@ const BrowserSessionRowContent = ({
}
}
const BrowserActionBox = ({
action,
coordinate,
text,
}: {
action: BrowserAction
coordinate?: string
text?: string
}) => {
const BrowserActionBox = ({ action, coordinate, text }: { action: BrowserAction; coordinate?: string; text?: string }) => {
const getBrowserActionText = (action: BrowserAction, coordinate?: string, text?: string) => {
switch (action) {
case "launch":

View file

@ -2,12 +2,7 @@ import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/reac
import deepEqual from "fast-deep-equal"
import React, { memo, useEffect, useMemo, useRef } from "react"
import { useSize } from "react-use"
import {
ClineApiReqInfo,
ClineAskUseMcpServer,
ClineMessage,
ClineSayTool,
} from "../../../../src/shared/ExtensionMessage"
import { ClineApiReqInfo, ClineAskUseMcpServer, ClineMessage, ClineSayTool } from "../../../../src/shared/ExtensionMessage"
import { COMMAND_OUTPUT_STRING, COMMAND_REQ_APP_STRING } from "../../../../src/shared/combineCommandSequences"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { findMatchingResourceOrTemplate } from "../../utils/mcp"
@ -69,13 +64,7 @@ const ChatRow = memo(
export default ChatRow
export const ChatRowContent = ({
message,
isExpanded,
onToggleExpand,
lastModifiedMessage,
isLast,
}: ChatRowContentProps) => {
export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessage, isLast }: ChatRowContentProps) => {
const { mcpServers } = useExtensionState()
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
if (message.text != null && message.say === "api_req_started") {
@ -107,23 +96,17 @@ export const ChatRowContent = ({
switch (type) {
case "error":
return [
<span
className="codicon codicon-error"
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
<span className="codicon codicon-error" style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: errorColor, fontWeight: "bold" }}>Error</span>,
]
case "mistake_limit_reached":
return [
<span
className="codicon codicon-error"
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
<span className="codicon codicon-error" style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: errorColor, fontWeight: "bold" }}>Cline is having trouble...</span>,
]
case "auto_approval_max_req_reached":
return [
<span
className="codicon codicon-warning"
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
<span className="codicon codicon-warning" style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: errorColor, fontWeight: "bold" }}>Maximum Requests Reached</span>,
]
case "command":
@ -131,14 +114,10 @@ export const ChatRowContent = ({
isCommandExecuting ? (
<ProgressIndicator />
) : (
<span
className="codicon codicon-terminal"
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
<span className="codicon codicon-terminal" style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
),
<span style={{ color: normalColor, fontWeight: "bold" }}>
{message.type === "ask"
? "Cline wants to execute this command:"
: "Cline executed this command:"}
{message.type === "ask" ? "Cline wants to execute this command:" : "Cline executed this command:"}
</span>,
]
case "use_mcp_server":
@ -147,30 +126,25 @@ export const ChatRowContent = ({
isMcpServerResponding ? (
<ProgressIndicator />
) : (
<span
className="codicon codicon-server"
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
<span className="codicon codicon-server" style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
),
<span style={{ color: normalColor, fontWeight: "bold" }}>
{message.type === "ask" ? (
<>
Cline wants to{" "}
{mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on the{" "}
Cline wants to {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on the{" "}
<code>{mcpServerUse.serverName}</code> MCP server:
</>
) : (
<>
Cline {mcpServerUse.type === "use_mcp_tool" ? "used a tool" : "accessed a resource"} on
the <code>{mcpServerUse.serverName}</code> MCP server:
Cline {mcpServerUse.type === "use_mcp_tool" ? "used a tool" : "accessed a resource"} on the{" "}
<code>{mcpServerUse.serverName}</code> MCP server:
</>
)}
</span>,
]
case "completion_result":
return [
<span
className="codicon codicon-check"
style={{ color: successColor, marginBottom: "-1.5px" }}></span>,
<span className="codicon codicon-check" style={{ color: successColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: successColor, fontWeight: "bold" }}>Task Completed</span>,
]
case "api_req_started":
@ -222,9 +196,7 @@ export const ChatRowContent = ({
]
case "followup":
return [
<span
className="codicon codicon-question"
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
<span className="codicon codicon-question" style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
<span style={{ color: normalColor, fontWeight: "bold" }}>Cline has a question:</span>,
]
default:
@ -276,9 +248,7 @@ export const ChatRowContent = ({
<div style={headerStyle}>
{toolIcon("edit")}
<span style={{ fontWeight: "bold" }}>
{message.type === "ask"
? "Cline wants to edit this file:"
: "Cline is editing this file:"}
{message.type === "ask" ? "Cline wants to edit this file:" : "Cline is editing this file:"}
</span>
</div>
<CodeAccordian
@ -296,9 +266,7 @@ export const ChatRowContent = ({
<div style={headerStyle}>
{toolIcon("new-file")}
<span style={{ fontWeight: "bold" }}>
{message.type === "ask"
? "Cline wants to create a new file:"
: "Cline is creating a new file:"}
{message.type === "ask" ? "Cline wants to create a new file:" : "Cline is creating a new file:"}
</span>
</div>
<CodeAccordian
@ -616,8 +584,7 @@ export const ChatRowContent = ({
tool={{
name: useMcpServer.toolName || "",
description:
server?.tools?.find((tool) => tool.name === useMcpServer.toolName)
?.description || "",
server?.tools?.find((tool) => tool.name === useMcpServer.toolName)?.description || "",
}}
/>
{useMcpServer.arguments && useMcpServer.arguments !== "{}" && (
@ -656,9 +623,7 @@ export const ChatRowContent = ({
style={{
...headerStyle,
marginBottom:
(cost == null && apiRequestFailedMessage) || apiReqStreamingFailedMessage
? 10
: 0,
(cost == null && apiRequestFailedMessage) || apiReqStreamingFailedMessage ? 10 : 0,
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
@ -819,8 +784,8 @@ export const ChatRowContent = ({
<span style={{ fontWeight: 500, color: "#FFA500" }}>Diff Edit Failed</span>
</div>
<div>
This usually happens when the model uses search patterns that don't match anything
in the file. Retrying...
This usually happens when the model uses search patterns that don't match anything in the
file. Retrying...
</div>
</div>
</>
@ -857,15 +822,13 @@ export const ChatRowContent = ({
fontSize: 18,
color: "#FFA500",
}}></i>
<span style={{ fontWeight: 500, color: "#FFA500" }}>
Shell Integration Unavailable
</span>
<span style={{ fontWeight: 500, color: "#FFA500" }}>Shell Integration Unavailable</span>
</div>
<div>
Cline won't be able to view the command's output. Please update VSCode (
<code>CMD/CTRL + Shift + P</code> → "Update") and make sure you're using a supported
shell: zsh, bash, fish, or PowerShell (<code>CMD/CTRL + Shift + P</code> →
"Terminal: Select Default Profile").{" "}
<code>CMD/CTRL + Shift + P</code> → "Update") and make sure you're using a supported shell:
zsh, bash, fish, or PowerShell (<code>CMD/CTRL + Shift + P</code> → "Terminal: Select Default
Profile").{" "}
<a
href="https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Shell-Integration-Unavailable"
style={{ color: "inherit", textDecoration: "underline" }}>

View file

@ -72,10 +72,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
contextMenuContainerRef.current &&
!contextMenuContainerRef.current.contains(event.target as Node)
) {
if (contextMenuContainerRef.current && !contextMenuContainerRef.current.contains(event.target as Node)) {
setShowContextMenu(false)
}
}
@ -116,11 +113,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
insertValue = "problems"
}
const { newValue, mentionIndex } = insertMention(
textAreaRef.current.value,
cursorPosition,
insertValue,
)
const { newValue, mentionIndex } = insertMention(textAreaRef.current.value, cursorPosition, insertValue)
setInputValue(newValue)
const newCursorPosition = newValue.indexOf(" ", mentionIndex + insertValue.length) + 1
@ -162,20 +155,16 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
// Find selectable options (non-URL types)
const selectableOptions = options.filter(
(option) =>
option.type !== ContextMenuOptionType.URL &&
option.type !== ContextMenuOptionType.NoResults,
option.type !== ContextMenuOptionType.URL && option.type !== ContextMenuOptionType.NoResults,
)
if (selectableOptions.length === 0) return -1 // No selectable options
// Find the index of the next selectable option
const currentSelectableIndex = selectableOptions.findIndex(
(option) => option === options[prevIndex],
)
const currentSelectableIndex = selectableOptions.findIndex((option) => option === options[prevIndex])
const newSelectableIndex =
(currentSelectableIndex + direction + selectableOptions.length) %
selectableOptions.length
(currentSelectableIndex + direction + selectableOptions.length) % selectableOptions.length
// Find the index of the selected option in the original options array
return options.findIndex((option) => option === selectableOptions[newSelectableIndex])
@ -184,9 +173,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}
if ((event.key === "Enter" || event.key === "Tab") && selectedMenuIndex !== -1) {
event.preventDefault()
const selectedOption = getContextMenuOptions(searchQuery, selectedType, queryItems)[
selectedMenuIndex
]
const selectedOption = getContextMenuOptions(searchQuery, selectedType, queryItems)[selectedMenuIndex]
if (
selectedOption &&
selectedOption.type !== ContextMenuOptionType.URL &&
@ -312,8 +299,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
if (urlRegex.test(pastedText.trim())) {
e.preventDefault()
const trimmedUrl = pastedText.trim()
const newValue =
inputValue.slice(0, cursorPosition) + trimmedUrl + " " + inputValue.slice(cursorPosition)
const newValue = inputValue.slice(0, cursorPosition) + trimmedUrl + " " + inputValue.slice(cursorPosition)
setInputValue(newValue)
const newCursorPosition = cursorPosition + trimmedUrl.length + 1
setCursorPosition(newCursorPosition)
@ -569,9 +555,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}}>
<div style={{ display: "flex", flexDirection: "row", alignItems: "center" }}>
<div
className={`input-icon-button ${
shouldDisableImages ? "disabled" : ""
} codicon codicon-device-camera`}
className={`input-icon-button ${shouldDisableImages ? "disabled" : ""} codicon codicon-device-camera`}
onClick={() => {
if (!shouldDisableImages) {
onSelectImages()

View file

@ -231,8 +231,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
const isStreaming = useMemo(() => {
const isLastAsk = !!modifiedMessages.at(-1)?.ask // checking clineAsk isn't enough since messages effect may be called again for a tool for example, set clineAsk to its value, and if the next message is not an ask then it doesn't reset. This is likely due to how much more often we're updating messages as compared to before, and should be resolved with optimizations as it's likely a rendering bug. but as a final guard for now, the cancel button will show if the last message is not an ask
const isToolCurrentlyAsking =
isLastAsk && clineAsk !== undefined && enableButtons && primaryButtonText !== undefined
const isToolCurrentlyAsking = isLastAsk && clineAsk !== undefined && enableButtons && primaryButtonText !== undefined
if (isToolCurrentlyAsking) {
return false
}
@ -389,9 +388,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "selectedImages":
const newImages = message.images ?? []
if (newImages.length > 0) {
setSelectedImages((prevImages) =>
[...prevImages, ...newImages].slice(0, MAX_IMAGES_PER_MESSAGE),
)
setSelectedImages((prevImages) => [...prevImages, ...newImages].slice(0, MAX_IMAGES_PER_MESSAGE))
}
break
case "invoke":
@ -409,14 +406,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
// textAreaRef.current is not explicitly required here since react gaurantees that ref will be stable across re-renders, and we're not using its value but its reference.
},
[
isHidden,
textAreaDisabled,
enableButtons,
handleSendMessage,
handlePrimaryButtonClick,
handleSecondaryButtonClick,
],
[isHidden, textAreaDisabled, enableButtons, handleSendMessage, handlePrimaryButtonClick, handleSecondaryButtonClick],
)
useEvent("message", handleMessage)
@ -474,13 +464,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
return ["browser_action_launch"].includes(message.ask!)
}
if (message.type === "say") {
return [
"browser_action_launch",
"api_req_started",
"text",
"browser_action",
"browser_action_result",
].includes(message.say!)
return ["browser_action_launch", "api_req_started", "text", "browser_action", "browser_action_result"].includes(
message.say!,
)
}
return false
}
@ -750,10 +736,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
style={{ display: "inline" }}>
Claude 3.5 Sonnet's agentic coding capabilities,
</VSCodeLink>{" "}
I can handle complex software development tasks step-by-step. With tools that let me create
& edit files, explore complex projects, use the browser, and execute terminal commands
(after you grant permission), I can assist you in ways that go beyond code completion or
tech support. I can even use MCP to create new tools and extend my own capabilities.
I can handle complex software development tasks step-by-step. With tools that let me create & edit
files, explore complex projects, use the browser, and execute terminal commands (after you grant
permission), I can assist you in ways that go beyond code completion or tech support. I can even use
MCP to create new tools and extend my own capabilities.
</p>
</div>
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}

View file

@ -165,13 +165,9 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
/>
)}
{(option.type === ContextMenuOptionType.Problems ||
((option.type === ContextMenuOptionType.File ||
option.type === ContextMenuOptionType.Folder) &&
((option.type === ContextMenuOptionType.File || option.type === ContextMenuOptionType.Folder) &&
option.value)) && (
<i
className="codicon codicon-add"
style={{ fontSize: "14px", flexShrink: 0, marginLeft: 8 }}
/>
<i className="codicon codicon-add" style={{ fontSize: "14px", flexShrink: 0, marginLeft: 8 }} />
)}
</div>
))}

View file

@ -150,9 +150,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
minWidth: 0, // This allows the div to shrink below its content size
}}>
<span style={{ fontWeight: "bold" }}>Task{!isTaskExpanded && ":"}</span>
{!isTaskExpanded && (
<span style={{ marginLeft: 4 }}>{highlightMentions(task.text, false)}</span>
)}
{!isTaskExpanded && <span style={{ marginLeft: 4 }}>{highlightMentions(task.text, false)}</span>}
</div>
</div>
{!isTaskExpanded && isCostAvailable && (
@ -213,8 +211,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
style={{
width: 30,
height: "1.2em",
background:
"linear-gradient(to right, transparent, var(--vscode-badge-background))",
background: "linear-gradient(to right, transparent, var(--vscode-badge-background))",
}}
/>
<div

View file

@ -75,10 +75,7 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
.filter((item) => item.ts && item.task)
.slice(0, 3)
.map((item) => (
<div
key={item.id}
className="history-preview-item"
onClick={() => handleHistorySelect(item.id)}>
<div key={item.id} className="history-preview-item" onClick={() => handleHistorySelect(item.id)}>
<div style={{ padding: "12px" }}>
<div style={{ marginBottom: "8px" }}>
<span
@ -108,8 +105,7 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
</div>
<div style={{ fontSize: "0.85em", color: "var(--vscode-descriptionForeground)" }}>
<span>
Tokens: ↑{formatLargeNumber(item.tokensIn || 0)} ↓
{formatLargeNumber(item.tokensOut || 0)}
Tokens: ↑{formatLargeNumber(item.tokensIn || 0)} ↓{formatLargeNumber(item.tokensOut || 0)}
</span>
{!!item.cacheWrites && (
<>

View file

@ -180,10 +180,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
<VSCodeRadio value="oldest">Oldest</VSCodeRadio>
<VSCodeRadio value="mostExpensive">Most Expensive</VSCodeRadio>
<VSCodeRadio value="mostTokens">Most Tokens</VSCodeRadio>
<VSCodeRadio
value="mostRelevant"
disabled={!searchQuery}
style={{ opacity: searchQuery ? 1 : 0.5 }}>
<VSCodeRadio value="mostRelevant" disabled={!searchQuery} style={{ opacity: searchQuery ? 1 : 0.5 }}>
Most Relevant
</VSCodeRadio>
</VSCodeRadioGroup>
@ -219,9 +216,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
style={{
cursor: "pointer",
borderBottom:
index < taskHistory.length - 1
? "1px solid var(--vscode-panel-border)"
: "none",
index < taskHistory.length - 1 ? "1px solid var(--vscode-panel-border)" : "none",
}}
onClick={() => handleHistorySelect(item.id)}>
<div
@ -428,10 +423,7 @@ const ExportButton = ({ itemId }: { itemId: string }) => (
)
// https://gist.github.com/evenfrost/1ba123656ded32fb7a0cd4651efd4db0
export const highlight = (
fuseSearchResult: FuseResult<any>[],
highlightClassName: string = "history-item-highlight",
) => {
export const highlight = (fuseSearchResult: FuseResult<any>[], highlightClassName: string = "history-item-highlight") => {
const set = (obj: Record<string, any>, path: string, value: any) => {
const pathValue = path.split(".")
let i: number

View file

@ -37,48 +37,43 @@ const McpToolRow = ({ tool }: McpToolRowProps) => {
borderRadius: "3px",
padding: "8px",
}}>
<div
style={{ marginBottom: "4px", opacity: 0.8, fontSize: "11px", textTransform: "uppercase" }}>
<div style={{ marginBottom: "4px", opacity: 0.8, fontSize: "11px", textTransform: "uppercase" }}>
Parameters
</div>
{Object.entries(tool.inputSchema.properties as Record<string, any>).map(
([paramName, schema]) => {
const isRequired =
tool.inputSchema &&
"required" in tool.inputSchema &&
Array.isArray(tool.inputSchema.required) &&
tool.inputSchema.required.includes(paramName)
{Object.entries(tool.inputSchema.properties as Record<string, any>).map(([paramName, schema]) => {
const isRequired =
tool.inputSchema &&
"required" in tool.inputSchema &&
Array.isArray(tool.inputSchema.required) &&
tool.inputSchema.required.includes(paramName)
return (
<div
key={paramName}
return (
<div
key={paramName}
style={{
display: "flex",
alignItems: "baseline",
marginTop: "4px",
}}>
<code
style={{
display: "flex",
alignItems: "baseline",
marginTop: "4px",
color: "var(--vscode-textPreformat-foreground)",
marginRight: "8px",
}}>
<code
style={{
color: "var(--vscode-textPreformat-foreground)",
marginRight: "8px",
}}>
{paramName}
{isRequired && (
<span style={{ color: "var(--vscode-errorForeground)" }}>*</span>
)}
</code>
<span
style={{
opacity: 0.8,
overflowWrap: "break-word",
wordBreak: "break-word",
}}>
{schema.description || "No description"}
</span>
</div>
)
},
)}
{paramName}
{isRequired && <span style={{ color: "var(--vscode-errorForeground)" }}>*</span>}
</code>
<span
style={{
opacity: 0.8,
overflowWrap: "break-word",
wordBreak: "break-word",
}}>
{schema.description || "No description"}
</span>
</div>
)
})}
</div>
)}
</div>

View file

@ -1,10 +1,4 @@
import {
VSCodeButton,
VSCodeLink,
VSCodePanels,
VSCodePanelTab,
VSCodePanelView,
} from "@vscode/webview-ui-toolkit/react"
import { VSCodeButton, VSCodeLink, VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
import { vscode } from "../../utils/vscode"
import { useExtensionState } from "../../context/ExtensionStateContext"
@ -113,13 +107,12 @@ const McpView = ({ onDone }: McpViewProps) => {
<VSCodeLink href="https://github.com/modelcontextprotocol" style={{ display: "inline" }}>
Model Context Protocol
</VSCodeLink>{" "}
enables communication with locally running MCP servers that provide additional tools and resources
to extend Cline's capabilities. You can use{" "}
enables communication with locally running MCP servers that provide additional tools and resources to extend
Cline's capabilities. You can use{" "}
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
community-made servers
</VSCodeLink>{" "}
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest
npm docs").{" "}
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest npm docs").{" "}
<VSCodeLink href="https://x.com/sdrzn/status/1867271665086074969" style={{ display: "inline" }}>
See a demo here.
</VSCodeLink>
@ -195,10 +188,7 @@ const ServerRow = ({ server }: { server: McpServer }) => {
}}
onClick={handleRowClick}>
{!server.error && (
<span
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
style={{ marginRight: "8px" }}
/>
<span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`} style={{ marginRight: "8px" }} />
)}
<span style={{ flex: 1 }}>{server.name}</span>
<div
@ -250,14 +240,12 @@ const ServerRow = ({ server }: { server: McpServer }) => {
<VSCodePanels>
<VSCodePanelTab id="tools">Tools ({server.tools?.length || 0})</VSCodePanelTab>
<VSCodePanelTab id="resources">
Resources (
{[...(server.resourceTemplates || []), ...(server.resources || [])].length || 0})
Resources ({[...(server.resourceTemplates || []), ...(server.resources || [])].length || 0})
</VSCodePanelTab>
<VSCodePanelView id="tools-view">
{server.tools && server.tools.length > 0 ? (
<div
style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
<div style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
{server.tools.map((tool) => (
<McpToolRow key={tool.name} tool={tool} />
))}
@ -272,16 +260,13 @@ const ServerRow = ({ server }: { server: McpServer }) => {
<VSCodePanelView id="resources-view">
{(server.resources && server.resources.length > 0) ||
(server.resourceTemplates && server.resourceTemplates.length > 0) ? (
<div
style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
{[...(server.resourceTemplates || []), ...(server.resources || [])].map(
(item) => (
<McpResourceRow
key={"uriTemplate" in item ? item.uriTemplate : item.uri}
item={item}
/>
),
)}
<div style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
{[...(server.resourceTemplates || []), ...(server.resources || [])].map((item) => (
<McpResourceRow
key={"uriTemplate" in item ? item.uriTemplate : item.uri}
item={item}
/>
))}
</div>
) : (
<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>

View file

@ -33,10 +33,7 @@ import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { vscode } from "../../utils/vscode"
import VSCodeButtonLink from "../common/VSCodeButtonLink"
import OpenRouterModelPicker, {
ModelDescriptionMarkdown,
OPENROUTER_MODEL_PICKER_Z_INDEX,
} from "./OpenRouterModelPicker"
import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
interface ApiOptionsProps {
showModelOptions: boolean
@ -238,9 +235,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
}}>
This key is stored locally and only used to make API requests from this extension.
{!apiConfiguration?.deepSeekApiKey && (
<VSCodeLink
href="https://www.deepseek.com/"
style={{ display: "inline", fontSize: "inherit" }}>
<VSCodeLink href="https://www.deepseek.com/" style={{ display: "inline", fontSize: "inherit" }}>
You can get a DeepSeek API key by signing up here.
</VSCodeLink>
)}
@ -360,9 +355,9 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Authenticate by either providing the keys above or use the default AWS credential providers,
i.e. ~/.aws/credentials or environment variables. These credentials are only used locally to
make API requests from this extension.
Authenticate by either providing the keys above or use the default AWS credential providers, i.e.
~/.aws/credentials or environment variables. These credentials are only used locally to make API requests
from this extension.
</p>
</div>
)}
@ -403,9 +398,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
<VSCodeLink
href="https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#before_you_begin"
style={{ display: "inline", fontSize: "inherit" }}>
{
"1) create a Google Cloud account › enable the Vertex AI API › enable the desired Claude models,"
}
{"1) create a Google Cloud account › enable the Vertex AI API › enable the desired Claude models,"}
</VSCodeLink>{" "}
<VSCodeLink
href="https://cloud.google.com/docs/authentication/provide-credentials-adc#google-idp"
@ -434,9 +427,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
}}>
This key is stored locally and only used to make API requests from this extension.
{!apiConfiguration?.geminiApiKey && (
<VSCodeLink
href="https://ai.google.dev/"
style={{ display: "inline", fontSize: "inherit" }}>
<VSCodeLink href="https://ai.google.dev/" style={{ display: "inline", fontSize: "inherit" }}>
You can get a Gemini API key by signing up here.
</VSCodeLink>
)}
@ -495,8 +486,8 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
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 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>
@ -536,10 +527,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
}
}}>
{lmStudioModels.map((model) => (
<VSCodeRadio
key={model}
value={model}
checked={apiConfiguration?.lmStudioModelId === model}>
<VSCodeRadio key={model} value={model} checked={apiConfiguration?.lmStudioModelId === model}>
{model}
</VSCodeRadio>
))}
@ -551,8 +539,8 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
LM Studio allows you to run models locally on your computer. For instructions on how to get
started, see their
LM Studio allows you to run models locally on your computer. For instructions on how to get started, see
their
<VSCodeLink href="https://lmstudio.ai/docs" style={{ display: "inline", fontSize: "inherit" }}>
quickstart guide.
</VSCodeLink>
@ -564,8 +552,8 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
</VSCodeLink>{" "}
feature to use it with this extension.{" "}
<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 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>
@ -605,10 +593,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
}
}}>
{ollamaModels.map((model) => (
<VSCodeRadio
key={model}
value={model}
checked={apiConfiguration?.ollamaModelId === model}>
<VSCodeRadio key={model} value={model} checked={apiConfiguration?.ollamaModelId === model}>
{model}
</VSCodeRadio>
))}
@ -620,16 +605,16 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Ollama allows you to run models locally on your computer. For instructions on how to get
started, see their
Ollama allows you to run models locally on your computer. For instructions on how to get started, see
their
<VSCodeLink
href="https://github.com/ollama/ollama/blob/main/README.md"
style={{ display: "inline", fontSize: "inherit" }}>
quickstart guide.
</VSCodeLink>
<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 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>
@ -756,26 +741,25 @@ export const ModelInfoView = ({
),
modelInfo.supportsPromptCache && modelInfo.cacheWritesPrice && (
<span key="cacheWritesPrice">
<span style={{ fontWeight: 500 }}>Cache writes price:</span>{" "}
{formatPrice(modelInfo.cacheWritesPrice || 0)}/million tokens
<span style={{ fontWeight: 500 }}>Cache writes price:</span> {formatPrice(modelInfo.cacheWritesPrice || 0)}
/million tokens
</span>
),
modelInfo.supportsPromptCache && modelInfo.cacheReadsPrice && (
<span key="cacheReadsPrice">
<span style={{ fontWeight: 500 }}>Cache reads price:</span>{" "}
{formatPrice(modelInfo.cacheReadsPrice || 0)}/million tokens
<span style={{ fontWeight: 500 }}>Cache reads price:</span> {formatPrice(modelInfo.cacheReadsPrice || 0)}/million
tokens
</span>
),
modelInfo.outputPrice !== undefined && modelInfo.outputPrice > 0 && (
<span key="outputPrice">
<span style={{ fontWeight: 500 }}>Output price:</span> {formatPrice(modelInfo.outputPrice)}/million
tokens
<span style={{ fontWeight: 500 }}>Output price:</span> {formatPrice(modelInfo.outputPrice)}/million tokens
</span>
),
isGemini && (
<span key="geminiInfo" style={{ fontStyle: "italic" }}>
* Free up to {selectedModelId && selectedModelId.includes("flash") ? "15" : "2"} requests per minute.
After that, billing depends on prompt size.{" "}
* Free up to {selectedModelId && selectedModelId.includes("flash") ? "15" : "2"} requests per minute. After that,
billing depends on prompt size.{" "}
<VSCodeLink href="https://ai.google.dev/pricing" style={{ display: "inline", fontSize: "inherit" }}>
For more info, see pricing details.
</VSCodeLink>

View file

@ -380,8 +380,7 @@ export const ModelDescriptionMarkdown = memo(
style={{
width: 30,
height: "1.2em",
background:
"linear-gradient(to right, transparent, var(--vscode-sideBar-background))",
background: "linear-gradient(to right, transparent, var(--vscode-sideBar-background))",
}}
/>
<VSCodeLink

View file

@ -12,8 +12,7 @@ type SettingsViewProps = {
}
const SettingsView = ({ onDone }: SettingsViewProps) => {
const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } =
useExtensionState()
const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
const handleSubmit = () => {
@ -74,8 +73,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h3>
<VSCodeButton onClick={handleSubmit}>Done</VSCodeButton>
</div>
<div
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
<div style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
<div style={{ marginBottom: 5 }}>
<ApiOptions
showModelOptions={true}
@ -89,9 +87,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
value={customInstructions ?? ""}
style={{ width: "100%" }}
rows={4}
placeholder={
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
}
placeholder={'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'}
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
<span style={{ fontWeight: "500" }}>Custom Instructions</span>
</VSCodeTextArea>

View file

@ -30,9 +30,9 @@ const WelcomeView = () => {
style={{ display: "inline" }}>
Claude 3.5 Sonnet's agentic coding capabilities
</VSCodeLink>{" "}
and access to tools that let me create & edit files, explore complex projects, use the browser, and
execute terminal commands (with your permission, of course). I can even use MCP to create new tools and
extend my own capabilities.
and access to tools that let me create & edit files, explore complex projects, use the browser, and execute
terminal commands (with your permission, of course). I can even use MCP to create new tools and extend my own
capabilities.
</p>
<b>To get started, this extension needs an API provider for Claude 3.5 Sonnet.</b>

View file

@ -2,12 +2,7 @@ import React, { createContext, useCallback, useContext, useEffect, useState } fr
import { useEvent } from "react-use"
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../../src/shared/AutoApprovalSettings"
import { ExtensionMessage, ExtensionState } from "../../../src/shared/ExtensionMessage"
import {
ApiConfiguration,
ModelInfo,
openRouterDefaultModelId,
openRouterDefaultModelInfo,
} from "../../../src/shared/api"
import { ApiConfiguration, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../../src/shared/api"
import { findLastIndex } from "../../../src/shared/array"
import { McpServer } from "../../../src/shared/mcp"
import { convertTextMateToHljs } from "../utils/textMateToHljs"

View file

@ -1,10 +1,6 @@
import { mentionRegex } from "../../../src/shared/context-mentions"
export function insertMention(
text: string,
position: number,
value: string,
): { newValue: string; mentionIndex: number } {
export function insertMention(text: string, position: number, value: string): { newValue: string; mentionIndex: number } {
const beforeCursor = text.slice(0, position)
const afterCursor = text.slice(position)

View file

@ -6,10 +6,7 @@ import { McpResource, McpResourceTemplate } from "../../../src/shared/mcp"
* @param templates Array of URI templates to match against
* @returns The matching template or undefined if no match is found
*/
export function findMatchingTemplate(
uri: string,
templates: McpResourceTemplate[] = [],
): McpResourceTemplate | undefined {
export function findMatchingTemplate(uri: string, templates: McpResourceTemplate[] = []): McpResourceTemplate | undefined {
return templates.find((template) => {
// Convert template to regex pattern
const pattern = String(template.uriTemplate)

View file

@ -39,11 +39,7 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
}
break
case "openai":
if (
!apiConfiguration.openAiBaseUrl ||
!apiConfiguration.openAiApiKey ||
!apiConfiguration.openAiModelId
) {
if (!apiConfiguration.openAiBaseUrl || !apiConfiguration.openAiApiKey || !apiConfiguration.openAiModelId) {
return "You must provide a valid base URL, API key, and model ID."
}
break