Remove consult advisor tool

This commit is contained in:
Saoud Rizwan 2025-01-20 15:00:42 -08:00
parent f1a9f3fed7
commit 6ceda01b24
23 changed files with 99 additions and 889 deletions

View file

@ -2,7 +2,6 @@
## [3.2.0]
- Add 'Consult Advisor' tool to let Cline ask a powerful model like o1 for help when he hits a roadblock (available with OpenRouter and Anthropic)
- Add Plan/Act mode toggle to let you plan tasks with Cline before letting him get to work
- Easily switch between API providers and models using a new popup menu under the chat field
- Add VS Code LM API provider to run models provided by other VS Code extensions (e.g. GitHub Copilot). Shoutout to @julesmons, @RaySinner, and @MrUbens for putting this together!

View file

@ -1,5 +1,5 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { ApiConfiguration, ModelInfo, ModelType } from "../shared/api"
import { ApiConfiguration, ModelInfo } from "../shared/api"
import { AnthropicHandler } from "./providers/anthropic"
import { AwsBedrockHandler } from "./providers/bedrock"
import { OpenRouterHandler } from "./providers/openrouter"
@ -15,9 +15,8 @@ import { MistralHandler } from "./providers/mistral"
import { VsCodeLmHandler } from "./providers/vscode-lm"
export interface ApiHandler {
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
getModel(): { id: string; info: ModelInfo }
getAdvisorModel?(): { id: string; info: ModelInfo }
}
export interface SingleCompletionHandler {

View file

@ -1,14 +1,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
import {
anthropicDefaultAdvisorModelId,
anthropicDefaultModelId,
AnthropicModelId,
anthropicModels,
ApiHandlerOptions,
ModelInfo,
ModelType,
} from "../../shared/api"
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "../../shared/api"
import { ApiHandler } from "../index"
import { ApiStream } from "../transform/stream"
@ -24,8 +16,8 @@ export class AnthropicHandler implements ApiHandler {
})
}
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType: ModelType): ApiStream {
const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const model = this.getModel()
let stream: AnthropicStream<Anthropic.Beta.PromptCaching.Messages.RawPromptCachingBetaMessageStreamEvent>
const modelId = model.id
switch (modelId) {
@ -34,20 +26,6 @@ export class AnthropicHandler implements ApiHandler {
case "claude-3-5-haiku-20241022":
case "claude-3-opus-20240229":
case "claude-3-haiku-20240307": {
// don't use prompt caching for advisor model requests
if (modelType === "advisor") {
stream = (await this.client.messages.create({
model: modelId,
max_tokens: model.info.maxTokens || 8192,
temperature: 0,
system: [{ text: systemPrompt, type: "text" }],
messages,
// tools,
// tool_choice: { type: "auto" },
stream: true,
})) as any
break
}
/*
The latest message will be the new user message, one before will be the assistant message from a previous request, and the user message before that will be a previously cached user message. So we need to mark the latest user message as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server know the last message to retrieve from the cache for the current request..
*/
@ -208,16 +186,4 @@ export class AnthropicHandler implements ApiHandler {
info: anthropicModels[anthropicDefaultModelId],
}
}
getAdvisorModel(): { id: string; info: ModelInfo } {
const modelId = this.options.anthropicAdvisorModelId
if (modelId && modelId in anthropicModels) {
const id = modelId as AnthropicModelId
return { id, info: anthropicModels[id] }
}
return {
id: anthropicDefaultAdvisorModelId,
info: anthropicModels[anthropicDefaultAdvisorModelId],
}
}
}

View file

@ -2,15 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
import axios from "axios"
import OpenAI from "openai"
import { ApiHandler } from "../"
import {
ApiHandlerOptions,
ModelInfo,
ModelType,
openRouterDefaultAdvisorModelId,
openRouterDefaultAdvisorModelInfo,
openRouterDefaultModelId,
openRouterDefaultModelInfo,
} from "../../shared/api"
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../shared/api"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import delay from "delay"
@ -31,8 +23,8 @@ export class OpenRouterHandler implements ApiHandler {
})
}
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream {
const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const model = this.getModel()
// Convert Anthropic messages to OpenAI format
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
@ -55,11 +47,6 @@ export class OpenRouterHandler implements ApiHandler {
case "anthropic/claude-3-haiku:beta":
case "anthropic/claude-3-opus":
case "anthropic/claude-3-opus:beta":
// don't use prompt caching for advisor model requests
if (modelType === "advisor") {
break
}
openAiMessages[0] = {
role: "system",
content: [
@ -196,16 +183,4 @@ export class OpenRouterHandler implements ApiHandler {
info: openRouterDefaultModelInfo,
}
}
getAdvisorModel(): { id: string; info: ModelInfo } {
const modelId = this.options.openRouterAdvisorModelId
const modelInfo = this.options.openRouterAdvisorModelInfo
if (modelId && modelInfo) {
return { id: modelId, info: modelInfo }
}
return {
id: openRouterDefaultAdvisorModelId,
info: openRouterDefaultAdvisorModelInfo,
}
}
}

View file

@ -2,25 +2,29 @@ import { Anthropic } from "@anthropic-ai/sdk"
import cloneDeep from "clone-deep"
import delay from "delay"
import fs from "fs/promises"
import getFolderSize from "get-folder-size"
import os from "os"
import pWaitFor from "p-wait-for"
import * as path from "path"
import { serializeError } from "serialize-error"
import * as vscode from "vscode"
import { ApiHandler, buildApiHandler } from "../api"
import { ApiStream } from "../api/transform/stream"
import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker"
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider"
import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
import { extractTextFromFile } from "../integrations/misc/extract-text"
import { showSystemNotification } from "../integrations/notifications"
import { TerminalManager } from "../integrations/terminal/TerminalManager"
import { BrowserSession } from "../services/browser/BrowserSession"
import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
import { listFiles } from "../services/glob/list-files"
import { regexSearchFiles } from "../services/ripgrep"
import { parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter"
import { ApiConfiguration, ModelInfo } from "../shared/api"
import { ApiConfiguration } from "../shared/api"
import { findLast, findLastIndex } from "../shared/array"
import { AutoApprovalSettings } from "../shared/AutoApprovalSettings"
import { BrowserSettings } from "../shared/BrowserSettings"
import { ChatSettings } from "../shared/ChatSettings"
import { combineApiRequests } from "../shared/combineApiRequests"
import { combineCommandSequences, COMMAND_REQ_APP_STRING } from "../shared/combineCommandSequences"
import {
@ -31,7 +35,6 @@ import {
ClineApiReqInfo,
ClineAsk,
ClineAskUseMcpServer,
ClineConsultAdvisor,
ClineMessage,
ClineSay,
ClineSayBrowserAction,
@ -44,23 +47,18 @@ import { ClineAskResponse, ClineCheckpointRestore } from "../shared/WebviewMessa
import { calculateApiCost } from "../utils/cost"
import { fileExistsAtPath } from "../utils/fs"
import { arePathsEqual, getReadablePath } from "../utils/path"
import { fixModelHtmlEscaping, removeInvalidChars } from "../utils/string"
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message"
import { constructNewFileContent } from "./assistant-message/diff"
import { parseMentions } from "./mentions"
import { formatResponse } from "./prompts/responses"
import { addUserInstructions, SYSTEM_PROMPT } from "./prompts/system"
import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window"
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
import { showSystemNotification } from "../integrations/notifications"
import { removeInvalidChars } from "../utils/string"
import { fixModelHtmlEscaping } from "../utils/string"
import { OpenAiHandler } from "../api/providers/openai"
import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker"
import getFolderSize from "get-folder-size"
import { BrowserSettings } from "../shared/BrowserSettings"
import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor"
import { ChatSettings } from "../shared/ChatSettings"
import { OpenRouterHandler } from "../api/providers/openrouter"
import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window"
import { SYSTEM_PROMPT } from "./prompts/system"
import { addUserInstructions } from "./prompts/system"
import { OpenAiHandler } from "../api/providers/openai"
import { ApiStream } from "../api/transform/stream"
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
@ -97,7 +95,6 @@ export class Cline {
checkpointTrackerErrorMessage?: string
conversationHistoryDeletedRange?: [number, number]
isInitialized = false
private advisorProblem?: string
isAwaitingPlanResponse = false
didRespondToPlanAskBySwitchingMode = false
@ -1082,8 +1079,6 @@ export class Cline {
message.ask === "followup" ||
message.say === "use_mcp_server" ||
message.ask === "use_mcp_server" ||
message.say === "consult_advisor" ||
message.ask === "consult_advisor" ||
message.say === "browser_action" ||
message.say === "browser_action_launch" ||
message.ask === "browser_action_launch"
@ -1194,78 +1189,11 @@ export class Cline {
case "access_mcp_resource":
case "use_mcp_tool":
return this.autoApprovalSettings.actions.useMcp
case "consult_advisor":
return this.autoApprovalSettings.actions.consultAdvisor ?? false
}
}
return false
}
estimateAdvisorModelCost(problem: string) {
const truncatedConversationHistory = getTruncatedMessages(
this.apiConversationHistory,
this.conversationHistoryDeletedRange,
)
const advisorModel = this.api.getAdvisorModel?.()
if (!advisorModel) {
return 0
}
const advisorMessage = this.createAdvisorMessage(truncatedConversationHistory, advisorModel, problem)
const prompt = ADVISOR_SYSTEM_PROMPT() + advisorMessage
// Estimate ~3 chars per token as a rough approximation
const estimatedInputTokens = Math.ceil(prompt.length / 3)
const estimatedOutputTokens = 300 // typical response size
// Note: we don't prompt cache since we only send up one request at a time
const inputCost = (estimatedInputTokens * (advisorModel.info.inputPrice ?? 0)) / 1_000_000 // Convert from per million tokens
const outputCost = (estimatedOutputTokens * (advisorModel.info.outputPrice ?? 0)) / 1_000_000
return inputCost + outputCost
}
createAdvisorMessage(
truncatedConversationHistory: Anthropic.Messages.MessageParam[],
advisorModel: {
id: string
info: ModelInfo
},
advisorProblem: string,
) {
// Generate markdown
const markdownContent = truncatedConversationHistory
.map((message) => {
const role = message.role === "user" ? "**User:**" : "**Coding Agent:**"
const content = Array.isArray(message.content)
? message.content.map((block) => formatContentBlockToMarkdown(block)).join("\n")
: message.content
return `${role}\n\n${content}\n\n`
})
.join("---\n\n")
// Don't want to send the entire conv history, just the most recent context
// Get approximate char count from token limit
const advisorContextWindow = advisorModel.info.contextWindow || 128_000
const tokensToKeep = Math.floor(advisorContextWindow / 2)
// Estimate ~3 chars per token as a rough approximation
const charsToKeep = tokensToKeep * 3
// Get last n chars of markdown content
const isTruncated = markdownContent.length > charsToKeep
const firstMessage = truncatedConversationHistory.at(0)
const firstMessageContent = firstMessage
? Array.isArray(firstMessage.content)
? firstMessage.content.map((block) => (block.type === "text" ? block.text : "")).join("\n")
: firstMessage.content
: ""
const recentContext =
(isTruncated ? `**User:**:\n\n${firstMessageContent}\n\n... (older messages removed for brevity) ...\n\n` : "") +
markdownContent.slice(-charsToKeep)
const advisorMessage =
"\n\n# The conversation history leading up to this point:\n\n" +
recentContext +
"\n\n# The problem the coding agent needs advice on:\n\n" +
advisorProblem
return advisorMessage
}
async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
// Wait for MCP servers to be connected before generating system prompt
await pWaitFor(() => this.providerRef.deref()?.mcpHub?.isConnecting !== true, { timeout: 10_000 }).catch(() => {
@ -1277,15 +1205,11 @@ export class Cline {
throw new Error("MCP hub not available")
}
const advisorModel = this.api.getAdvisorModel?.()
const supportsConsultAdvisor = advisorModel !== undefined
let systemPrompt = await SYSTEM_PROMPT(
cwd,
this.api.getModel().info.supportsComputerUse ?? false,
mcpHub,
this.browserSettings,
supportsConsultAdvisor,
)
let settingsCustomInstructions = this.customInstructions?.trim()
@ -1354,26 +1278,6 @@ export class Cline {
let stream = this.api.createMessage(systemPrompt, truncatedConversationHistory)
// If we're consulting the advisor, override the request
if (this.advisorProblem && advisorModel) {
const advisorMessage = this.createAdvisorMessage(truncatedConversationHistory, advisorModel, this.advisorProblem)
stream = this.api.createMessage(
ADVISOR_SYSTEM_PROMPT(),
[
{
role: "user",
content: [
{
type: "text",
text: advisorMessage,
},
],
},
],
"advisor",
)
}
const iterator = stream[Symbol.asyncIterator]()
try {
@ -1438,11 +1342,6 @@ export class Cline {
const block = cloneDeep(this.assistantMessageContent[this.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too
switch (block.type) {
case "text": {
if (this.advisorProblem) {
await this.say("advisor_response", block.content, undefined, block.partial)
break
}
if (this.didRejectTool || this.didAlreadyUseTool) {
break
}
@ -1523,8 +1422,6 @@ export class Cline {
return `[${block.name} for '${block.params.server_name}']`
case "access_mcp_resource":
return `[${block.name} for '${block.params.server_name}']`
case "consult_advisor":
return `[${block.name} for '${block.params.problem}']`
case "ask_followup_question":
return `[${block.name} for '${block.params.question}']`
case "plan_mode_response":
@ -2618,85 +2515,6 @@ export class Cline {
break
}
}
case "consult_advisor": {
const problem: string | undefined = block.params.problem
try {
if (block.partial) {
const partialMessage = JSON.stringify({
problem: removeClosingTag("problem", problem),
advisorModelId: this.api.getAdvisorModel?.().id,
} satisfies ClineConsultAdvisor)
if (this.shouldAutoApproveTool(block.name)) {
this.removeLastPartialMessageIfExistsWithType("ask", "consult_advisor")
await this.say("consult_advisor", partialMessage, undefined, block.partial)
} else {
this.removeLastPartialMessageIfExistsWithType("say", "consult_advisor")
await this.ask("consult_advisor", partialMessage, block.partial).catch(() => {})
}
break
} else {
if (!problem) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("consult_advisor", "problem"))
await this.saveCheckpoint()
break
}
this.consecutiveMistakeCount = 0
const estimatedCost = undefined //this.estimateAdvisorModelCost(problem)
const completeMessage = JSON.stringify({
problem: removeClosingTag("problem", problem),
advisorModelId: this.api.getAdvisorModel?.().id,
estimatedCost,
} satisfies ClineConsultAdvisor)
if (this.shouldAutoApproveTool(block.name)) {
this.removeLastPartialMessageIfExistsWithType("ask", "consult_advisor")
await this.say("consult_advisor", completeMessage, undefined, false)
this.consecutiveAutoApprovedRequestsCount++
} else {
showNotificationForApprovalIfAutoApprovalEnabled(
`Cline wants to consult the Advisor model about: ${problem}`,
)
this.removeLastPartialMessageIfExistsWithType("say", "consult_advisor")
const didApprove = await askApproval("consult_advisor", completeMessage)
if (!didApprove) {
await this.saveCheckpoint()
break
}
}
// Update the last consult_advisor message in case the advisor model changed
const lastMessage = findLast(
this.clineMessages,
(m) => m.ask === "consult_advisor" || m.say === "consult_advisor",
)
if (lastMessage) {
lastMessage.text = JSON.stringify({
problem: removeClosingTag("problem", problem),
advisorModelId: this.api.getAdvisorModel?.().id,
estimatedCost,
} satisfies ClineConsultAdvisor)
}
// now execute the tool
this.advisorProblem = problem
// await this.say("consult_advisor_request_started")
// const resourceResult = "Just try again bro." //await this.providerRef.deref()?.mcpHub?.readResource(server_name, uri)
// await this.say("consult_advisor_response", resourceResult)
pushToolResult(formatResponse.toolResult("Awaiting response from the Advisor model..."))
await this.saveCheckpoint()
break
}
} catch (error) {
await handleError("consulting advisor", error)
await this.saveCheckpoint()
break
}
}
case "ask_followup_question": {
const question: string | undefined = block.params.question
try {
@ -3036,13 +2854,10 @@ export class Cline {
// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
const advisorRequest = this.advisorProblem ? `(...conversation history)\n\n${this.advisorProblem}` : undefined
await this.say(
"api_req_started",
JSON.stringify({
request:
advisorRequest ||
userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...",
request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...",
}),
)
@ -3073,7 +2888,7 @@ export class Cline {
// since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message
const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
this.clineMessages[lastApiReqIndex].text = JSON.stringify({
request: advisorRequest || userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
request: userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
} satisfies ClineApiReqInfo)
await this.saveClineMessages()
await this.providerRef.deref()?.postStateToWebview()
@ -3156,8 +2971,6 @@ export class Cline {
this.didAutomaticallyRetryFailedApiRequest = false
await this.diffViewProvider.reset()
const isCallingAdvisor = this.advisorProblem !== undefined
const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
let assistantMessage = ""
this.isStreaming = true
@ -3245,11 +3058,6 @@ export class Cline {
await this.saveClineMessages()
await this.providerRef.deref()?.postStateToWebview()
// If this last request was to the advisor model, then reset advisor problem to give control back to base model
if (isCallingAdvisor) {
this.advisorProblem = undefined
}
// now add to apiconversationhistory
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
let didEndLoop = false
@ -3273,20 +3081,12 @@ export class Cline {
const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use")
if (!didToolUse) {
if (isCallingAdvisor) {
// if the last request was a request to advisor then it wouldn't have used a tool
this.userMessageContent.push({
type: "text",
text: "Please continue with the task, taking into account the advisor's response provided above.",
})
} else {
// normal request where tool use is required
this.userMessageContent.push({
type: "text",
text: formatResponse.noToolsUsed(),
})
this.consecutiveMistakeCount++
}
// normal request where tool use is required
this.userMessageContent.push({
type: "text",
text: formatResponse.noToolsUsed(),
})
this.consecutiveMistakeCount++
}
const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent)

View file

@ -19,7 +19,6 @@ export const toolUseNames = [
"browser_action",
"use_mcp_tool",
"access_mcp_resource",
"consult_advisor",
"ask_followup_question",
"plan_mode_response",
"attempt_completion",
@ -45,7 +44,6 @@ export const toolParamNames = [
"tool_name",
"arguments",
"uri",
"problem",
"question",
"response",
"result",

View file

@ -1,22 +0,0 @@
export const ADVISOR_SYSTEM_PROMPT =
() => `You are a senior AI advisor with deep expertise in software development, system architecture, and technical problem-solving. Your role is to assist another AI agent by providing strategic guidance and solutions to coding challenges.
====
INPUT FORMAT
You will receive:
1. The autonomous agent's conversation history thus far
2. A specific problem or question the agent needs help with
====
HOW TO RESPOND
After being given the necessary context, you may start by assessing the problem and key challenges, focusing on the most critical aspects that need to be addressed.
You may then recommend a strategy or solution, broken down into clear, actionable steps. Include rationale for key decisions and potential trade-offs considered. Use specific technical guidance, including code snippets, architecture recommendations, or debugging strategies as needed. Focus on practical, implementable advice the agent can use to apply the solution.
====
Remember: Your goal is to provide clear, actionable guidance that helps the agent make progress. Focus on practical solutions rather than theoretical discussions.`

View file

@ -9,7 +9,6 @@ export const SYSTEM_PROMPT = async (
supportsComputerUse: boolean,
mcpHub: McpHub,
browserSettings: BrowserSettings,
supportsConsultAdvisor: boolean,
) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
====
@ -205,20 +204,7 @@ Usage:
<access_mcp_resource>
<server_name>server name here</server_name>
<uri>resource URI here</uri>
</access_mcp_resource>${
supportsConsultAdvisor
? `
## consult_advisor
Description: Request to consult an advanced-reasoning AI model about a problem or question you are facing. This can be used to resolve errors you are stuck on, or get input from the model to work through a challenge you are facing. The relevant conversation history leading to the problem will also be provided to the advisor for additional context.
Parameters:
- problem: (required) A string describing the issue, question, or context you want the advisor to address.
Usage:
<consult_advisor>
<problem>Your problem or question here</problem>
</consult_advisor>`
: ""
}
</access_mcp_resource>
## ask_followup_question
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
@ -839,69 +825,7 @@ You have access to two tools for working with files: **write_to_file** and **rep
3. For major overhauls or initial file creation, rely on write_to_file.
4. Once the file has been edited with either write_to_file or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes.
By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.${
supportsConsultAdvisor
? `
====
CONSULTING THE ADVISOR MODEL
You can use the consult_advisor tool to get suggestions from an advisor model, a powerful AI model that can provide strategic guidance and help solve complex problems. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand.
# When to Use the Advisor
- When stuck on persistent bugs that you cannot resolve
- If you've tried multiple approaches without success
- When facing complex type errors or package incompatibilities
- When debugging intricate interactions between multiple systems
- If you need deeper insight into system behavior that may not be apparent
# How to Use Effectively
## Provide Clear Context
- Explain the current situation and challenge
- Include relevant code snippets or error messages
- Describe what you've already tried
- Specify what kind of guidance you're seeking
## Ask Specific Questions
- Instead of "Why isn't this working?"
- Better: "I'm encountering this specific type error when integrating these packages, here's what I've tried..."
Example Usage:
<consult_advisor>
<problem>
I'm encountering persistent type errors while working with @types/react-query v4.0.0:
Error: Type 'QueryClient' is not assignable to parameter of type 'never'.
The types of 'getQueryCache().notify' are incompatible between these types.
I've tried:
- Checking package versions compatibility
- Explicitly typing the QueryClient instance
- Updating @types/react and @types/react-query
Current package versions:
react-query: ^3.39.3
@types/react-query: ^4.0.0
react: ^18.2.0
typescript: ^4.9.5
The error persists despite these attempts. Could this be due to version mismatches or breaking changes I'm not aware of?
</problem>
</consult_advisor>
# Benefits of Using the Advisor
- Break through debugging roadblocks
- Get fresh perspectives on complex issues
- Understand root causes of persistent bugs
- Solve challenging technical issues
Remember: While you should attempt to solve problems with your own reasoning first, the advisor is a powerful resource available when you're stuck on a bug. Don't hesitate to consult it when you've hit a persistent roadblock that you cannot resolve.`
: ""
}
By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.
====
@ -929,9 +853,7 @@ CAPABILITIES
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
supportsComputerUse ? ", use the browser" : ""
}, read and edit files${
supportsConsultAdvisor ? ", consult an advisor" : ""
}, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
@ -941,11 +863,7 @@ CAPABILITIES
? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser."
: ""
}
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.${
supportsConsultAdvisor
? "\n- When you hit a roadblock, such as an error you've attempted to resolve several times without success, you can use the consult_advisor tool to get suggestions from an advanced-reasoning AI model. The conversation history that led to the current situation is automatically passed to the advisor, allowing it to provide contextually relevant guidance based on the full picture of the task at hand."
: ""
}
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
====

View file

@ -46,7 +46,6 @@ type SecretKey =
type GlobalStateKey =
| "apiProvider"
| "apiModelId"
| "anthropicAdvisorModelId"
| "awsRegion"
| "awsUseCrossRegionInference"
| "vertexProjectId"
@ -63,9 +62,7 @@ type GlobalStateKey =
| "anthropicBaseUrl"
| "azureApiVersion"
| "openRouterModelId"
| "openRouterAdvisorModelId"
| "openRouterModelInfo"
| "openRouterAdvisorModelInfo"
| "autoApprovalSettings"
| "browserSettings"
| "chatSettings"
@ -372,13 +369,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
)
await this.postStateToWebview()
}
if (apiConfiguration.openRouterAdvisorModelId) {
await this.updateGlobalState(
"openRouterAdvisorModelInfo",
openRouterModels[apiConfiguration.openRouterAdvisorModelId],
)
await this.postStateToWebview()
}
}
})
break
@ -398,7 +388,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
const {
apiProvider,
apiModelId,
anthropicAdvisorModelId,
apiKey,
openRouterApiKey,
awsAccessKey,
@ -423,13 +412,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterAdvisorModelId,
openRouterAdvisorModelInfo,
vsCodeLmModelSelector,
} = message.apiConfiguration
await this.updateGlobalState("apiProvider", apiProvider)
await this.updateGlobalState("apiModelId", apiModelId)
await this.updateGlobalState("anthropicAdvisorModelId", anthropicAdvisorModelId)
await this.storeSecret("apiKey", apiKey)
await this.storeSecret("openRouterApiKey", openRouterApiKey)
await this.storeSecret("awsAccessKey", awsAccessKey)
@ -454,8 +440,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("azureApiVersion", azureApiVersion)
await this.updateGlobalState("openRouterModelId", openRouterModelId)
await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo)
await this.updateGlobalState("openRouterAdvisorModelId", openRouterAdvisorModelId)
await this.updateGlobalState("openRouterAdvisorModelInfo", openRouterAdvisorModelInfo)
await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector)
if (this.cline) {
this.cline.api = buildApiHandler(message.apiConfiguration)
@ -607,11 +591,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
case "cancelTask":
this.cancelTask()
break
case "openAdvisorModelSettings":
this.postMessageToWebview({
type: "openAdvisorModelSettings",
})
break
case "getLatestState":
await this.postStateToWebview()
break
@ -1106,7 +1085,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
const [
storedApiProvider,
apiModelId,
anthropicAdvisorModelId,
apiKey,
openRouterApiKey,
awsAccessKey,
@ -1131,8 +1109,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterAdvisorModelId,
openRouterAdvisorModelInfo,
lastShownAnnouncementId,
customInstructions,
taskHistory,
@ -1143,7 +1119,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
this.getGlobalState("anthropicAdvisorModelId") as Promise<string | undefined>,
this.getSecret("apiKey") as Promise<string | undefined>,
this.getSecret("openRouterApiKey") as Promise<string | undefined>,
this.getSecret("awsAccessKey") as Promise<string | undefined>,
@ -1168,8 +1143,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("azureApiVersion") as Promise<string | undefined>,
this.getGlobalState("openRouterModelId") as Promise<string | undefined>,
this.getGlobalState("openRouterModelInfo") as Promise<ModelInfo | undefined>,
this.getGlobalState("openRouterAdvisorModelId") as Promise<string | undefined>,
this.getGlobalState("openRouterAdvisorModelInfo") as Promise<ModelInfo | undefined>,
this.getGlobalState("lastShownAnnouncementId") as Promise<string | undefined>,
this.getGlobalState("customInstructions") as Promise<string | undefined>,
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
@ -1197,7 +1170,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
apiConfiguration: {
apiProvider,
apiModelId,
anthropicAdvisorModelId,
apiKey,
openRouterApiKey,
awsAccessKey,
@ -1222,8 +1194,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterAdvisorModelId,
openRouterAdvisorModelInfo,
vsCodeLmModelSelector,
},
lastShownAnnouncementId,

View file

@ -8,7 +8,6 @@ export interface AutoApprovalSettings {
executeCommands: boolean // Execute safe commands
useBrowser: boolean // Use browser
useMcp: boolean // Use MCP servers
consultAdvisor?: boolean // Consult the advisor model
}
// Global settings
maxRequests: number // Maximum number of auto-approved requests
@ -23,7 +22,6 @@ export const DEFAULT_AUTO_APPROVAL_SETTINGS: AutoApprovalSettings = {
executeCommands: false,
useBrowser: false,
useMcp: false,
consultAdvisor: false,
},
maxRequests: 20,
enableNotifications: false,

View file

@ -22,7 +22,6 @@ export interface ExtensionMessage {
| "openRouterModels"
| "mcpServers"
| "relinquishControl"
| "openAdvisorModelSettings"
| "vsCodeLmModels"
| "requestVsCodeLmModels"
text?: string
@ -81,7 +80,6 @@ export type ClineAsk =
| "auto_approval_max_req_reached"
| "browser_action_launch"
| "use_mcp_server"
| "consult_advisor"
export type ClineSay =
| "task"
@ -103,10 +101,8 @@ export type ClineSay =
| "mcp_server_request_started"
| "mcp_server_response"
| "use_mcp_server"
| "consult_advisor"
| "diff_error"
| "deleted_api_reqs"
| "advisor_response"
export interface ClineSayTool {
tool:
@ -149,12 +145,6 @@ export interface ClineAskUseMcpServer {
uri?: string
}
export interface ClineConsultAdvisor {
problem: string
advisorModelId?: string
estimatedCost?: number
}
export interface ClineApiReqInfo {
request?: string
tokensIn?: number

View file

@ -33,7 +33,6 @@ export interface WebviewMessage {
| "checkpointDiff"
| "checkpointRestore"
| "taskCompletionViewChanges"
| "openAdvisorModelSettings"
| "requestVsCodeLmModels"
| "toggleToolAutoApprove"
| "toggleMcpServer"

View file

@ -15,13 +15,10 @@ export type ApiProvider =
export interface ApiHandlerOptions {
apiModelId?: string
apiKey?: string // anthropic
anthropicAdvisorModelId?: string
anthropicBaseUrl?: string
openRouterApiKey?: string
openRouterModelId?: string
openRouterAdvisorModelId?: string
openRouterModelInfo?: ModelInfo
openRouterAdvisorModelInfo?: ModelInfo
awsAccessKey?: string
awsSecretKey?: string
awsSessionToken?: string
@ -63,13 +60,10 @@ export interface ModelInfo {
description?: string
}
export type ModelType = "base" | "advisor"
// Anthropic
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
export type AnthropicModelId = keyof typeof anthropicModels
export const anthropicDefaultModelId: AnthropicModelId = "claude-3-5-sonnet-20241022"
export const anthropicDefaultAdvisorModelId: AnthropicModelId = "claude-3-opus-20240229"
export const anthropicModels = {
"claude-3-5-sonnet-20241022": {
maxTokens: 8192,
@ -186,18 +180,6 @@ export const openRouterDefaultModelInfo: ModelInfo = {
description:
"The new Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: New Sonnet scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal\n\n_This is a faster endpoint, made available in collaboration with Anthropic, that is self-moderated: response moderation happens on the provider's side instead of OpenRouter's. For requests that pass moderation, it's identical to the [Standard](/anthropic/claude-3.5-sonnet) variant._",
}
export const openRouterDefaultAdvisorModelId = "openai/o1-preview" // will always exist in openRouterModels
export const openRouterDefaultAdvisorModelInfo: ModelInfo = {
maxTokens: 33_000,
contextWindow: 128_000,
supportsImages: true,
supportsComputerUse: false,
supportsPromptCache: false,
inputPrice: 15,
outputPrice: 60,
description:
"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.",
}
// Vertex AI
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude

View file

@ -31,18 +31,8 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
</h3>
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
<li>
<b>
New <code>Consult Advisor</code> tool
</b>{" "}
lets Cline ask a powerful model like o1 for help when stuck. Cline provides the full context of the problem,
and the Advisor model responds with a solution. <i>(Available with OpenRouter and Anthropic.)</i>{" "}
<VSCodeLink href="https://x.com/sdrzn/status/1876378124126236949" style={{ display: "inline" }}>
See a demo here!
</VSCodeLink>
</li>
<li>
<b>Plan/Act mode toggle:</b> Plan mode lets Cline ask clarifying questions, brainstorm ideas, and architect a
solution. Switch back to Act mode to let him execute the plan!
<b>Plan/Act mode toggle:</b> Plan mode lets Cline focus on gathering information, asking clarifying questions,
brainstorm ideas, and architect a solution. Switch back to Act mode to let him execute the plan!
</li>
<li>
<b>Quick API/model switching</b> with a new popup menu under the chat field

View file

@ -46,25 +46,16 @@ const ACTION_METADATA: {
shortName: "MCP",
description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.",
},
{
id: "consultAdvisor",
label: "Consult the Advisor model",
shortName: "Advisor",
description: "Allows Cline to consult the Advisor model to get advice on how to proceed.",
},
]
const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const { autoApprovalSettings, apiConfiguration } = useExtensionState()
const { autoApprovalSettings } = useExtensionState()
const [isExpanded, setIsExpanded] = useState(false)
const [isHoveringCollapsibleSection, setIsHoveringCollapsibleSection] = useState(false)
// Careful not to use partials to mutate since spread operator only does shallow copy
const supportsAdvisor = apiConfiguration?.apiProvider === "openrouter" || apiConfiguration?.apiProvider === "anthropic"
const actionMetadata = ACTION_METADATA.filter((action) => supportsAdvisor || action.id !== "consultAdvisor")
const enabledActions = actionMetadata.filter((action) => autoApprovalSettings.actions[action.id])
const enabledActions = ACTION_METADATA.filter((action) => autoApprovalSettings.actions[action.id])
const enabledActionsList = enabledActions.map((action) => action.shortName).join(", ")
const hasEnabledActions = enabledActions.length > 0
@ -228,7 +219,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
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>
{actionMetadata.map((action) => (
{ACTION_METADATA.map((action) => (
<div key={action.id} style={{ margin: "6px 0" }}>
<VSCodeCheckbox
checked={autoApprovalSettings.actions[action.id]}

View file

@ -1,4 +1,4 @@
import { VSCodeBadge, VSCodeLink, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
import deepEqual from "fast-deep-equal"
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useEvent, useSize } from "react-use"
@ -8,9 +8,8 @@ import {
ClineAskUseMcpServer,
ClineMessage,
ClineSayTool,
ExtensionMessage,
COMPLETION_RESULT_CHANGES_FLAG,
ClineConsultAdvisor,
ExtensionMessage,
} from "../../../../src/shared/ExtensionMessage"
import { COMMAND_OUTPUT_STRING, COMMAND_REQ_APP_STRING } from "../../../../src/shared/combineCommandSequences"
import { useExtensionState } from "../../context/ExtensionStateContext"
@ -25,7 +24,6 @@ import Thumbnails from "../common/Thumbnails"
import McpResourceRow from "../mcp/McpResourceRow"
import McpToolRow from "../mcp/McpToolRow"
import { highlightMentions } from "./TaskHeader"
import { normalizeApiConfiguration } from "../settings/ApiOptions"
const ChatRowContainer = styled.div`
padding: 10px 6px 10px 15px;
@ -64,9 +62,7 @@ const ChatRow = memo(
message.say === "completion_result" ||
message.ask === "completion_result" ||
message.say === "use_mcp_server" ||
message.ask === "use_mcp_server" ||
message.say === "consult_advisor" ||
message.ask === "consult_advisor")
message.ask === "use_mcp_server")
if (shouldShowCheckpoints && isLast) {
shouldShowCheckpoints =
@ -103,7 +99,7 @@ const ChatRow = memo(
export default ChatRow
export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessage, isLast }: ChatRowContentProps) => {
const { mcpServers, apiConfiguration } = useExtensionState()
const { mcpServers } = useExtensionState()
const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false)
@ -145,10 +141,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
useEvent("message", handleMessage)
const { selectedAdvisorModelId } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
const [icon, title] = useMemo(() => {
switch (type) {
case "error":
@ -224,23 +216,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
)}
</span>,
]
case "consult_advisor":
// const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor
const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor
return [
<span
className="codicon codicon-comment-discussion"
style={{
color: normalColor,
marginBottom: "-1.5px",
}}></span>,
<span style={{ color: normalColor, fontWeight: "bold" }}>
<>
Cline wants to consult{" "}
{<code>{isLast ? selectedAdvisorModelId : consultAdvisor.advisorModelId}</code> || "Advisor model"}:
</>
</span>,
]
case "completion_result":
return [
<span
@ -332,8 +307,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
isMcpServerResponding,
message.text,
message.type,
selectedAdvisorModelId,
isLast,
])
const headerStyle: React.CSSProperties = {
@ -751,63 +724,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
)
}
if (message.ask === "consult_advisor" || message.say === "consult_advisor") {
const consultAdvisor = JSON.parse(message.text || "{}") as ClineConsultAdvisor
// const server = mcpServers.find((server) => server.name === useMcpServer.serverName)
return (
<>
<div style={headerStyle}>
{icon}
{title}
</div>
<div
style={{
background: "var(--vscode-textCodeBlock-background)",
borderRadius: "3px",
padding: "8px 10px",
marginTop: "8px",
}}>
<div
style={{
wordBreak: "break-word",
overflowWrap: "anywhere",
marginBottom: -10,
marginTop: -10,
}}>
<MarkdownBlock markdown={consultAdvisor.problem} />
</div>
{consultAdvisor.estimatedCost != null && (
<div
style={{
marginTop: 8,
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
borderTop: "1px solid var(--vscode-textSeparator-foreground)",
paddingTop: 8,
}}>
Estimated cost: ${Number(consultAdvisor.estimatedCost).toFixed(4)}
</div>
)}
</div>
<div
style={{
padding: 8,
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
}}>
You can change the Advisor model Cline consults with{" "}
<VSCodeLink
style={{ display: "inline", fontSize: "inherit" }}
onClick={() => vscode.postMessage({ type: "openAdvisorModelSettings" })}>
in API Settings.
</VSCodeLink>
</div>
</>
)
}
switch (message.type) {
case "say":
switch (message.say) {
@ -926,32 +842,6 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
<Markdown markdown={message.text} />
</div>
)
case "advisor_response":
return (
<div
style={{
border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 25%, transparent)",
borderRadius: "4px",
padding: "16px",
position: "relative",
marginTop: "10px",
}}>
<div
style={{
position: "absolute",
top: "-8px",
left: "16px",
backgroundColor: "var(--vscode-sideBar-background)",
padding: "0 8px",
color: "var(--vscode-descriptionForeground)",
fontSize: "12px",
textTransform: "uppercase",
}}>
Advisor Response
</div>
<Markdown markdown={message.text} />
</div>
)
case "user_feedback":
return (
<div

View file

@ -1,28 +1,8 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
import DynamicTextArea from "react-textarea-autosize"
import { mentionRegex, mentionRegexGlobal } from "../../../../src/shared/context-mentions"
import { useExtensionState } from "../../context/ExtensionStateContext"
import {
ContextMenuOptionType,
getContextMenuOptions,
insertMention,
removeMention,
shouldShowContextMenu,
} from "../../utils/context-mentions"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
import Thumbnails from "../common/Thumbnails"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useClickAway, useWindowSize } from "react-use"
import styled from "styled-components"
import { useEvent, useWindowSize } from "react-use"
import { vscode } from "../../utils/vscode"
import ApiOptions from "../settings/ApiOptions"
import { useClickAway } from "react-use"
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import { validateAdvisorModelId } from "../../utils/validate"
import { validateModelId } from "../../utils/validate"
import { validateApiConfiguration } from "../../utils/validate"
import {
anthropicDefaultModelId,
bedrockDefaultModelId,
@ -33,6 +13,22 @@ import {
openRouterDefaultModelId,
vertexDefaultModelId,
} from "../../../../src/shared/api"
import { mentionRegex, mentionRegexGlobal } from "../../../../src/shared/context-mentions"
import { useExtensionState } from "../../context/ExtensionStateContext"
import {
ContextMenuOptionType,
getContextMenuOptions,
insertMention,
removeMention,
shouldShowContextMenu,
} from "../../utils/context-mentions"
import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
import Thumbnails from "../common/Thumbnails"
import ApiOptions from "../settings/ApiOptions"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
interface ChatTextAreaProps {
inputValue: string
@ -237,7 +233,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const [intendedCursorPosition, setIntendedCursorPosition] = useState<number | null>(null)
const contextMenuContainerRef = useRef<HTMLDivElement>(null)
const [showModelSelector, setShowModelSelector] = useState(false)
const [showModelSelectorWithAdvisor, setShowModelSelectorWithAdvisor] = useState(false)
const modelSelectorRef = useRef<HTMLDivElement>(null)
const { width: viewportWidth, height: viewportHeight } = useWindowSize()
const buttonRef = useRef<HTMLDivElement>(null)
@ -657,9 +652,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const submitApiConfig = useCallback(() => {
const apiValidationResult = validateApiConfiguration(apiConfiguration)
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels)
if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) {
if (!apiValidationResult && !modelIdValidationResult) {
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
} else {
vscode.postMessage({ type: "getLatestState" })
@ -734,14 +728,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}
}, [showModelSelector, viewportWidth, viewportHeight])
// Reset advisor settings when model selector is closed
useEffect(() => {
if (!showModelSelector) {
// Attempt to save if possible
// NOTE: we cannot call this here since it will create an infinite loop between this effect and the callback since getLatestState will update state. Instead we should submitapiconfig when the menu is explicitly closed, rather than as an effect of showModelSelector changing.
// handleApiConfigSubmit()
setShowModelSelectorWithAdvisor(false)
// Reset any active styling by blurring the button
const button = buttonRef.current?.querySelector("a")
if (button) {
@ -750,18 +742,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}
}, [showModelSelector])
const handleMessage = useCallback((e: MessageEvent) => {
const message: ExtensionMessage = e.data
switch (message.type) {
case "openAdvisorModelSettings":
setShowModelSelector(true)
setShowModelSelectorWithAdvisor(true)
break
}
}, [])
useEvent("message", handleMessage)
return (
<div>
<div
@ -996,10 +976,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}}>
<ApiOptions
showModelOptions={true}
showAdvisorModelSettings={showModelSelectorWithAdvisor}
apiErrorMessage={undefined}
modelIdErrorMessage={undefined}
advisorModelIdErrorMessage={undefined}
isPopup={true}
/>
</ModelSelectorTooltip>

View file

@ -155,13 +155,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setPrimaryButtonText("Approve")
setSecondaryButtonText("Reject")
break
case "consult_advisor":
setTextAreaDisabled(isPartial)
setClineAsk("consult_advisor")
setEnableButtons(!isPartial)
setPrimaryButtonText("Approve")
setSecondaryButtonText("Reject")
break
case "completion_result":
// extension waiting for feedback. but we can just present a new task button
setTextAreaDisabled(isPartial)
@ -205,13 +198,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "error":
case "api_req_finished":
case "text":
case "advisor_response":
case "browser_action":
case "browser_action_result":
case "browser_action_launch":
case "command":
case "use_mcp_server":
case "consult_advisor":
case "command_output":
case "mcp_server_request_started":
case "mcp_server_response":
@ -284,7 +275,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "command": // user can provide feedback to a tool or command use
case "command_output": // user can send input to command stdin
case "use_mcp_server":
case "consult_advisor":
case "completion_result": // if this happens then the user has feedback for the completion result
case "resume_task":
case "resume_completed_task":
@ -327,7 +317,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "tool":
case "browser_action_launch":
case "use_mcp_server":
case "consult_advisor":
case "resume_task":
case "mistake_limit_reached":
case "auto_approval_max_req_reached":
@ -367,7 +356,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
case "tool":
case "browser_action_launch":
case "use_mcp_server":
case "consult_advisor":
// responds to the API with a "This operation failed" and lets it try again
vscode.postMessage({
type: "askResponse",

View file

@ -13,8 +13,6 @@ import {
ApiConfiguration,
ApiProvider,
ModelInfo,
ModelType,
anthropicDefaultAdvisorModelId,
anthropicDefaultModelId,
anthropicModels,
azureOpenAiDefaultApiVersion,
@ -29,8 +27,6 @@ import {
openAiModelInfoSaneDefaults,
openAiNativeDefaultModelId,
openAiNativeModels,
openRouterDefaultAdvisorModelId,
openRouterDefaultAdvisorModelInfo,
openRouterDefaultModelId,
openRouterDefaultModelInfo,
vertexDefaultModelId,
@ -48,47 +44,9 @@ interface ApiOptionsProps {
showModelOptions: boolean
apiErrorMessage?: string
modelIdErrorMessage?: string
advisorModelIdErrorMessage?: string
showAdvisorModelSettings?: boolean
isPopup?: boolean
}
const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelected: boolean }) => {
if (!isSelected) return null
return <div style={{ marginTop: 10 }}>{children}</div>
}
const StyledTabButton = styled.button<{ isSelected: boolean }>`
background: transparent;
border: none;
padding: 8px 16px;
color: ${(props) => (props.isSelected ? "var(--vscode-tab-activeForeground)" : "var(--vscode-tab-inactiveForeground)")};
cursor: pointer;
border-bottom: 2px solid ${(props) => (props.isSelected ? "var(--vscode-foreground)" : "transparent")};
font-size: 12px;
font-weight: 500;
&:hover {
color: var(--vscode-tab-activeForeground);
}
`
const TabButton = ({
isSelected,
onClick,
children,
}: {
isSelected: boolean
onClick: () => void
children: React.ReactNode
}) => {
return (
<StyledTabButton isSelected={isSelected} onClick={onClick}>
{children}
</StyledTabButton>
)
}
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
const DROPDOWN_Z_INDEX = 1001 // Higher than the OpenRouterModelPicker's and ModelSelectorTooltip's z-index
@ -113,14 +71,7 @@ declare module "vscode" {
}
}
const ApiOptions = ({
showModelOptions,
apiErrorMessage,
modelIdErrorMessage,
advisorModelIdErrorMessage,
showAdvisorModelSettings,
isPopup,
}: ApiOptionsProps) => {
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, isPopup }: ApiOptionsProps) => {
const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState()
const [ollamaModels, setOllamaModels] = useState<string[]>([])
const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
@ -128,7 +79,6 @@ const ApiOptions = ({
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
const [selectedTab, setSelectedTab] = useState(showAdvisorModelSettings ? "advisor" : "base")
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
setApiConfiguration({
@ -137,7 +87,7 @@ const ApiOptions = ({
})
}
const { selectedProvider, selectedModelId, selectedModelInfo, selectedAdvisorModelId } = useMemo(() => {
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
@ -187,16 +137,12 @@ const ApiOptions = ({
As a workaround, we create separate instances of the dropdown for each provider, and then conditionally render the one that matches the current provider.
*/
const createDropdown = (models: Record<string, ModelInfo>, modelType?: ModelType) => {
const createDropdown = (models: Record<string, ModelInfo>) => {
return (
<VSCodeDropdown
id="model-id"
// right now anthropic is the only non-openrouter provider that supports advisor models.
// if anthropic then selectedAdvisorId will always have value
value={modelType === "advisor" ? selectedAdvisorModelId || selectedModelId : selectedModelId}
onChange={
modelType === "advisor" ? handleInputChange("anthropicAdvisorModelId") : handleInputChange("apiModelId")
}
value={selectedModelId}
onChange={handleInputChange("apiModelId")}
style={{ width: "100%" }}>
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(models).map((modelId) => (
@ -866,7 +812,6 @@ const ApiOptions = ({
)}
{selectedProvider !== "openrouter" &&
selectedProvider !== "anthropic" &&
selectedProvider !== "openai" &&
selectedProvider !== "ollama" &&
selectedProvider !== "lmstudio" &&
@ -877,6 +822,7 @@ const ApiOptions = ({
<label htmlFor="model-id">
<span style={{ fontWeight: 500 }}>Model</span>
</label>
{selectedProvider === "anthropic" && createDropdown(anthropicModels)}
{selectedProvider === "bedrock" && createDropdown(bedrockModels)}
{selectedProvider === "vertex" && createDropdown(vertexModels)}
{selectedProvider === "gemini" && createDropdown(geminiModels)}
@ -895,7 +841,9 @@ const ApiOptions = ({
</>
)}
{selectedProvider !== "openrouter" && selectedProvider !== "anthropic" && modelIdErrorMessage && (
{selectedProvider === "openrouter" && showModelOptions && <OpenRouterModelPicker isPopup={isPopup} />}
{modelIdErrorMessage && (
<p
style={{
margin: "-10px 0 4px 0",
@ -905,79 +853,6 @@ const ApiOptions = ({
{modelIdErrorMessage}
</p>
)}
{(selectedProvider === "openrouter" || selectedProvider === "anthropic") && showModelOptions && (
<div style={{ marginTop: -5 }}>
<div style={{ display: "flex", borderBottom: "1px solid var(--vscode-panel-border)" }}>
<TabButton isSelected={selectedTab === "base"} onClick={() => setSelectedTab("base")}>
Cline Model
</TabButton>
<TabButton isSelected={selectedTab === "advisor"} onClick={() => setSelectedTab("advisor")}>
Advisor Model
</TabButton>
</div>
<TabPanel isSelected={selectedTab === "base"}>
<p
style={{
fontSize: "12px",
marginBottom: "10px",
color: "var(--vscode-foreground)",
}}>
This model is the default driver for Cline. It will read and edit files, run commands, and more, with
your permission at each step.
</p>
{selectedProvider === "anthropic" && (
<div className="dropdown-container" style={{ marginBottom: 15 }}>
{createDropdown(anthropicModels, "base")}
</div>
)}
{selectedProvider === "openrouter" && (
<OpenRouterModelPicker modelType="base" key="base-model-picker" isPopup={isPopup} />
)}
{modelIdErrorMessage && (
<p
style={{
margin: "-10px 0 4px 0",
fontSize: 12,
color: "var(--vscode-errorForeground)",
}}>
{modelIdErrorMessage}
</p>
)}
</TabPanel>
<TabPanel isSelected={selectedTab === "advisor"}>
<p
style={{
fontSize: "12px",
marginBottom: "10px",
color: "var(--vscode-foreground)",
}}>
The Cline model can consult this more powerful model for advice when running into roadblocks, such as
an error it cannot resolve.
</p>
{selectedProvider === "anthropic" && (
<div className="dropdown-container" style={{ marginBottom: 15 }}>
{createDropdown(anthropicModels, "advisor")}
</div>
)}
{selectedProvider === "openrouter" && (
<OpenRouterModelPicker modelType="advisor" key="advisor-model-picker" isPopup={isPopup} />
)}
{advisorModelIdErrorMessage && (
<p
style={{
margin: "-10px 0 4px 0",
fontSize: 12,
color: "var(--vscode-errorForeground)",
}}>
{advisorModelIdErrorMessage}
</p>
)}
</TabPanel>
</div>
)}
</div>
)
}
@ -1127,8 +1002,6 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
selectedProvider: ApiProvider
selectedModelId: string
selectedModelInfo: ModelInfo
selectedAdvisorModelId?: string
selectedAdvisorModelInfo?: ModelInfo
} {
const provider = apiConfiguration?.apiProvider || "anthropic"
const modelId = apiConfiguration?.apiModelId
@ -1151,10 +1024,7 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
}
switch (provider) {
case "anthropic":
return {
...getProviderData(anthropicModels, anthropicDefaultModelId),
selectedAdvisorModelId: apiConfiguration?.anthropicAdvisorModelId || anthropicDefaultAdvisorModelId,
}
return getProviderData(anthropicModels, anthropicDefaultModelId)
case "bedrock":
return getProviderData(bedrockModels, bedrockDefaultModelId)
case "vertex":
@ -1172,8 +1042,6 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
selectedProvider: provider,
selectedModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
selectedModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo,
selectedAdvisorModelId: apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId,
selectedAdvisorModelInfo: apiConfiguration?.openRouterAdvisorModelInfo || openRouterDefaultAdvisorModelInfo,
}
case "openai":
return {

View file

@ -4,12 +4,7 @@ import React, { KeyboardEvent, memo, useEffect, useMemo, useRef, useState } from
import { useRemark } from "react-remark"
import { useMount } from "react-use"
import styled from "styled-components"
import {
ModelType,
openRouterDefaultAdvisorModelId,
openRouterDefaultAdvisorModelInfo,
openRouterDefaultModelId,
} from "../../../../src/shared/api"
import { openRouterDefaultModelId } from "../../../../src/shared/api"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { vscode } from "../../utils/vscode"
import { highlight } from "../history/HistoryView"
@ -17,17 +12,12 @@ import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions"
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
export interface OpenRouterModelPickerProps {
modelType: ModelType
isPopup?: boolean
}
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ modelType, isPopup }) => {
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }) => {
const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState()
const [searchTerm, setSearchTerm] = useState(
modelType === "advisor"
? apiConfiguration?.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId
: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
)
const [searchTerm, setSearchTerm] = useState(apiConfiguration?.openRouterModelId || openRouterDefaultModelId)
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
const dropdownRef = useRef<HTMLDivElement>(null)
@ -39,20 +29,15 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ modelType
// could be setting invalid model id/undefined info but validation will catch it
setApiConfiguration({
...apiConfiguration,
...(modelType === "advisor"
? {
openRouterAdvisorModelId: newModelId,
openRouterAdvisorModelInfo: openRouterModels[newModelId],
}
: {
openRouterModelId: newModelId,
openRouterModelInfo: openRouterModels[newModelId],
}),
...{
openRouterModelId: newModelId,
openRouterModelInfo: openRouterModels[newModelId],
},
})
setSearchTerm(newModelId)
}
const { selectedModelId, selectedModelInfo, selectedAdvisorModelId, selectedAdvisorModelInfo } = useMemo(() => {
const { selectedModelId, selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
@ -161,9 +146,9 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ modelType
`}
</style>
<div style={{ display: "flex", flexDirection: "column" }}>
{/* <label htmlFor="model-search">
<label htmlFor="model-search">
<span style={{ fontWeight: 500 }}>Model</span>
</label> */}
</label>
<DropdownWrapper ref={dropdownRef}>
<VSCodeTextField
id="model-search"
@ -222,14 +207,8 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ modelType
{hasInfo ? (
<ModelInfoView
selectedModelId={
modelType === "advisor" ? selectedAdvisorModelId || openRouterDefaultAdvisorModelId : selectedModelId
}
modelInfo={
modelType === "advisor"
? selectedAdvisorModelInfo || openRouterDefaultAdvisorModelInfo
: selectedModelInfo
}
selectedModelId={selectedModelId}
modelInfo={selectedModelInfo}
isDescriptionExpanded={isDescriptionExpanded}
setIsDescriptionExpanded={setIsDescriptionExpanded}
isPopup={isPopup}
@ -241,31 +220,19 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ modelType
marginTop: 0,
color: "var(--vscode-descriptionForeground)",
}}>
{modelType === "base" ? (
<>
The extension automatically fetches the latest list of models available on{" "}
<VSCodeLink style={{ display: "inline", fontSize: "inherit" }} href="https://openrouter.ai/models">
OpenRouter.
</VSCodeLink>
If you're unsure which model to choose, Cline works best with{" "}
<VSCodeLink
style={{ display: "inline", fontSize: "inherit" }}
onClick={() => handleModelChange("anthropic/claude-3.5-sonnet:beta")}>
anthropic/claude-3.5-sonnet:beta.
</VSCodeLink>
You can also try searching "free" for no-cost options currently available.
</>
) : (
<>
It's recommended using a higher-reasoning model such as{" "}
<VSCodeLink
style={{ display: "inline", fontSize: "inherit" }}
onClick={() => handleModelChange("openai/o1-preview")}>
openai/o1-preview
</VSCodeLink>
for the best results.
</>
)}
<>
The extension automatically fetches the latest list of models available on{" "}
<VSCodeLink style={{ display: "inline", fontSize: "inherit" }} href="https://openrouter.ai/models">
OpenRouter.
</VSCodeLink>
If you're unsure which model to choose, Cline works best with{" "}
<VSCodeLink
style={{ display: "inline", fontSize: "inherit" }}
onClick={() => handleModelChange("anthropic/claude-3.5-sonnet:beta")}>
anthropic/claude-3.5-sonnet:beta.
</VSCodeLink>
You can also try searching "free" for no-cost options currently available.
</>
</p>
)}
</div>

View file

@ -1,7 +1,7 @@
import { VSCodeButton, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
import { memo, useEffect, useState } from "react"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { validateAdvisorModelId, validateApiConfiguration, validateModelId } from "../../utils/validate"
import { validateApiConfiguration, validateModelId } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "./ApiOptions"
@ -15,18 +15,15 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
const [advisorModelIdErrorMessage, setAdvisorModelIdErrorMessage] = useState<string | undefined>(undefined)
const handleSubmit = () => {
const apiValidationResult = validateApiConfiguration(apiConfiguration)
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
const advisorModelIdValidationResult = validateAdvisorModelId(apiConfiguration, openRouterModels)
setApiErrorMessage(apiValidationResult)
setModelIdErrorMessage(modelIdValidationResult)
setAdvisorModelIdErrorMessage(advisorModelIdValidationResult)
if (!apiValidationResult && !modelIdValidationResult && !advisorModelIdValidationResult) {
if (!apiValidationResult && !modelIdValidationResult) {
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
vscode.postMessage({
type: "customInstructions",
@ -39,7 +36,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
useEffect(() => {
setApiErrorMessage(undefined)
setModelIdErrorMessage(undefined)
setAdvisorModelIdErrorMessage(undefined)
}, [apiConfiguration])
// validate as soon as the component is mounted
@ -95,7 +91,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
showModelOptions={true}
apiErrorMessage={apiErrorMessage}
modelIdErrorMessage={modelIdErrorMessage}
advisorModelIdErrorMessage={advisorModelIdErrorMessage}
/>
</div>

View file

@ -2,14 +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,
openRouterDefaultAdvisorModelId,
openRouterDefaultAdvisorModelInfo,
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"
@ -49,7 +42,6 @@ export const ExtensionStateContextProvider: React.FC<{
const [filePaths, setFilePaths] = useState<string[]>([])
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
[openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo,
})
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
@ -107,7 +99,6 @@ export const ExtensionStateContextProvider: React.FC<{
const updatedModels = message.openRouterModels ?? {}
setOpenRouterModels({
[openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model
[openRouterDefaultAdvisorModelId]: openRouterDefaultAdvisorModelInfo,
...updatedModels,
})
break

View file

@ -1,4 +1,4 @@
import { ApiConfiguration, openRouterDefaultAdvisorModelId, openRouterDefaultModelId } from "../../../src/shared/api"
import { ApiConfiguration, openRouterDefaultModelId } from "../../../src/shared/api"
import { ModelInfo } from "../../../src/shared/api"
export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined {
if (apiConfiguration) {
@ -88,23 +88,3 @@ export function validateModelId(
}
return undefined
}
export function validateAdvisorModelId(
apiConfiguration?: ApiConfiguration,
openRouterModels?: Record<string, ModelInfo>,
): string | undefined {
if (apiConfiguration) {
switch (apiConfiguration.apiProvider) {
case "openrouter":
const advisorModelId = apiConfiguration.openRouterAdvisorModelId || openRouterDefaultAdvisorModelId // in case the user hasn't changed the model id, it will be undefined by default
if (!advisorModelId) {
return "You must provide a model ID."
}
if (openRouterModels && !Object.keys(openRouterModels).includes(advisorModelId)) {
return "The model ID you provided is not available. Please choose a different model."
}
break
}
}
return undefined
}