mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-07 02:58:15 +00:00
Run prettier
This commit is contained in:
parent
4544a3ad67
commit
b0396a6083
44 changed files with 234 additions and 648 deletions
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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" }}>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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} />}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)" }}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue