mirror of
https://github.com/cline/cline.git
synced 2026-09-11 05:47:07 +08:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46cbeae022 | ||
|
|
7d0dd3d7be | ||
|
|
d8203d13bb | ||
|
|
739db3014c | ||
|
|
33fe2cb555 | ||
|
|
d1a31619f7 | ||
|
|
ebe4999625 | ||
|
|
ef30c08c2c |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
updating o3 model pricing
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Sorting mcp marketplace by newest listings by default
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Migrate focusChatInput message to protobus
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix menu for setting terminal timeout
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add prompt caching indicator to grok 3
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Supporting Notifications MCP with Cline
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding Thinking UX for Gemini
|
||||
@@ -1,46 +1,10 @@
|
||||
<!-- ⚠️ Important: Discussion Required Before PR (Community Contributors) -->
|
||||
|
||||
**For community contributors, before submitting this PR, please ensure you have:**
|
||||
|
||||
- [ ] **Opened an issue** to discuss your proposed changes with the community
|
||||
- [ ] **Received approval** from a core Cline contributor to proceed with the implementation
|
||||
- [ ] **Linked the issue below** in the "Related Issue" section
|
||||
|
||||
**Exceptions:** Small bug fixes, typo corrections, minor wording improvements, or simple type fixes that don't change functionality may be submitted directly.
|
||||
|
||||
**Why this requirement?** We deeply appreciate all community contributions - they are the core reason we're able to operate successfully and keep innovating! We welcome community input and want to make it as easy as possible for people to submit quality work. This process helps our core maintainers review new ideas faster and saves contributor time by ensuring you have the go-ahead before spending time on implementation.
|
||||
|
||||
---
|
||||
|
||||
### Related Issue
|
||||
|
||||
<!-- Replace XXXX with the issue number that this PR addresses -->
|
||||
**Issue:** #XXXX
|
||||
|
||||
### Description
|
||||
|
||||
<!--
|
||||
Help reviewers understand your changes by making this PR readable and well-organized:
|
||||
|
||||
- What problem does this PR solve?
|
||||
- Why were these changes introduced and what purpose do they serve?
|
||||
- For larger changes, provide context about your approach and reasoning
|
||||
|
||||
Small PRs may need minimal description, but larger changes benefit from explaining where you're coming from. Much of this context can be in the linked issue above, so feel free to reference it rather than repeating everything here.
|
||||
-->
|
||||
<!-- Describe your changes in detail. What problem does this PR solve? -->
|
||||
|
||||
### Test Procedure
|
||||
|
||||
<!--
|
||||
Please walk us through your testing approach and thought process. This helps reviewers understand that you've thoroughly considered the impact of your changes:
|
||||
|
||||
- How did you test this change?
|
||||
- What could potentially break and how did you verify it doesn't?
|
||||
- What existing functionality might be affected and how did you check it still works?
|
||||
- Why are you confident this is ready for merge?
|
||||
|
||||
We're not looking for exhaustive documentation - just evidence that you've thought through the implications of your changes and tested accordingly.
|
||||
-->
|
||||
<!-- How did you test this? Are you confident that it will not introduce bugs? If so, why? -->
|
||||
|
||||
### Type of Change
|
||||
|
||||
@@ -65,15 +29,7 @@ We're not looking for exhaustive documentation - just evidence that you've thoug
|
||||
|
||||
### Screenshots
|
||||
|
||||
<!--
|
||||
Help reviewers quickly understand your changes:
|
||||
|
||||
- **UI Changes**: Please include screenshots showing before/after states
|
||||
- **Complex Workflows**: Consider uploading a screen recording (video) if your changes involve multiple steps or state transitions
|
||||
- **Backend Changes**: Not required, but feel free to include terminal output or other evidence that demonstrates functionality
|
||||
|
||||
This helps reviewers see what you've built without having to pull down and test your branch first.
|
||||
-->
|
||||
<!-- For UI changes, add screenshots here -->
|
||||
|
||||
### Additional Notes
|
||||
|
||||
|
||||
+4
-4
@@ -32,8 +32,8 @@ src/shared/proto/host/*.ts
|
||||
webview-ui/src/services/grpc-client.ts
|
||||
# Standalone
|
||||
src/standalone/server-setup.ts
|
||||
src/standalone/services/host-grpc-client.ts
|
||||
# Host bridge
|
||||
src/hosts/vscode/*/methods.ts
|
||||
src/hosts/vscode/*/index.ts
|
||||
src/hosts/vscode/client/host-grpc-client.ts
|
||||
src/hosts/vscode/host-grpc-service-config.ts
|
||||
hosts/vscode/*/methods.ts
|
||||
hosts/vscode/*/index.ts
|
||||
hosts/vscode/host-grpc-service-config.ts
|
||||
|
||||
Vendored
+2
-38
@@ -128,25 +128,7 @@
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild",
|
||||
"group": "build",
|
||||
"problemMatcher": {
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^✘ \\[ERROR\\] (.*)$",
|
||||
"message": 1
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(.*):(\\d+):(\\d+):$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^\\[watch\\] build started$",
|
||||
"endsPattern": "^\\[watch\\] build finished$"
|
||||
}
|
||||
},
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild",
|
||||
"dependsOn": ["npm: protos"],
|
||||
@@ -164,25 +146,7 @@
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild:test",
|
||||
"group": "build",
|
||||
"problemMatcher": {
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^✘ \\[ERROR\\] (.*)$",
|
||||
"message": 1
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(.*):(\\d+):(\\d+):$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^\\[watch\\] build started$",
|
||||
"endsPattern": "^\\[watch\\] build finished$"
|
||||
}
|
||||
},
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch:esbuild:test",
|
||||
"dependsOn": ["npm: protos"],
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
version: v2
|
||||
modules:
|
||||
- path: proto
|
||||
name: cline/cline/lint
|
||||
|
||||
lint:
|
||||
use:
|
||||
- STANDARD
|
||||
|
||||
except: # Add exceptions for current patterns that contradict STANDARD settings
|
||||
- RPC_PASCAL_CASE # rpcs are camel case (start with lowercase)
|
||||
- PACKAGE_DIRECTORY_MATCH # the protos in the cline package are not in a dir named cline.
|
||||
- RPC_REQUEST_RESPONSE_UNIQUE # request messages are not unique.
|
||||
- RPC_REQUEST_STANDARD_NAME # request messages dont all end with Request
|
||||
- RPC_RESPONSE_STANDARD_NAME # response messages dont all end with Response
|
||||
- PACKAGE_VERSION_SUFFIX # package name does not contain version.
|
||||
- ENUM_VALUE_PREFIX # enum values dont start with the enum name.
|
||||
- ENUM_ZERO_VALUE_SUFFIX # first value does not have to be UNSPECIFIED.
|
||||
|
||||
# breaking:
|
||||
# use:
|
||||
# - WIRE_JSON # Detect changes that break the json wire format (this is the minimum recommended level.)
|
||||
@@ -13,7 +13,6 @@ interface RunDiffEvalOptions {
|
||||
verbose: boolean
|
||||
testPath: string
|
||||
outputPath: string
|
||||
replay: boolean
|
||||
}
|
||||
|
||||
export async function runDiffEvalHandler(options: RunDiffEvalOptions) {
|
||||
@@ -51,10 +50,6 @@ export async function runDiffEvalHandler(options: RunDiffEvalOptions) {
|
||||
args.push("--parallel")
|
||||
}
|
||||
|
||||
if (options.replay) {
|
||||
args.push("--replay")
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
args.push("--verbose")
|
||||
}
|
||||
|
||||
@@ -91,7 +91,6 @@ program
|
||||
.option("--diff-edit-function <name>", "The diff editing function to use", "constructNewFileContentV2")
|
||||
.option("--thinking-budget <tokens>", "Set the thinking tokens budget", "0")
|
||||
.option("--parallel", "Run tests in parallel", false)
|
||||
.option("--replay", "Run evaluation from a pre-recorded LLM output, skipping the API call", false)
|
||||
.option("-v, --verbose", "Enable verbose logging", false)
|
||||
.action(async (options) => {
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
parseAssistantMessageV3,
|
||||
AssistantMessageContent,
|
||||
} from "./parsing/parse-assistant-message-06-06-25" // "../../src/core/assistant-message"
|
||||
import { constructNewFileContent as constructNewFileContentV1, constructNewFileContentV2 } from "./diff-apply/diff-06-06-25"
|
||||
import { constructNewFileContentV2 } from "./diff-apply/diff-06-06-25"
|
||||
import { constructNewFileContent as constructNewFileContentV3 } from "../../src/core/assistant-message/diff" // this defaults to the new v1 when called
|
||||
|
||||
type ParseAssistantMessageFn = (message: string) => AssistantMessageContent[]
|
||||
@@ -21,7 +21,6 @@ const parsingFunctions: Record<string, ParseAssistantMessageFn> = {
|
||||
}
|
||||
|
||||
const diffEditingFunctions: Record<string, ConstructNewFileContentFn> = {
|
||||
constructNewFileContentV1: constructNewFileContentV1,
|
||||
constructNewFileContentV2: constructNewFileContentV2,
|
||||
constructNewFileContentV3: constructNewFileContentV3, // position invariant diff
|
||||
}
|
||||
@@ -115,10 +114,10 @@ export async function runSingleEvaluation(input: TestInput): Promise<TestResult>
|
||||
parsingFunction,
|
||||
diffEditFunction,
|
||||
thinkingBudgetTokens,
|
||||
originalDiffEditToolCallMessage,
|
||||
} = input
|
||||
|
||||
const requiredParams = {
|
||||
apiKey,
|
||||
systemPrompt,
|
||||
messages,
|
||||
modelId,
|
||||
@@ -164,26 +163,17 @@ export async function runSingleEvaluation(input: TestInput): Promise<TestResult>
|
||||
},
|
||||
}
|
||||
|
||||
const openRouterHandler = new OpenRouterHandler(options)
|
||||
|
||||
// Get the output of streaming output of this llm call
|
||||
let streamResult: StreamResult
|
||||
if (originalDiffEditToolCallMessage !== undefined) {
|
||||
// Replay mode: mock the stream result
|
||||
streamResult = {
|
||||
assistantMessage: originalDiffEditToolCallMessage,
|
||||
reasoningMessage: "",
|
||||
usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0, totalCost: 0 },
|
||||
}
|
||||
} else {
|
||||
// Live mode: existing API call logic
|
||||
try {
|
||||
const openRouterHandler = new OpenRouterHandler(options)
|
||||
streamResult = await processStream(openRouterHandler, systemPrompt, messages)
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: "llm_stream_error",
|
||||
errorString: error.message || error.toString(),
|
||||
}
|
||||
try {
|
||||
streamResult = await processStream(openRouterHandler, systemPrompt, messages)
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: "llm_stream_error",
|
||||
errorString: error.message || error.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,12 @@ const systemPromptGeneratorLookup: Record<string, ConstructSystemPromptFn> = {
|
||||
type TestResultSet = { [test_id: string]: (TestResult & { test_id?: string })[] }
|
||||
|
||||
class NodeTestRunner {
|
||||
private apiKey: string | undefined
|
||||
private apiKey: string
|
||||
|
||||
constructor(isReplay: boolean) {
|
||||
if (!isReplay) {
|
||||
this.apiKey = process.env.OPENROUTER_API_KEY
|
||||
if (!this.apiKey) {
|
||||
throw new Error("OPENROUTER_API_KEY environment variable not set for a non-replay run.")
|
||||
}
|
||||
constructor() {
|
||||
this.apiKey = process.env.OPENROUTER_API_KEY!
|
||||
if (!this.apiKey) {
|
||||
throw new Error("OPENROUTER_API_KEY environment variable not set")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,14 +125,6 @@ class NodeTestRunner {
|
||||
* Run a single test example
|
||||
*/
|
||||
async runSingleTest(testCase: ProcessedTestCase, testConfig: TestConfig): Promise<TestResult> {
|
||||
if (testConfig.replay && !testCase.original_diff_edit_tool_call_message) {
|
||||
return {
|
||||
success: false,
|
||||
error: "missing_original_diff_edit_tool_call_message",
|
||||
errorString: `Test case ${testCase.test_id} is missing 'original_diff_edit_tool_call_message' for replay.`,
|
||||
}
|
||||
}
|
||||
|
||||
const customSystemPrompt = this.constructSystemPrompt(testCase.system_prompt_details, testConfig.system_prompt_name)
|
||||
|
||||
// messages don't include system prompt and are everything up to the first replace_in_file tool call which results in a diff edit error
|
||||
@@ -148,7 +138,6 @@ class NodeTestRunner {
|
||||
parsingFunction: testConfig.parsing_function,
|
||||
diffEditFunction: testConfig.diff_edit_function,
|
||||
thinkingBudgetTokens: testConfig.thinking_tokens_budget,
|
||||
originalDiffEditToolCallMessage: testConfig.replay ? testCase.original_diff_edit_tool_call_message : undefined,
|
||||
}
|
||||
|
||||
return await runSingleEvaluation(input)
|
||||
@@ -331,7 +320,6 @@ async function main() {
|
||||
.option("--diff-edit-function <name>", "The diff editing function to use", "constructNewFileContentV2")
|
||||
.option("--thinking-budget <tokens>", "Set the thinking tokens budget", "0")
|
||||
.option("--parallel", "Run tests in parallel", false)
|
||||
.option("--replay", "Run evaluation from a pre-recorded LLM output, skipping the API call", false)
|
||||
.option("-v, --verbose", "Enable verbose logging", false)
|
||||
|
||||
program.parse(process.argv)
|
||||
@@ -348,13 +336,12 @@ async function main() {
|
||||
parsing_function: options.parsingFunction,
|
||||
diff_edit_function: options.diffEditFunction,
|
||||
thinking_tokens_budget: parseInt(options.thinkingBudget, 10),
|
||||
replay: options.replay,
|
||||
}
|
||||
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
|
||||
const runner = new NodeTestRunner(testConfig.replay)
|
||||
const runner = new NodeTestRunner()
|
||||
const testCases = runner.loadTestCases(testPath)
|
||||
|
||||
const processedTestCases: ProcessedTestCase[] = testCases.map((tc) => ({
|
||||
@@ -364,9 +351,6 @@ async function main() {
|
||||
|
||||
log(isVerbose, `-Loaded ${testCases.length} test cases.`)
|
||||
log(isVerbose, `-Executing ${testConfig.number_of_runs} run(s) per test case.`)
|
||||
if (testConfig.replay) {
|
||||
log(isVerbose, `-Running in REPLAY mode. No API calls will be made.`)
|
||||
}
|
||||
log(isVerbose, "Starting tests...\n")
|
||||
|
||||
const results = options.parallel
|
||||
|
||||
@@ -211,7 +211,7 @@ export async function constructNewFileContent(
|
||||
diffContent: string,
|
||||
originalContent: string,
|
||||
isFinal: boolean,
|
||||
version: "v1" | "v2" = "v1",
|
||||
version: "v1" | "v2" = "v2",
|
||||
): Promise<string> {
|
||||
const constructor = constructNewFileContentVersionMapping[version]
|
||||
if (!constructor) {
|
||||
|
||||
@@ -13,7 +13,6 @@ export interface ProcessedTestCase {
|
||||
file_contents: string
|
||||
file_path: string
|
||||
system_prompt_details: SystemPromptDetails
|
||||
original_diff_edit_tool_call_message: string
|
||||
}
|
||||
|
||||
export interface TestCase {
|
||||
@@ -22,7 +21,6 @@ export interface TestCase {
|
||||
file_contents: string
|
||||
file_path: string
|
||||
system_prompt_details: SystemPromptDetails
|
||||
original_diff_edit_tool_call_message: string
|
||||
}
|
||||
|
||||
export interface TestConfig {
|
||||
@@ -32,7 +30,6 @@ export interface TestConfig {
|
||||
parsing_function: string
|
||||
diff_edit_function: string
|
||||
thinking_tokens_budget: number
|
||||
replay: boolean
|
||||
}
|
||||
|
||||
export interface SystemPromptDetails {
|
||||
@@ -75,7 +72,7 @@ export interface ExtractedToolCall {
|
||||
}
|
||||
|
||||
export interface TestInput {
|
||||
apiKey?: string
|
||||
apiKey: string
|
||||
systemPrompt: string
|
||||
messages: Anthropic.Messages.MessageParam[]
|
||||
modelId: string
|
||||
@@ -84,5 +81,4 @@ export interface TestInput {
|
||||
parsingFunction: string
|
||||
diffEditFunction: string
|
||||
thinkingBudgetTokens: number
|
||||
originalDiffEditToolCallMessage?: string
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { hostServiceHandlers } from "./host-grpc-service-config"
|
||||
import { GrpcRequestRegistry } from "@core/controller/grpc-request-registry"
|
||||
import { GrpcRequestRegistry } from "../../src/core/controller/grpc-request-registry"
|
||||
|
||||
/**
|
||||
* Type definition for a streaming response handler
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Uri } from "@shared/proto/host/uri"
|
||||
import { StringRequest } from "@shared/proto/common"
|
||||
import { Uri } from "../../../src/shared/proto/host/uri"
|
||||
import { StringRequest } from "../../../src/shared/proto/common"
|
||||
|
||||
/**
|
||||
* Creates a file URI from a file path
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as vscode from "vscode"
|
||||
import { JoinPathRequest, Uri } from "@shared/proto/host/uri"
|
||||
import { JoinPathRequest, Uri } from "../../../src/shared/proto/host/uri"
|
||||
|
||||
/**
|
||||
* Joins a URI with additional path segments
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Uri } from "@shared/proto/host/uri"
|
||||
import { StringRequest } from "@shared/proto/common"
|
||||
import { Uri } from "../../../src/shared/proto/host/uri"
|
||||
import { StringRequest } from "../../../src/shared/proto/common"
|
||||
|
||||
/**
|
||||
* Parses a string URI into a Uri object
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from "fs/promises"
|
||||
import * as fsSync from "fs"
|
||||
import { SubscribeToFileRequest, FileChangeEvent, FileChangeEvent_ChangeType } from "@shared/proto/host/watch"
|
||||
import { SubscribeToFileRequest, FileChangeEvent, FileChangeEvent_ChangeType } from "../../../src/shared/proto/host/watch"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../host-grpc-handler"
|
||||
|
||||
// Debounce configuration
|
||||
Generated
+10
-216
@@ -16,7 +16,7 @@
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
"@google/genai": "^0.13.0",
|
||||
"@google/genai": "1.0.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
@@ -76,7 +76,6 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bufbuild/buf": "^1.54.0",
|
||||
"@changesets/cli": "^2.27.12",
|
||||
"@types/chai": "^5.0.1",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
@@ -4219,150 +4218,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@bufbuild/buf": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.54.0.tgz",
|
||||
"integrity": "sha512-UkjZmVslA7YAxhUQVxE2O4HX4qD7aMspjkuG3vsjnvmAkiV6Jhz47z3focCuPI28e59H20TiQNhc9Y3fkffWPw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"buf": "bin/buf",
|
||||
"protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking",
|
||||
"protoc-gen-buf-lint": "bin/protoc-gen-buf-lint"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@bufbuild/buf-darwin-arm64": "1.54.0",
|
||||
"@bufbuild/buf-darwin-x64": "1.54.0",
|
||||
"@bufbuild/buf-linux-aarch64": "1.54.0",
|
||||
"@bufbuild/buf-linux-armv7": "1.54.0",
|
||||
"@bufbuild/buf-linux-x64": "1.54.0",
|
||||
"@bufbuild/buf-win32-arm64": "1.54.0",
|
||||
"@bufbuild/buf-win32-x64": "1.54.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-darwin-arm64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.54.0.tgz",
|
||||
"integrity": "sha512-MkwlxcuHH8YO2wyQ2nGAv5SwBRCR4PtA8zcQb7AR6q93Cgy314ac8blGjfpenprjI3kAAhxc9BQK4t+/hkIS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-darwin-x64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.54.0.tgz",
|
||||
"integrity": "sha512-59Z+6BxvVwBbcpLOAwD8TLobngb9YUvUZ1nnP1IyIJnay/tIY+yfmgAdgMwm3VUZlbaFlURGmD34UAwEsxodGQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-linux-aarch64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.54.0.tgz",
|
||||
"integrity": "sha512-cUbvujfoAQGsnRH/+UfKxt0Hfe6PGHjM/gLiC2Kgv8fcoIWjPJMBBgdl/TLbq1QrVcCXSvMc16hW5ias7Jdyfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-linux-armv7": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.54.0.tgz",
|
||||
"integrity": "sha512-xdKjzPsOo6E2eth3uGIRoVG9TpPVHOUucr0MeCRVhM2hb5gbM8KQLn6iDxVGbQFq6eL2qe+B0b8k9HfuwzirWA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-linux-x64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.54.0.tgz",
|
||||
"integrity": "sha512-ZnfaE5GLAhyvR/ponDgG+s6FbtMEm+RaS2f0EoBLORYC7sK/Elfmw2Q0XcjHyEl83u4hELCqej9T0eUxbgxtow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-win32-arm64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.54.0.tgz",
|
||||
"integrity": "sha512-N5YlX8c6p+KZIWYmx03viYF/FLuY5GyzHgor17nuJUYhF1xFyIJL8v4mhqcQ8Pq0xua9IyRwmSxHJKyrdNatcg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/buf-win32-x64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.54.0.tgz",
|
||||
"integrity": "sha512-PepTA9RcLCjukQhFPFBqKXF9mVwct+ZSBeuLjFuUVcHovdGUZXspNTb5LnuIDjWXx2fcALs0xb/FNUNd6pNjbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz",
|
||||
@@ -5643,9 +5498,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@google/genai": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.13.0.tgz",
|
||||
"integrity": "sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.0.0.tgz",
|
||||
"integrity": "sha512-IQiL8UlPblGDrMhTuiHZbfMDVx0KY3eYkmB5Ro9wwyXovYCFIhL5ZC7LP42FjFUj0eWUa4Auo8Ixqf2dqx9JjA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"google-auth-library": "^9.14.2",
|
||||
@@ -5655,6 +5510,9 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
@@ -30226,70 +30084,6 @@
|
||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||
"dev": true
|
||||
},
|
||||
"@bufbuild/buf": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.54.0.tgz",
|
||||
"integrity": "sha512-UkjZmVslA7YAxhUQVxE2O4HX4qD7aMspjkuG3vsjnvmAkiV6Jhz47z3focCuPI28e59H20TiQNhc9Y3fkffWPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@bufbuild/buf-darwin-arm64": "1.54.0",
|
||||
"@bufbuild/buf-darwin-x64": "1.54.0",
|
||||
"@bufbuild/buf-linux-aarch64": "1.54.0",
|
||||
"@bufbuild/buf-linux-armv7": "1.54.0",
|
||||
"@bufbuild/buf-linux-x64": "1.54.0",
|
||||
"@bufbuild/buf-win32-arm64": "1.54.0",
|
||||
"@bufbuild/buf-win32-x64": "1.54.0"
|
||||
}
|
||||
},
|
||||
"@bufbuild/buf-darwin-arm64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.54.0.tgz",
|
||||
"integrity": "sha512-MkwlxcuHH8YO2wyQ2nGAv5SwBRCR4PtA8zcQb7AR6q93Cgy314ac8blGjfpenprjI3kAAhxc9BQK4t+/hkIS/A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/buf-darwin-x64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.54.0.tgz",
|
||||
"integrity": "sha512-59Z+6BxvVwBbcpLOAwD8TLobngb9YUvUZ1nnP1IyIJnay/tIY+yfmgAdgMwm3VUZlbaFlURGmD34UAwEsxodGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/buf-linux-aarch64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.54.0.tgz",
|
||||
"integrity": "sha512-cUbvujfoAQGsnRH/+UfKxt0Hfe6PGHjM/gLiC2Kgv8fcoIWjPJMBBgdl/TLbq1QrVcCXSvMc16hW5ias7Jdyfw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/buf-linux-armv7": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.54.0.tgz",
|
||||
"integrity": "sha512-xdKjzPsOo6E2eth3uGIRoVG9TpPVHOUucr0MeCRVhM2hb5gbM8KQLn6iDxVGbQFq6eL2qe+B0b8k9HfuwzirWA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/buf-linux-x64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.54.0.tgz",
|
||||
"integrity": "sha512-ZnfaE5GLAhyvR/ponDgG+s6FbtMEm+RaS2f0EoBLORYC7sK/Elfmw2Q0XcjHyEl83u4hELCqej9T0eUxbgxtow==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/buf-win32-arm64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.54.0.tgz",
|
||||
"integrity": "sha512-N5YlX8c6p+KZIWYmx03viYF/FLuY5GyzHgor17nuJUYhF1xFyIJL8v4mhqcQ8Pq0xua9IyRwmSxHJKyrdNatcg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/buf-win32-x64": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.54.0.tgz",
|
||||
"integrity": "sha512-PepTA9RcLCjukQhFPFBqKXF9mVwct+ZSBeuLjFuUVcHovdGUZXspNTb5LnuIDjWXx2fcALs0xb/FNUNd6pNjbA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@bufbuild/protobuf": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz",
|
||||
@@ -31391,9 +31185,9 @@
|
||||
}
|
||||
},
|
||||
"@google/genai": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.13.0.tgz",
|
||||
"integrity": "sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.0.0.tgz",
|
||||
"integrity": "sha512-IQiL8UlPblGDrMhTuiHZbfMDVx0KY3eYkmB5Ro9wwyXovYCFIhL5ZC7LP42FjFUj0eWUa4Auo8Ixqf2dqx9JjA==",
|
||||
"requires": {
|
||||
"google-auth-library": "^9.14.2",
|
||||
"ws": "^8.18.0",
|
||||
|
||||
+3
-4
@@ -331,12 +331,12 @@
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.js --production",
|
||||
"protos": "node proto/build-proto.js && node scripts/generate-server-setup.mjs",
|
||||
"postprotos": "prettier src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/standalone/server-setup.ts --write --log-level silent",
|
||||
"postprotos": "prettier src/shared/proto src/core/controller webview-ui/src/services src/standalone/server-setup.ts --write --log-level silent",
|
||||
"compile-tests": "node ./scripts/build-tests.js",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"pretest": "npm run compile-tests && npm run compile && npm run compile-standalone && npm run lint",
|
||||
"check-types": "npm run protos && tsc --noEmit",
|
||||
"lint": "eslint src --ext ts && eslint webview-ui/src --ext ts && buf lint && cd webview-ui && npm run lint",
|
||||
"lint": "eslint src --ext ts && eslint webview-ui/src --ext ts && cd webview-ui && npm run lint",
|
||||
"format": "prettier . --check",
|
||||
"format:fix": "prettier . --write",
|
||||
"test": "npm-run-all test:unit test:integration",
|
||||
@@ -359,7 +359,6 @@
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bufbuild/buf": "^1.54.0",
|
||||
"@changesets/cli": "^2.27.12",
|
||||
"@types/chai": "^5.0.1",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
@@ -405,7 +404,7 @@
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
"@google/genai": "^0.13.0",
|
||||
"@google/genai": "1.0.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
|
||||
+16
-48
@@ -15,24 +15,12 @@ const protoc = path.join(require.resolve("grpc-tools"), "../bin/protoc")
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const SCRIPT_DIR = path.dirname(__filename)
|
||||
const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
|
||||
const TS_OUT_DIR = path.join(ROOT_DIR, "src", "shared", "proto")
|
||||
|
||||
const isWindows = process.platform === "win32"
|
||||
const tsProtoPlugin = isWindows
|
||||
? path.join(ROOT_DIR, "node_modules", ".bin", "protoc-gen-ts_proto.cmd") // Use the .bin directory path for Windows
|
||||
: require.resolve("ts-proto/protoc-gen-ts_proto")
|
||||
|
||||
const TS_PROTO_OPTIONS = [
|
||||
"env=node",
|
||||
"esModuleInterop=true",
|
||||
|
||||
"outputIndex=true", // output an index file for each package which exports all protos in the package.
|
||||
"outputServices=generic-definitions",
|
||||
|
||||
"useOptionals=messages", // Message fields are optional, scalars are not.
|
||||
"useDate=false", // Timestamp fields will not be automatically converted to Date.
|
||||
]
|
||||
|
||||
// List of gRPC services
|
||||
// To add a new service, simply add it to this map and run this script
|
||||
// The service handler will be automatically discovered and used by grpc-handler.ts
|
||||
@@ -59,9 +47,7 @@ const hostServiceNameMap = {
|
||||
watch: "host.WatchService",
|
||||
// Add new host services here
|
||||
}
|
||||
const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) =>
|
||||
path.join(ROOT_DIR, "src", "hosts", "vscode", serviceKey),
|
||||
)
|
||||
const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) => path.join(ROOT_DIR, "hosts", "vscode", serviceKey))
|
||||
|
||||
async function main() {
|
||||
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
|
||||
@@ -69,9 +55,18 @@ async function main() {
|
||||
// Check for Apple Silicon compatibility before proceeding
|
||||
checkAppleSiliconCompatibility()
|
||||
|
||||
// Define output directories
|
||||
const TS_OUT_DIR = path.join(ROOT_DIR, "src", "shared", "proto")
|
||||
|
||||
// Create output directories if they don't exist
|
||||
await fs.mkdir(TS_OUT_DIR, { recursive: true })
|
||||
await cleanup()
|
||||
|
||||
// Clean up existing generated files
|
||||
console.log(chalk.cyan("Cleaning up existing generated TypeScript files..."))
|
||||
const existingFiles = await globby("**/*.ts", { cwd: TS_OUT_DIR })
|
||||
for (const file of existingFiles) {
|
||||
await fs.unlink(path.join(TS_OUT_DIR, file))
|
||||
}
|
||||
|
||||
// Check for missing proto files for services in serviceNameMap
|
||||
await ensureProtoFilesExist()
|
||||
@@ -86,7 +81,9 @@ async function main() {
|
||||
`--proto_path="${SCRIPT_DIR}"`,
|
||||
`--plugin=protoc-gen-ts_proto="${tsProtoPlugin}"`,
|
||||
`--ts_proto_out="${TS_OUT_DIR}"`,
|
||||
`--ts_proto_opt=${TS_PROTO_OPTIONS.join(",")} `,
|
||||
"--ts_proto_opt=exportCommonSymbols=false",
|
||||
"--ts_proto_opt=outputIndex=true",
|
||||
"--ts_proto_opt=outputServices=generic-definitions,env=node,esModuleInterop=true,useDate=false,useOptionals=messages",
|
||||
...protoFiles,
|
||||
].join(" ")
|
||||
try {
|
||||
@@ -595,7 +592,7 @@ export interface HostServiceHandlerConfig {
|
||||
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {${serviceConfigs.join(",")}
|
||||
};`
|
||||
|
||||
const configPath = path.join(ROOT_DIR, "src", "hosts", "vscode", "host-grpc-service-config.ts")
|
||||
const configPath = path.join(ROOT_DIR, "hosts", "vscode", "host-grpc-service-config.ts")
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true })
|
||||
await fs.writeFile(configPath, content)
|
||||
console.log(chalk.green(`Generated host service configuration at ${configPath}`))
|
||||
@@ -640,41 +637,12 @@ export {
|
||||
${serviceExports.join(",\n\t")}
|
||||
}`
|
||||
|
||||
const configPath = path.join(ROOT_DIR, "src", "hosts", "vscode", "client", "host-grpc-client.ts")
|
||||
const configPath = path.join(ROOT_DIR, "src", "standalone", "services", "host-grpc-client.ts")
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true })
|
||||
await fs.writeFile(configPath, content)
|
||||
console.log(chalk.green(`Generated host gRPC client at ${configPath}`))
|
||||
}
|
||||
|
||||
async function cleanup() {
|
||||
// Clean up existing generated files
|
||||
console.log(chalk.cyan("Cleaning up existing generated TypeScript files..."))
|
||||
const existingFiles = await globby("**/*.ts", { cwd: TS_OUT_DIR })
|
||||
for (const file of existingFiles) {
|
||||
await fs.unlink(path.join(TS_OUT_DIR, file))
|
||||
}
|
||||
|
||||
// Clean up generated files that were moved.
|
||||
await fs.rm(path.join(ROOT_DIR, "src", "standalone", "services", "host-grpc-client.ts"), { force: true })
|
||||
await rmdir(path.join(ROOT_DIR, "src", "standalone", "services"))
|
||||
await fs.rm(path.join(ROOT_DIR, "hosts", "vscode"), { force: true, recursive: true })
|
||||
await rmdir(path.join(ROOT_DIR, "hosts"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an empty dir, do nothing if the directory doesn't exist or is not empty.
|
||||
*/
|
||||
async function rmdir(path) {
|
||||
try {
|
||||
await fs.rmdir(path)
|
||||
} catch (error) {
|
||||
if (error.code !== "ENOTEMPTY" && error.code !== "ENOENT") {
|
||||
// Only re-throw if it's not "not empty" or "doesn't exist"
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Apple Silicon compatibility
|
||||
function checkAppleSiliconCompatibility() {
|
||||
// Only run check on macOS
|
||||
|
||||
@@ -25,12 +25,12 @@ message Uri {
|
||||
string path = 3;
|
||||
string query = 4;
|
||||
string fragment = 5;
|
||||
string fs_path = 6;
|
||||
string fsPath = 6;
|
||||
}
|
||||
|
||||
// Request for joining path segments to a URI
|
||||
message JoinPathRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
Uri base = 2;
|
||||
repeated string path_segments = 3;
|
||||
repeated string pathSegments = 3;
|
||||
}
|
||||
|
||||
+9
-154
@@ -22,21 +22,19 @@ service ModelsService {
|
||||
rpc refreshRequestyModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Subscribe to OpenRouter models updates
|
||||
rpc subscribeToOpenRouterModels(EmptyRequest) returns (stream OpenRouterCompatibleModelInfo);
|
||||
// Updates API configuration
|
||||
rpc updateApiConfigurationProto(UpdateApiConfigurationRequest) returns (Empty);
|
||||
}
|
||||
|
||||
// List of VS Code LM models
|
||||
message VsCodeLmModelsArray {
|
||||
repeated LanguageModelChatSelector models = 1;
|
||||
repeated VsCodeLmModel models = 1;
|
||||
}
|
||||
|
||||
// Structure representing a language model chat selector
|
||||
message LanguageModelChatSelector {
|
||||
optional string vendor = 1;
|
||||
optional string family = 2;
|
||||
optional string version = 3;
|
||||
optional string id = 4;
|
||||
// Structure representing a VS Code LM model
|
||||
message VsCodeLmModel {
|
||||
string vendor = 1;
|
||||
string family = 2;
|
||||
string version = 3;
|
||||
string id = 4;
|
||||
}
|
||||
|
||||
// Price tier for tiered pricing models
|
||||
@@ -85,149 +83,6 @@ message OpenRouterCompatibleModelInfo {
|
||||
// Request for fetching OpenAI models
|
||||
message OpenAiModelsRequest {
|
||||
Metadata metadata = 1;
|
||||
string base_url = 2;
|
||||
string api_key = 3;
|
||||
}
|
||||
|
||||
// Request for updating API configuration
|
||||
message UpdateApiConfigurationRequest {
|
||||
Metadata metadata = 1;
|
||||
ModelsApiConfiguration api_configuration = 2;
|
||||
}
|
||||
|
||||
// API Provider enumeration
|
||||
enum ApiProvider {
|
||||
ANTHROPIC = 0;
|
||||
OPENROUTER = 1;
|
||||
BEDROCK = 2;
|
||||
VERTEX = 3;
|
||||
OPENAI = 4;
|
||||
OLLAMA = 5;
|
||||
LMSTUDIO = 6;
|
||||
GEMINI = 7;
|
||||
OPENAI_NATIVE = 8;
|
||||
REQUESTY = 9;
|
||||
TOGETHER = 10;
|
||||
DEEPSEEK = 11;
|
||||
QWEN = 12;
|
||||
DOUBAO = 13;
|
||||
MISTRAL = 14;
|
||||
VSCODE_LM = 15;
|
||||
CLINE = 16;
|
||||
LITELLM = 17;
|
||||
NEBIUS = 18;
|
||||
FIREWORKS = 19;
|
||||
ASKSAGE = 20;
|
||||
XAI = 21;
|
||||
SAMBANOVA = 22;
|
||||
CEREBRAS = 23;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
message OpenAiCompatibleModelInfo {
|
||||
optional int32 max_tokens = 1;
|
||||
optional int32 context_window = 2;
|
||||
optional bool supports_images = 3;
|
||||
bool supports_prompt_cache = 4;
|
||||
optional double input_price = 5;
|
||||
optional double output_price = 6;
|
||||
optional ThinkingConfig thinking_config = 7;
|
||||
optional bool supports_global_endpoint = 8;
|
||||
optional double cache_writes_price = 9;
|
||||
optional double cache_reads_price = 10;
|
||||
optional string description = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional double temperature = 13;
|
||||
optional bool is_r1_format_required = 14;
|
||||
}
|
||||
|
||||
// Model info for LiteLLM models
|
||||
message LiteLLMModelInfo {
|
||||
optional int32 max_tokens = 1;
|
||||
optional int32 context_window = 2;
|
||||
optional bool supports_images = 3;
|
||||
bool supports_prompt_cache = 4;
|
||||
optional double input_price = 5;
|
||||
optional double output_price = 6;
|
||||
optional ThinkingConfig thinking_config = 7;
|
||||
optional bool supports_global_endpoint = 8;
|
||||
optional double cache_writes_price = 9;
|
||||
optional double cache_reads_price = 10;
|
||||
optional string description = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional double temperature = 13;
|
||||
}
|
||||
|
||||
// Main ApiConfiguration message
|
||||
message ModelsApiConfiguration {
|
||||
// From ApiHandlerOptions (excluding onRetryAttempt function)
|
||||
optional string api_model_id = 1;
|
||||
optional string api_key = 2;
|
||||
optional string cline_api_key = 3;
|
||||
optional string task_id = 4;
|
||||
optional string lite_llm_base_url = 5;
|
||||
optional string lite_llm_model_id = 6;
|
||||
optional string lite_llm_api_key = 7;
|
||||
optional bool lite_llm_use_prompt_cache = 8;
|
||||
map<string, string> open_ai_headers = 9;
|
||||
optional LiteLLMModelInfo lite_llm_model_info = 10;
|
||||
optional string anthropic_base_url = 11;
|
||||
optional string open_router_api_key = 12;
|
||||
optional string open_router_model_id = 13;
|
||||
optional OpenRouterModelInfo open_router_model_info = 14;
|
||||
optional string open_router_provider_sorting = 15;
|
||||
optional string aws_access_key = 16;
|
||||
optional string aws_secret_key = 17;
|
||||
optional string aws_session_token = 18;
|
||||
optional string aws_region = 19;
|
||||
optional bool aws_use_cross_region_inference = 20;
|
||||
optional bool aws_bedrock_use_prompt_cache = 21;
|
||||
optional bool aws_use_profile = 22;
|
||||
optional string aws_profile = 23;
|
||||
optional string aws_bedrock_endpoint = 24;
|
||||
optional bool aws_bedrock_custom_selected = 25;
|
||||
optional string aws_bedrock_custom_model_base_id = 26;
|
||||
optional string vertex_project_id = 27;
|
||||
optional string vertex_region = 28;
|
||||
optional string open_ai_base_url = 29;
|
||||
optional string open_ai_api_key = 30;
|
||||
optional string open_ai_model_id = 31;
|
||||
optional OpenAiCompatibleModelInfo open_ai_model_info = 32;
|
||||
optional string ollama_model_id = 33;
|
||||
optional string ollama_base_url = 34;
|
||||
optional string ollama_api_options_ctx_num = 35;
|
||||
optional string lm_studio_model_id = 36;
|
||||
optional string lm_studio_base_url = 37;
|
||||
optional string gemini_api_key = 38;
|
||||
optional string gemini_base_url = 39;
|
||||
optional string open_ai_native_api_key = 40;
|
||||
optional string deep_seek_api_key = 41;
|
||||
optional string requesty_api_key = 42;
|
||||
optional string requesty_model_id = 43;
|
||||
optional OpenRouterModelInfo requesty_model_info = 44;
|
||||
optional string together_api_key = 45;
|
||||
optional string together_model_id = 46;
|
||||
optional string fireworks_api_key = 47;
|
||||
optional string fireworks_model_id = 48;
|
||||
optional int32 fireworks_model_max_completion_tokens = 49;
|
||||
optional int32 fireworks_model_max_tokens = 50;
|
||||
optional string qwen_api_key = 51;
|
||||
optional string doubao_api_key = 52;
|
||||
optional string mistral_api_key = 53;
|
||||
optional string azure_api_version = 54;
|
||||
optional LanguageModelChatSelector vs_code_lm_model_selector = 55;
|
||||
optional string qwen_api_line = 56;
|
||||
optional string nebius_api_key = 57;
|
||||
optional string asksage_api_url = 58;
|
||||
optional string asksage_api_key = 59;
|
||||
optional string xai_api_key = 60;
|
||||
optional int32 thinking_budget_tokens = 61;
|
||||
optional string reasoning_effort = 62;
|
||||
optional string sambanova_api_key = 63;
|
||||
optional string cerebras_api_key = 64;
|
||||
optional int32 request_timeout_ms = 65;
|
||||
|
||||
// From ApiConfiguration (additional fields)
|
||||
optional ApiProvider api_provider = 66;
|
||||
repeated string favorited_model_ids = 67;
|
||||
string baseUrl = 2;
|
||||
string apiKey = 3;
|
||||
}
|
||||
|
||||
+10
-8
@@ -11,6 +11,7 @@ service StateService {
|
||||
rpc toggleFavoriteModel(StringRequest) returns (Empty);
|
||||
rpc resetState(EmptyRequest) returns (Empty);
|
||||
rpc togglePlanActMode(TogglePlanActModeRequest) returns (Empty);
|
||||
rpc updateTerminalConnectionTimeout(Int64Request) returns (Int64);
|
||||
rpc updateAutoApprovalSettings(AutoApprovalSettingsRequest) returns (Empty);
|
||||
rpc updateSettings(UpdateSettingsRequest) returns (Empty);
|
||||
}
|
||||
@@ -69,14 +70,15 @@ message AutoApprovalSettingsRequest {
|
||||
message UpdateSettingsRequest {
|
||||
Metadata metadata = 1;
|
||||
optional ApiConfiguration api_configuration = 2;
|
||||
optional string telemetry_setting = 3;
|
||||
optional bool plan_act_separate_models_setting = 4;
|
||||
optional bool enable_checkpoints_setting = 5;
|
||||
optional bool mcp_marketplace_enabled = 6;
|
||||
optional ChatSettings chat_settings = 7;
|
||||
optional int64 shell_integration_timeout = 8;
|
||||
optional bool terminal_reuse_enabled = 9;
|
||||
optional bool mcp_responses_collapsed = 10;
|
||||
optional string custom_instructions_setting = 3;
|
||||
optional string telemetry_setting = 4;
|
||||
optional bool plan_act_separate_models_setting = 5;
|
||||
optional bool enable_checkpoints_setting = 6;
|
||||
optional bool mcp_marketplace_enabled = 7;
|
||||
optional ChatSettings chat_settings = 8;
|
||||
optional int64 shell_integration_timeout = 9;
|
||||
optional bool terminal_reuse_enabled = 10;
|
||||
optional bool mcp_responses_collapsed = 11;
|
||||
}
|
||||
|
||||
// Complete API Configuration message
|
||||
|
||||
+8
-12
@@ -15,7 +15,7 @@ enum WebviewProviderType {
|
||||
// Define a new message type for webview provider info
|
||||
message WebviewProviderTypeRequest {
|
||||
Metadata metadata = 1;
|
||||
WebviewProviderType provider_type = 2;
|
||||
WebviewProviderType providerType = 2;
|
||||
}
|
||||
|
||||
// Enum for ClineMessage type
|
||||
@@ -65,14 +65,13 @@ enum ClineSay {
|
||||
BROWSER_ACTION_RESULT = 16;
|
||||
MCP_SERVER_REQUEST_STARTED = 17;
|
||||
MCP_SERVER_RESPONSE = 18;
|
||||
MCP_NOTIFICATION = 19;
|
||||
USE_MCP_SERVER_SAY = 20;
|
||||
DIFF_ERROR = 21;
|
||||
DELETED_API_REQS = 22;
|
||||
CLINEIGNORE_ERROR = 23;
|
||||
CHECKPOINT_CREATED = 24;
|
||||
LOAD_MCP_DOCUMENTATION = 25;
|
||||
INFO = 26;
|
||||
USE_MCP_SERVER_SAY = 19;
|
||||
DIFF_ERROR = 20;
|
||||
DELETED_API_REQS = 21;
|
||||
CLINEIGNORE_ERROR = 22;
|
||||
CHECKPOINT_CREATED = 23;
|
||||
LOAD_MCP_DOCUMENTATION = 24;
|
||||
INFO = 25;
|
||||
}
|
||||
|
||||
// Enum for ClineSayTool tool types
|
||||
@@ -256,7 +255,4 @@ service UiService {
|
||||
|
||||
// Subscribe to relinquish control events
|
||||
rpc subscribeToRelinquishControl(EmptyRequest) returns (stream Empty);
|
||||
|
||||
// Subscribe to focus chat input events with client ID
|
||||
rpc subscribeToFocusChatInput(StringRequest) returns (stream Empty);
|
||||
}
|
||||
|
||||
@@ -35,59 +35,6 @@ interface ExtendedMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
// Define types for stream response content blocks
|
||||
interface ContentBlockStart {
|
||||
contentBlockIndex?: number
|
||||
start?: {
|
||||
type?: string
|
||||
thinking?: string
|
||||
}
|
||||
contentBlock?: {
|
||||
type?: string
|
||||
thinking?: string
|
||||
}
|
||||
type?: string
|
||||
thinking?: string
|
||||
}
|
||||
|
||||
// Define types for stream response deltas
|
||||
interface ContentBlockDelta {
|
||||
contentBlockIndex?: number
|
||||
delta?: {
|
||||
type?: string
|
||||
thinking?: string
|
||||
text?: string
|
||||
reasoningContent?: {
|
||||
text?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define types for supported content types
|
||||
type SupportedContentType = "text" | "image" | "thinking"
|
||||
|
||||
interface ContentItem {
|
||||
type: SupportedContentType
|
||||
text?: string
|
||||
source?: {
|
||||
data: string | Buffer | Uint8Array
|
||||
media_type?: string
|
||||
}
|
||||
}
|
||||
|
||||
// Define cache point type for AWS Bedrock
|
||||
interface CachePointContentBlock {
|
||||
cachePoint: {
|
||||
type: "default"
|
||||
}
|
||||
}
|
||||
|
||||
// Define provider options type based on AWS SDK patterns
|
||||
interface ProviderChainOptions {
|
||||
ignoreCache?: boolean
|
||||
profile?: string
|
||||
}
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
|
||||
export class AwsBedrockHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
@@ -160,7 +107,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
sessionToken?: string
|
||||
}> {
|
||||
// Configure provider options
|
||||
const providerOptions: ProviderChainOptions = {}
|
||||
const providerOptions: any = {}
|
||||
if (this.options.awsUseProfile) {
|
||||
// For profile-based auth, always use ignoreCache to detect credential file changes
|
||||
// This solves the AWS Identity Manager issue where credential files change externally
|
||||
@@ -514,7 +461,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
|
||||
// Handle content block start - check if Bedrock uses Anthropic SDK format
|
||||
if (chunk.contentBlockStart) {
|
||||
const blockStart = chunk.contentBlockStart as ContentBlockStart
|
||||
const blockStart = chunk.contentBlockStart as any
|
||||
const blockIndex = chunk.contentBlockStart.contentBlockIndex
|
||||
|
||||
// Check for thinking block in various possible formats
|
||||
@@ -550,7 +497,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
|
||||
// Check if this is a thinking block
|
||||
const blockType = blockTypes.get(blockIndex)
|
||||
const delta = chunk.contentBlockDelta.delta as ContentBlockDelta["delta"]
|
||||
const delta = chunk.contentBlockDelta.delta as any
|
||||
|
||||
// Handle thinking delta (Anthropic SDK format)
|
||||
if (delta?.type === "thinking_delta" || delta?.thinking) {
|
||||
@@ -780,7 +727,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Log unsupported content types for debugging
|
||||
console.warn(`Unsupported content type: ${(item as ContentItem).type}`)
|
||||
console.warn(`Unsupported content type: ${(item as any).type}`)
|
||||
return null
|
||||
})
|
||||
.filter((item): item is ContentBlock => item !== null)
|
||||
@@ -824,7 +771,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
imageData = new Uint8Array(Buffer.from(base64Data, "base64"))
|
||||
} else if (item.source.data && typeof item.source.data === "object") {
|
||||
// Try to convert to Uint8Array
|
||||
imageData = new Uint8Array(Buffer.from(item.source.data as Buffer | Uint8Array))
|
||||
imageData = new Uint8Array(Buffer.from(item.source.data as any))
|
||||
} else {
|
||||
throw new Error("Unsupported image data format")
|
||||
}
|
||||
@@ -870,7 +817,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
cachePoint: {
|
||||
type: "default",
|
||||
},
|
||||
} as CachePointContentBlock, // Properly typed cache point for AWS SDK
|
||||
} as any, // Type assertion needed for AWS SDK compatibility
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import { GoogleGenAI, type GenerateContentConfig, type GenerateContentResponseUsageMetadata } from "@google/genai"
|
||||
import { withRetry } from "../retry"
|
||||
import { Part } from "@google/genai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
@@ -96,9 +97,10 @@ export class GeminiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Add thinking config if the model supports it
|
||||
if (info.thinkingConfig?.outputPrice !== undefined && maxBudget > 0) {
|
||||
if (thinkingBudget > 0) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: thinkingBudget,
|
||||
includeThoughts: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +113,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
let promptTokens = 0
|
||||
let outputTokens = 0
|
||||
let cacheReadTokens = 0
|
||||
let thoughtsTokenCount = 0 // Initialize thought token counts
|
||||
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
|
||||
|
||||
try {
|
||||
@@ -130,6 +133,31 @@ export class GeminiHandler implements ApiHandler {
|
||||
isFirstSdkChunk = false
|
||||
}
|
||||
|
||||
// Handle thinking content from Gemini's response
|
||||
const candidateForThoughts = chunk?.candidates?.[0]
|
||||
const partsForThoughts = candidateForThoughts?.content?.parts
|
||||
let thoughts = "" // Initialize as empty string
|
||||
|
||||
if (partsForThoughts) {
|
||||
// This ensures partsForThoughts is a Part[] array
|
||||
for (const part of partsForThoughts) {
|
||||
const { thought, text } = part as Part
|
||||
if (thought && text) {
|
||||
// Ensure part.text exists
|
||||
// Handle the thought part
|
||||
thoughts += text + "\n" // Append thought and a newline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thoughts.trim() !== "") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thoughts.trim(),
|
||||
}
|
||||
thoughts = "" // Reset thoughts after yielding
|
||||
}
|
||||
|
||||
if (chunk.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -141,6 +169,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
lastUsageMetadata = chunk.usageMetadata
|
||||
promptTokens = lastUsageMetadata.promptTokenCount ?? promptTokens
|
||||
outputTokens = lastUsageMetadata.candidatesTokenCount ?? outputTokens
|
||||
thoughtsTokenCount = lastUsageMetadata.thoughtsTokenCount ?? thoughtsTokenCount
|
||||
cacheReadTokens = lastUsageMetadata.cachedContentTokenCount ?? cacheReadTokens
|
||||
}
|
||||
}
|
||||
@@ -151,12 +180,14 @@ export class GeminiHandler implements ApiHandler {
|
||||
info,
|
||||
inputTokens: promptTokens,
|
||||
outputTokens,
|
||||
thoughtsTokenCount,
|
||||
cacheReadTokens,
|
||||
})
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: promptTokens,
|
||||
outputTokens,
|
||||
thoughtsTokenCount,
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens: 0,
|
||||
totalCost,
|
||||
@@ -239,11 +270,13 @@ export class GeminiHandler implements ApiHandler {
|
||||
info,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
thoughtsTokenCount = 0,
|
||||
cacheReadTokens = 0,
|
||||
}: {
|
||||
info: ModelInfo
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
thoughtsTokenCount: number
|
||||
cacheReadTokens?: number
|
||||
}) {
|
||||
// Exit early if any required pricing information is missing
|
||||
@@ -275,18 +308,18 @@ export class GeminiHandler implements ApiHandler {
|
||||
const inputTokensCost = inputPrice * (uncachedInputTokens / 1_000_000)
|
||||
|
||||
// 2. Output token costs
|
||||
const outputTokensCost = outputPrice * (outputTokens / 1_000_000)
|
||||
const responseTokensCost = outputPrice * ((outputTokens + thoughtsTokenCount) / 1_000_000)
|
||||
|
||||
// 3. Cache read costs (immediate)
|
||||
const cacheReadCost = (cacheReadTokens ?? 0) > 0 ? cacheReadsPrice * ((cacheReadTokens ?? 0) / 1_000_000) : 0
|
||||
|
||||
// Calculate total immediate cost (excluding cache write/storage costs)
|
||||
const totalCost = inputTokensCost + outputTokensCost + cacheReadCost
|
||||
const totalCost = inputTokensCost + responseTokensCost + cacheReadCost
|
||||
|
||||
// Create the trace object for debugging
|
||||
const trace: Record<string, { price: number; tokens: number; cost: number }> = {
|
||||
input: { price: inputPrice, tokens: uncachedInputTokens, cost: inputTokensCost },
|
||||
output: { price: outputPrice, tokens: outputTokens, cost: outputTokensCost },
|
||||
output: { price: outputPrice, tokens: outputTokens, cost: responseTokensCost },
|
||||
}
|
||||
|
||||
// Only include cache read costs in the trace (cache write costs are tracked separately)
|
||||
|
||||
@@ -17,5 +17,6 @@ export interface ApiStreamUsageChunk {
|
||||
outputTokens: number
|
||||
cacheWriteTokens?: number
|
||||
cacheReadTokens?: number
|
||||
thoughtsTokenCount?: number // openrouter
|
||||
totalCost?: number // openrouter
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RelativePathsRequest, RelativePaths } from "@shared/proto/file"
|
||||
import { FileMethodHandler } from "./index"
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { UriServiceClient } from "@hosts/host-bridge-client"
|
||||
import { UriServiceClient } from "../../../standalone/services/host-grpc-client"
|
||||
import { Metadata, StringRequest } from "@shared/proto/common"
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,7 +114,7 @@ export class Controller {
|
||||
try {
|
||||
await storeSecret(this.context, "clineApiKey", undefined)
|
||||
await updateGlobalState(this.context, "userInfo", undefined)
|
||||
await updateWorkspaceState(this.context, "apiProvider", "openrouter")
|
||||
await updateGlobalState(this.context, "apiProvider", "openrouter")
|
||||
await this.postStateToWebview()
|
||||
vscode.window.showInformationMessage("Successfully logged out of Cline")
|
||||
} catch (error) {
|
||||
@@ -130,6 +130,7 @@ export class Controller {
|
||||
await this.clearTask() // ensures that an existing task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
|
||||
const {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
@@ -171,6 +172,7 @@ export class Controller {
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled ?? true,
|
||||
enableCheckpointsSetting ?? true,
|
||||
customInstructions,
|
||||
task,
|
||||
images,
|
||||
files,
|
||||
@@ -202,7 +204,15 @@ export class Controller {
|
||||
await this.setUserInfo(message.user || undefined)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
|
||||
case "apiConfiguration":
|
||||
if (message.apiConfiguration) {
|
||||
await updateApiConfiguration(this.context, message.apiConfiguration)
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler(message.apiConfiguration)
|
||||
}
|
||||
}
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "fetchUserCreditsData": {
|
||||
await this.fetchUserCreditsData()
|
||||
break
|
||||
@@ -287,9 +297,9 @@ export class Controller {
|
||||
|
||||
if (shouldSwitchModel) {
|
||||
// Save the last model used in this mode
|
||||
await updateWorkspaceState(this.context, "previousModeApiProvider", apiConfiguration.apiProvider)
|
||||
await updateWorkspaceState(this.context, "previousModeThinkingBudgetTokens", apiConfiguration.thinkingBudgetTokens)
|
||||
await updateWorkspaceState(this.context, "previousModeReasoningEffort", apiConfiguration.reasoningEffort)
|
||||
await updateGlobalState(this.context, "previousModeApiProvider", apiConfiguration.apiProvider)
|
||||
await updateGlobalState(this.context, "previousModeThinkingBudgetTokens", apiConfiguration.thinkingBudgetTokens)
|
||||
await updateGlobalState(this.context, "previousModeReasoningEffort", apiConfiguration.reasoningEffort)
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
case "vertex":
|
||||
@@ -299,16 +309,16 @@ export class Controller {
|
||||
case "qwen":
|
||||
case "deepseek":
|
||||
case "xai":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateWorkspaceState(
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomSelected",
|
||||
apiConfiguration.awsBedrockCustomSelected,
|
||||
)
|
||||
await updateWorkspaceState(
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomModelBaseId",
|
||||
apiConfiguration.awsBedrockCustomModelBaseId,
|
||||
@@ -316,34 +326,34 @@ export class Controller {
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
await updateWorkspaceState(this.context, "previousModeModelInfo", apiConfiguration.openRouterModelInfo)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.openRouterModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
// Important we don't set modelId to this, as it's an object not string (webview expects model id to be a string)
|
||||
await updateWorkspaceState(
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeVsCodeLmModelSelector",
|
||||
apiConfiguration.vsCodeLmModelSelector,
|
||||
)
|
||||
break
|
||||
case "openai":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.openAiModelId)
|
||||
await updateWorkspaceState(this.context, "previousModeModelInfo", apiConfiguration.openAiModelInfo)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openAiModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.openAiModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.ollamaModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.ollamaModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.lmStudioModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.lmStudioModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
|
||||
await updateWorkspaceState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
|
||||
break
|
||||
case "requesty":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.requestyModelId)
|
||||
await updateWorkspaceState(this.context, "previousModeModelInfo", apiConfiguration.requestyModelInfo)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.requestyModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.requestyModelInfo)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -355,9 +365,9 @@ export class Controller {
|
||||
newReasoningEffort ||
|
||||
newVsCodeLmModelSelector
|
||||
) {
|
||||
await updateWorkspaceState(this.context, "apiProvider", newApiProvider)
|
||||
await updateWorkspaceState(this.context, "thinkingBudgetTokens", newThinkingBudgetTokens)
|
||||
await updateWorkspaceState(this.context, "reasoningEffort", newReasoningEffort)
|
||||
await updateGlobalState(this.context, "apiProvider", newApiProvider)
|
||||
await updateGlobalState(this.context, "thinkingBudgetTokens", newThinkingBudgetTokens)
|
||||
await updateGlobalState(this.context, "reasoningEffort", newReasoningEffort)
|
||||
switch (newApiProvider) {
|
||||
case "anthropic":
|
||||
case "vertex":
|
||||
@@ -367,38 +377,38 @@ export class Controller {
|
||||
case "qwen":
|
||||
case "deepseek":
|
||||
case "xai":
|
||||
await updateWorkspaceState(this.context, "apiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateWorkspaceState(this.context, "apiModelId", newModelId)
|
||||
await updateWorkspaceState(this.context, "awsBedrockCustomSelected", newAwsBedrockCustomSelected)
|
||||
await updateWorkspaceState(this.context, "awsBedrockCustomModelBaseId", newAwsBedrockCustomModelBaseId)
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomSelected", newAwsBedrockCustomSelected)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomModelBaseId", newAwsBedrockCustomModelBaseId)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateWorkspaceState(this.context, "openRouterModelId", newModelId)
|
||||
await updateWorkspaceState(this.context, "openRouterModelInfo", newModelInfo)
|
||||
await updateGlobalState(this.context, "openRouterModelId", newModelId)
|
||||
await updateGlobalState(this.context, "openRouterModelInfo", newModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
await updateWorkspaceState(this.context, "vsCodeLmModelSelector", newVsCodeLmModelSelector)
|
||||
await updateGlobalState(this.context, "vsCodeLmModelSelector", newVsCodeLmModelSelector)
|
||||
break
|
||||
case "openai":
|
||||
await updateWorkspaceState(this.context, "openAiModelId", newModelId)
|
||||
await updateWorkspaceState(this.context, "openAiModelInfo", newModelInfo)
|
||||
await updateGlobalState(this.context, "openAiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "openAiModelInfo", newModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await updateWorkspaceState(this.context, "ollamaModelId", newModelId)
|
||||
await updateGlobalState(this.context, "ollamaModelId", newModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await updateWorkspaceState(this.context, "lmStudioModelId", newModelId)
|
||||
await updateGlobalState(this.context, "lmStudioModelId", newModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await updateWorkspaceState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
|
||||
await updateWorkspaceState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
|
||||
break
|
||||
case "requesty":
|
||||
await updateWorkspaceState(this.context, "requestyModelId", newModelId)
|
||||
await updateWorkspaceState(this.context, "requestyModelInfo", newModelInfo)
|
||||
await updateGlobalState(this.context, "requestyModelId", newModelId)
|
||||
await updateGlobalState(this.context, "requestyModelInfo", newModelInfo)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -409,7 +419,7 @@ export class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
await updateWorkspaceState(this.context, "chatSettings", chatSettings)
|
||||
await updateGlobalState(this.context, "chatSettings", chatSettings)
|
||||
await this.postStateToWebview()
|
||||
|
||||
if (this.task) {
|
||||
@@ -458,6 +468,14 @@ export class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
async updateCustomInstructions(instructions?: string) {
|
||||
// User may be clearing the field
|
||||
await updateGlobalState(this.context, "customInstructions", instructions || undefined)
|
||||
if (this.task) {
|
||||
this.task.customInstructions = instructions || undefined
|
||||
}
|
||||
}
|
||||
|
||||
// Account
|
||||
|
||||
async fetchUserCreditsData() {
|
||||
@@ -492,7 +510,7 @@ export class Controller {
|
||||
await sendAuthCallbackEvent(customToken)
|
||||
|
||||
const clineProvider: ApiProvider = "cline"
|
||||
await updateWorkspaceState(this.context, "apiProvider", clineProvider)
|
||||
await updateGlobalState(this.context, "apiProvider", clineProvider)
|
||||
|
||||
// Update API configuration with the new provider and API key
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
@@ -651,7 +669,7 @@ export class Controller {
|
||||
}
|
||||
|
||||
const openrouter: ApiProvider = "openrouter"
|
||||
await updateWorkspaceState(this.context, "apiProvider", openrouter)
|
||||
await updateGlobalState(this.context, "apiProvider", openrouter)
|
||||
await storeSecret(this.context, "openRouterApiKey", apiKey)
|
||||
await this.postStateToWebview()
|
||||
if (this.task) {
|
||||
@@ -939,6 +957,7 @@ export class Controller {
|
||||
const {
|
||||
apiConfiguration,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
taskHistory,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
@@ -970,6 +989,7 @@ export class Controller {
|
||||
return {
|
||||
version: this.context.extension?.packageJSON?.version ?? "",
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
uriScheme: vscode.env.uriScheme,
|
||||
currentTaskItem: this.task?.taskId ? (taskHistory || []).find((item) => item.id === this.task?.taskId) : undefined,
|
||||
checkpointTrackerErrorMessage: this.task?.checkpointTrackerErrorMessage,
|
||||
|
||||
@@ -99,11 +99,6 @@ export async function refreshOpenRouterModels(
|
||||
modelInfo.cacheWritesPrice = 0.14
|
||||
modelInfo.cacheReadsPrice = 0.014
|
||||
break
|
||||
case "x-ai/grok-3-beta":
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = 0
|
||||
modelInfo.cacheReadsPrice = 0
|
||||
break
|
||||
default:
|
||||
if (rawModel.id.startsWith("openai/")) {
|
||||
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { Controller } from "../index"
|
||||
import { Empty } from "@shared/proto/common"
|
||||
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
|
||||
import { updateApiConfiguration } from "../../storage/state"
|
||||
import { buildApiHandler } from "@api/index"
|
||||
import { convertProtoToApiConfiguration } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
|
||||
/**
|
||||
* Updates API configuration
|
||||
* @param controller The controller instance
|
||||
* @param request The update API configuration request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function updateApiConfigurationProto(
|
||||
controller: Controller,
|
||||
request: UpdateApiConfigurationRequest,
|
||||
): Promise<Empty> {
|
||||
try {
|
||||
if (!request.apiConfiguration) {
|
||||
console.log("[APICONFIG: updateApiConfigurationProto] API configuration is required")
|
||||
throw new Error("API configuration is required")
|
||||
}
|
||||
|
||||
// Convert proto ApiConfiguration to application ApiConfiguration
|
||||
const appApiConfiguration = convertProtoToApiConfiguration(request.apiConfiguration)
|
||||
|
||||
// Update the API configuration in storage
|
||||
await updateApiConfiguration(controller.context, appApiConfiguration)
|
||||
|
||||
// Update the task's API handler if there's an active task
|
||||
if (controller.task) {
|
||||
controller.task.api = buildApiHandler(appApiConfiguration)
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
await controller.postStateToWebview()
|
||||
|
||||
return Empty.create()
|
||||
} catch (error) {
|
||||
console.error(`Failed to update API configuration: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,11 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
}
|
||||
}
|
||||
|
||||
// Update custom instructions
|
||||
if (request.customInstructionsSetting !== undefined) {
|
||||
await controller.updateCustomInstructions(request.customInstructionsSetting)
|
||||
}
|
||||
|
||||
// Update telemetry setting
|
||||
if (request.telemetrySetting) {
|
||||
await controller.updateTelemetrySetting(request.telemetrySetting as TelemetrySetting)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Controller } from ".."
|
||||
import { Int64, Int64Request } from "../../../shared/proto/common"
|
||||
import { updateGlobalState } from "../../storage/state"
|
||||
|
||||
/**
|
||||
* Updates the terminal connection timeout setting
|
||||
* @param controller The controller instance
|
||||
* @param request The request containing the timeout value in milliseconds
|
||||
* @returns The updated timeout value
|
||||
*/
|
||||
export async function updateTerminalConnectionTimeout(controller: Controller, request: Int64Request): Promise<Int64> {
|
||||
try {
|
||||
const timeout = request.value
|
||||
|
||||
if (typeof timeout === "number" && !isNaN(timeout) && timeout > 0) {
|
||||
// Update the global state directly
|
||||
await updateGlobalState(controller.context, "shellIntegrationTimeout", timeout)
|
||||
return Int64.create({ value: timeout })
|
||||
} else {
|
||||
console.warn(`Invalid shell integration timeout value received: ${timeout}. Expected a positive number.`)
|
||||
throw new Error("Invalid timeout value. Expected a positive number.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to update terminal connection timeout: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Controller } from "../index"
|
||||
import { EmptyRequest, Empty } from "@shared/proto/common"
|
||||
import { handleModelsServiceRequest } from "../models"
|
||||
import { getAllExtensionState, getGlobalState, updateWorkspaceState } from "../../storage/state"
|
||||
import { getAllExtensionState, getGlobalState, updateGlobalState } from "../../storage/state"
|
||||
import { sendOpenRouterModelsEvent } from "../models/subscribeToOpenRouterModels"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "../mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
|
||||
@@ -32,7 +32,7 @@ export async function initializeWebview(controller: Controller, request: EmptyRe
|
||||
// Update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const { apiConfiguration } = await getAllExtensionState(controller.context)
|
||||
if (apiConfiguration.openRouterModelId && response.models[apiConfiguration.openRouterModelId]) {
|
||||
await updateWorkspaceState(
|
||||
await updateGlobalState(
|
||||
controller.context,
|
||||
"openRouterModelInfo",
|
||||
response.models[apiConfiguration.openRouterModelId],
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { StringRequest, Empty } from "@shared/proto/common"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
// Map client IDs to their subscription handlers
|
||||
const focusChatInputSubscriptions = new Map<string, StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to focus chat input events
|
||||
* @param controller The controller instance
|
||||
* @param request The request containing the client ID
|
||||
* @param responseStream The streaming response handler
|
||||
* @param requestId The ID of the request
|
||||
*/
|
||||
export async function subscribeToFocusChatInput(
|
||||
controller: Controller,
|
||||
request: StringRequest,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
const clientId = request.value
|
||||
if (!clientId) {
|
||||
throw new Error("Client ID is required for focusChatInput subscription")
|
||||
}
|
||||
|
||||
// Store this subscription with its client ID
|
||||
focusChatInputSubscriptions.set(clientId, responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
focusChatInputSubscriptions.delete(clientId)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
if (requestId) {
|
||||
getRequestRegistry().registerRequest(requestId, cleanup, { type: "focus_chat_input_subscription" }, responseStream)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a focus chat input event to a specific webview by client ID
|
||||
* @param clientId The ID of the client to send the event to
|
||||
*/
|
||||
export async function sendFocusChatInputEvent(clientId: string): Promise<void> {
|
||||
const responseStream = focusChatInputSubscriptions.get(clientId)
|
||||
if (!responseStream) {
|
||||
console.warn(`No subscription found for client ID: ${clientId}`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const event = Empty.create({})
|
||||
await responseStream(
|
||||
event,
|
||||
false, // Not the last message
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Error sending focus chat input event to client ${clientId}:`, error)
|
||||
// Remove the subscription if there was an error
|
||||
focusChatInputSubscriptions.delete(clientId)
|
||||
}
|
||||
}
|
||||
@@ -663,6 +663,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
|
||||
}
|
||||
|
||||
export function addUserInstructions(
|
||||
settingsCustomInstructions?: string,
|
||||
globalClineRulesFileInstructions?: string,
|
||||
localClineRulesFileInstructions?: string,
|
||||
localCursorRulesFileInstructions?: string,
|
||||
@@ -675,6 +676,9 @@ export function addUserInstructions(
|
||||
if (preferredLanguageInstructions) {
|
||||
customInstructions += preferredLanguageInstructions + "\n\n"
|
||||
}
|
||||
if (settingsCustomInstructions) {
|
||||
customInstructions += settingsCustomInstructions + "\n\n"
|
||||
}
|
||||
if (globalClineRulesFileInstructions) {
|
||||
customInstructions += globalClineRulesFileInstructions + "\n\n"
|
||||
}
|
||||
|
||||
@@ -651,6 +651,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
|
||||
|
||||
|
||||
export function addUserInstructions(
|
||||
settingsCustomInstructions?: string,
|
||||
globalClineRulesFileInstructions?: string,
|
||||
localClineRulesFileInstructions?: string,
|
||||
localCursorRulesFileInstructions?: string,
|
||||
@@ -663,6 +664,9 @@ export function addUserInstructions(
|
||||
if (preferredLanguageInstructions) {
|
||||
customInstructions += preferredLanguageInstructions + "\n\n"
|
||||
}
|
||||
if (settingsCustomInstructions) {
|
||||
customInstructions += settingsCustomInstructions + "\n\n"
|
||||
}
|
||||
if (globalClineRulesFileInstructions) {
|
||||
customInstructions += globalClineRulesFileInstructions + "\n\n"
|
||||
}
|
||||
|
||||
@@ -24,20 +24,26 @@ export type SecretKey =
|
||||
| "cerebrasApiKey"
|
||||
|
||||
export type GlobalStateKey =
|
||||
| "apiProvider"
|
||||
| "apiModelId"
|
||||
| "awsRegion"
|
||||
| "awsUseCrossRegionInference"
|
||||
| "awsBedrockUsePromptCache"
|
||||
| "awsBedrockEndpoint"
|
||||
| "awsProfile"
|
||||
| "awsUseProfile"
|
||||
| "awsBedrockCustomSelected"
|
||||
| "awsBedrockCustomModelBaseId"
|
||||
| "vertexProjectId"
|
||||
| "vertexRegion"
|
||||
| "lastShownAnnouncementId"
|
||||
| "customInstructions"
|
||||
| "taskHistory"
|
||||
| "openAiBaseUrl"
|
||||
| "openAiModelId"
|
||||
| "openAiModelInfo"
|
||||
| "openAiHeaders"
|
||||
| "ollamaModelId"
|
||||
| "ollamaBaseUrl"
|
||||
| "ollamaApiOptionsCtxNum"
|
||||
| "lmStudioModelId"
|
||||
@@ -45,20 +51,40 @@ export type GlobalStateKey =
|
||||
| "anthropicBaseUrl"
|
||||
| "geminiBaseUrl"
|
||||
| "azureApiVersion"
|
||||
| "openRouterModelId"
|
||||
| "openRouterModelInfo"
|
||||
| "openRouterProviderSorting"
|
||||
| "autoApprovalSettings"
|
||||
| "globalClineRulesToggles"
|
||||
| "globalWorkflowToggles"
|
||||
| "browserSettings"
|
||||
| "chatSettings"
|
||||
| "vsCodeLmModelSelector"
|
||||
| "userInfo"
|
||||
| "previousModeApiProvider"
|
||||
| "previousModeModelId"
|
||||
| "previousModeThinkingBudgetTokens"
|
||||
| "previousModeReasoningEffort"
|
||||
| "previousModeVsCodeLmModelSelector"
|
||||
| "previousModeAwsBedrockCustomSelected"
|
||||
| "previousModeAwsBedrockCustomModelBaseId"
|
||||
| "previousModeModelInfo"
|
||||
| "liteLlmBaseUrl"
|
||||
| "liteLlmModelId"
|
||||
| "liteLlmModelInfo"
|
||||
| "liteLlmUsePromptCache"
|
||||
| "fireworksModelId"
|
||||
| "fireworksModelMaxCompletionTokens"
|
||||
| "fireworksModelMaxTokens"
|
||||
| "qwenApiLine"
|
||||
| "requestyModelId"
|
||||
| "requestyModelInfo"
|
||||
| "togetherModelId"
|
||||
| "mcpMarketplaceCatalog"
|
||||
| "telemetrySetting"
|
||||
| "asksageApiUrl"
|
||||
| "thinkingBudgetTokens"
|
||||
| "reasoningEffort"
|
||||
| "planActSeparateModelsSetting"
|
||||
| "enableCheckpointsSetting"
|
||||
| "mcpMarketplaceEnabled"
|
||||
@@ -69,35 +95,4 @@ export type GlobalStateKey =
|
||||
| "terminalReuseEnabled"
|
||||
| "isNewUser"
|
||||
|
||||
export type LocalStateKey =
|
||||
| "localClineRulesToggles"
|
||||
| "chatSettings"
|
||||
// Current active model configuration (per workspace)
|
||||
| "apiProvider"
|
||||
| "apiModelId"
|
||||
| "thinkingBudgetTokens"
|
||||
| "reasoningEffort"
|
||||
| "vsCodeLmModelSelector"
|
||||
| "awsBedrockCustomSelected"
|
||||
| "awsBedrockCustomModelBaseId"
|
||||
| "openRouterModelId"
|
||||
| "openRouterModelInfo"
|
||||
| "openAiModelId"
|
||||
| "openAiModelInfo"
|
||||
| "ollamaModelId"
|
||||
| "lmStudioModelId"
|
||||
| "liteLlmModelId"
|
||||
| "liteLlmModelInfo"
|
||||
| "requestyModelId"
|
||||
| "requestyModelInfo"
|
||||
| "togetherModelId"
|
||||
| "fireworksModelId"
|
||||
// Previous mode saved configurations (per workspace)
|
||||
| "previousModeApiProvider"
|
||||
| "previousModeModelId"
|
||||
| "previousModeModelInfo"
|
||||
| "previousModeVsCodeLmModelSelector"
|
||||
| "previousModeThinkingBudgetTokens"
|
||||
| "previousModeReasoningEffort"
|
||||
| "previousModeAwsBedrockCustomSelected"
|
||||
| "previousModeAwsBedrockCustomModelBaseId"
|
||||
export type LocalStateKey = "localClineRulesToggles"
|
||||
|
||||
+96
-209
@@ -11,9 +11,6 @@ import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { UserInfo } from "@shared/UserInfo"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { ensureRulesDirectoryExists } from "./disk"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
/*
|
||||
Storage
|
||||
https://dev.to/kompotkot/how-to-use-secretstorage-in-your-vscode-extensions-2hco
|
||||
@@ -54,57 +51,6 @@ export async function getWorkspaceState(context: vscode.ExtensionContext, key: s
|
||||
return await context.workspaceState.get(key)
|
||||
}
|
||||
|
||||
export async function migratePlanActGlobalToWorkspaceStorage(context: vscode.ExtensionContext) {
|
||||
// Keys that were migrated from global storage to workspace storage
|
||||
const keysToMigrate = [
|
||||
// Core settings
|
||||
"apiProvider",
|
||||
"apiModelId",
|
||||
"thinkingBudgetTokens",
|
||||
"reasoningEffort",
|
||||
"chatSettings",
|
||||
"vsCodeLmModelSelector",
|
||||
|
||||
// Provider-specific model keys
|
||||
"awsBedrockCustomSelected",
|
||||
"awsBedrockCustomModelBaseId",
|
||||
"openRouterModelId",
|
||||
"openRouterModelInfo",
|
||||
"openAiModelId",
|
||||
"openAiModelInfo",
|
||||
"ollamaModelId",
|
||||
"lmStudioModelId",
|
||||
"liteLlmModelId",
|
||||
"liteLlmModelInfo",
|
||||
"requestyModelId",
|
||||
"requestyModelInfo",
|
||||
"togetherModelId",
|
||||
"fireworksModelId",
|
||||
|
||||
// Previous mode settings
|
||||
"previousModeApiProvider",
|
||||
"previousModeModelId",
|
||||
"previousModeModelInfo",
|
||||
"previousModeVsCodeLmModelSelector",
|
||||
"previousModeThinkingBudgetTokens",
|
||||
"previousModeReasoningEffort",
|
||||
"previousModeAwsBedrockCustomSelected",
|
||||
"previousModeAwsBedrockCustomModelBaseId",
|
||||
]
|
||||
|
||||
for (const key of keysToMigrate) {
|
||||
const globalValue = await getGlobalState(context, key as GlobalStateKey)
|
||||
if (globalValue !== undefined) {
|
||||
const workspaceValue = await getWorkspaceState(context, key)
|
||||
if (workspaceValue === undefined) {
|
||||
await updateWorkspaceState(context, key, globalValue)
|
||||
}
|
||||
// Delete from global storage regardless of whether we copied it
|
||||
await updateGlobalState(context, key as GlobalStateKey, undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRaw: boolean | undefined): Promise<boolean> {
|
||||
const config = vscode.workspace.getConfiguration("cline")
|
||||
const mcpMarketplaceEnabled = config.get<boolean>("mcpMarketplace.enabled")
|
||||
@@ -128,54 +74,11 @@ async function migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw: bool
|
||||
return enableCheckpointsSettingRaw ?? true
|
||||
}
|
||||
|
||||
export async function migrateCustomInstructionsToGlobalRules(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
const customInstructions = (await context.globalState.get("customInstructions")) as string | undefined
|
||||
|
||||
if (customInstructions?.trim()) {
|
||||
console.log("Migrating custom instructions to global Cline rules...")
|
||||
|
||||
// Create global .clinerules directory if it doesn't exist
|
||||
const globalRulesDir = await ensureRulesDirectoryExists()
|
||||
|
||||
// Use a fixed filename for custom instructions
|
||||
const migrationFileName = "custom_instructions.md"
|
||||
const migrationFilePath = path.join(globalRulesDir, migrationFileName)
|
||||
|
||||
try {
|
||||
// Check if file already exists to determine if we should append
|
||||
let existingContent = ""
|
||||
try {
|
||||
existingContent = await fs.readFile(migrationFilePath, "utf8")
|
||||
} catch (readError) {
|
||||
// File doesn't exist, which is fine
|
||||
}
|
||||
|
||||
// Append or create the file with custom instructions
|
||||
const contentToWrite = existingContent
|
||||
? `${existingContent}\n\n---\n\n${customInstructions.trim()}`
|
||||
: customInstructions.trim()
|
||||
|
||||
await fs.writeFile(migrationFilePath, contentToWrite)
|
||||
console.log(`Successfully ${existingContent ? "appended to" : "created"} migration file: ${migrationFilePath}`)
|
||||
} catch (fileError) {
|
||||
console.error("Failed to write migration file:", fileError)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove customInstructions from global state only after successful file creation
|
||||
await context.globalState.update("customInstructions", undefined)
|
||||
console.log("Successfully migrated custom instructions to global Cline rules")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to migrate custom instructions to global rules:", error)
|
||||
// Continue execution - migration failure shouldn't break extension startup
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
const [
|
||||
isNewUser,
|
||||
storedApiProvider,
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineApiKey,
|
||||
@@ -188,13 +91,19 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
openAiApiKey,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
openAiHeaders,
|
||||
ollamaModelId,
|
||||
ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId,
|
||||
lmStudioBaseUrl,
|
||||
anthropicBaseUrl,
|
||||
geminiApiKey,
|
||||
@@ -202,28 +111,49 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherApiKey,
|
||||
togetherModelId,
|
||||
qwenApiKey,
|
||||
doubaoApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openRouterProviderSorting,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
taskHistory,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
vsCodeLmModelSelector,
|
||||
liteLlmBaseUrl,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
liteLlmUsePromptCache,
|
||||
fireworksApiKey,
|
||||
fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens,
|
||||
userInfo,
|
||||
previousModeApiProvider,
|
||||
previousModeModelId,
|
||||
previousModeModelInfo,
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
qwenApiLine,
|
||||
liteLlmApiKey,
|
||||
telemetrySetting,
|
||||
asksageApiKey,
|
||||
asksageApiUrl,
|
||||
xaiApiKey,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
sambanovaApiKey,
|
||||
cerebrasApiKey,
|
||||
nebiusApiKey,
|
||||
@@ -239,6 +169,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
terminalReuseEnabled,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "isNewUser") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
|
||||
getSecret(context, "apiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "openRouterApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "clineApiKey") as Promise<string | undefined>,
|
||||
@@ -251,13 +183,19 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "awsBedrockEndpoint") as Promise<string | undefined>,
|
||||
getGlobalState(context, "awsProfile") as Promise<string | undefined>,
|
||||
getGlobalState(context, "awsUseProfile") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "vertexProjectId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "vertexRegion") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openAiBaseUrl") as Promise<string | undefined>,
|
||||
getSecret(context, "openAiApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openAiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openAiModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "openAiHeaders") as Promise<Record<string, string> | undefined>,
|
||||
getGlobalState(context, "ollamaModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "ollamaBaseUrl") as Promise<string | undefined>,
|
||||
getGlobalState(context, "ollamaApiOptionsCtxNum") as Promise<string | undefined>,
|
||||
getGlobalState(context, "lmStudioModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "lmStudioBaseUrl") as Promise<string | undefined>,
|
||||
getGlobalState(context, "anthropicBaseUrl") as Promise<string | undefined>,
|
||||
getSecret(context, "geminiApiKey") as Promise<string | undefined>,
|
||||
@@ -265,28 +203,49 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getSecret(context, "openAiNativeApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "deepSeekApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "requestyApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "requestyModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "requestyModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getSecret(context, "togetherApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "togetherModelId") as Promise<string | undefined>,
|
||||
getSecret(context, "qwenApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "doubaoApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "mistralApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "azureApiVersion") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openRouterModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openRouterModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "openRouterProviderSorting") as Promise<string | undefined>,
|
||||
getGlobalState(context, "lastShownAnnouncementId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "customInstructions") as Promise<string | undefined>,
|
||||
getGlobalState(context, "taskHistory") as Promise<HistoryItem[] | undefined>,
|
||||
getGlobalState(context, "autoApprovalSettings") as Promise<AutoApprovalSettings | undefined>,
|
||||
getGlobalState(context, "browserSettings") as Promise<BrowserSettings | undefined>,
|
||||
getGlobalState(context, "chatSettings") as Promise<ChatSettings | undefined>,
|
||||
getGlobalState(context, "vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "liteLlmBaseUrl") as Promise<string | undefined>,
|
||||
getGlobalState(context, "liteLlmModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "liteLlmModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "liteLlmUsePromptCache") as Promise<boolean | undefined>,
|
||||
getSecret(context, "fireworksApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "fireworksModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "fireworksModelMaxCompletionTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "fireworksModelMaxTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "userInfo") as Promise<UserInfo | undefined>,
|
||||
getGlobalState(context, "previousModeApiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "previousModeModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "previousModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "previousModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "previousModeReasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "qwenApiLine") as Promise<string | undefined>,
|
||||
getSecret(context, "liteLlmApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "telemetrySetting") as Promise<TelemetrySetting | undefined>,
|
||||
getSecret(context, "asksageApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "asksageApiUrl") as Promise<string | undefined>,
|
||||
getSecret(context, "xaiApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "reasoningEffort") as Promise<string | undefined>,
|
||||
getSecret(context, "sambanovaApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "cerebrasApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "nebiusApiKey") as Promise<string | undefined>,
|
||||
@@ -302,68 +261,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "terminalReuseEnabled") as Promise<boolean | undefined>,
|
||||
])
|
||||
|
||||
const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
|
||||
|
||||
const [
|
||||
chatSettings,
|
||||
storedApiProvider,
|
||||
apiModelId,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
vsCodeLmModelSelector,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
ollamaModelId,
|
||||
lmStudioModelId,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherModelId,
|
||||
fireworksModelId,
|
||||
previousModeApiProvider,
|
||||
previousModeModelId,
|
||||
previousModeModelInfo,
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
] = await Promise.all([
|
||||
getWorkspaceState(context, "chatSettings") as Promise<ChatSettings | undefined>,
|
||||
getWorkspaceState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getWorkspaceState(context, "apiModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getWorkspaceState(context, "reasoningEffort") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getWorkspaceState(context, "awsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getWorkspaceState(context, "awsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getWorkspaceState(context, "openRouterModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "openRouterModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getWorkspaceState(context, "openAiModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "openAiModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getWorkspaceState(context, "ollamaModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "lmStudioModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "liteLlmModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "liteLlmModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getWorkspaceState(context, "requestyModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "requestyModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getWorkspaceState(context, "togetherModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "fireworksModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "previousModeApiProvider") as Promise<ApiProvider | undefined>,
|
||||
getWorkspaceState(context, "previousModeModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "previousModeModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getWorkspaceState(context, "previousModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getWorkspaceState(context, "previousModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getWorkspaceState(context, "previousModeReasoningEffort") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "previousModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getWorkspaceState(context, "previousModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
if (storedApiProvider) {
|
||||
apiProvider = storedApiProvider
|
||||
@@ -378,6 +275,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
}
|
||||
}
|
||||
|
||||
const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
|
||||
|
||||
const mcpMarketplaceEnabled = await migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRaw)
|
||||
const enableCheckpointsSetting = await migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw)
|
||||
const mcpResponsesCollapsed = mcpResponsesCollapsedRaw ?? false
|
||||
@@ -471,6 +370,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
},
|
||||
isNewUser: isNewUser ?? true,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
taskHistory,
|
||||
autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string
|
||||
globalClineRulesToggles: globalClineRulesToggles || {},
|
||||
@@ -563,68 +463,37 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
cerebrasApiKey,
|
||||
nebiusApiKey,
|
||||
favoritedModelIds,
|
||||
fireworksApiKey,
|
||||
fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens,
|
||||
} = apiConfiguration
|
||||
// Workspace state updates
|
||||
await updateWorkspaceState(context, "apiProvider", apiProvider)
|
||||
await updateWorkspaceState(context, "apiModelId", apiModelId)
|
||||
await updateWorkspaceState(context, "thinkingBudgetTokens", thinkingBudgetTokens)
|
||||
await updateWorkspaceState(context, "reasoningEffort", reasoningEffort)
|
||||
await updateWorkspaceState(context, "vsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
await updateWorkspaceState(context, "awsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await updateWorkspaceState(context, "awsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await updateWorkspaceState(context, "openRouterModelId", openRouterModelId)
|
||||
await updateWorkspaceState(context, "openRouterModelInfo", openRouterModelInfo)
|
||||
await updateWorkspaceState(context, "openAiModelId", openAiModelId)
|
||||
await updateWorkspaceState(context, "openAiModelInfo", openAiModelInfo)
|
||||
await updateWorkspaceState(context, "ollamaModelId", ollamaModelId)
|
||||
await updateWorkspaceState(context, "lmStudioModelId", lmStudioModelId)
|
||||
await updateWorkspaceState(context, "liteLlmModelId", liteLlmModelId)
|
||||
await updateWorkspaceState(context, "liteLlmModelInfo", liteLlmModelInfo)
|
||||
await updateWorkspaceState(context, "requestyModelId", requestyModelId)
|
||||
await updateWorkspaceState(context, "requestyModelInfo", requestyModelInfo)
|
||||
await updateWorkspaceState(context, "togetherModelId", togetherModelId)
|
||||
await updateWorkspaceState(context, "fireworksModelId", fireworksModelId)
|
||||
|
||||
// Global state updates
|
||||
await updateGlobalState(context, "apiProvider", apiProvider)
|
||||
await updateGlobalState(context, "apiModelId", apiModelId)
|
||||
await storeSecret(context, "apiKey", apiKey)
|
||||
await storeSecret(context, "openRouterApiKey", openRouterApiKey)
|
||||
await storeSecret(context, "awsAccessKey", awsAccessKey)
|
||||
await storeSecret(context, "awsSecretKey", awsSecretKey)
|
||||
await storeSecret(context, "awsSessionToken", awsSessionToken)
|
||||
await updateGlobalState(context, "awsRegion", awsRegion)
|
||||
await updateGlobalState(context, "awsUseCrossRegionInference", awsUseCrossRegionInference)
|
||||
await updateGlobalState(context, "awsBedrockUsePromptCache", awsBedrockUsePromptCache)
|
||||
await updateGlobalState(context, "awsBedrockEndpoint", awsBedrockEndpoint)
|
||||
await updateGlobalState(context, "awsProfile", awsProfile)
|
||||
await updateGlobalState(context, "awsUseProfile", awsUseProfile)
|
||||
await updateGlobalState(context, "awsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await updateGlobalState(context, "awsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await updateGlobalState(context, "vertexProjectId", vertexProjectId)
|
||||
await updateGlobalState(context, "vertexRegion", vertexRegion)
|
||||
await updateGlobalState(context, "openAiBaseUrl", openAiBaseUrl)
|
||||
await storeSecret(context, "openAiApiKey", openAiApiKey)
|
||||
await updateGlobalState(context, "openAiModelId", openAiModelId)
|
||||
await updateGlobalState(context, "openAiModelInfo", openAiModelInfo)
|
||||
await updateGlobalState(context, "openAiHeaders", openAiHeaders || {})
|
||||
await updateGlobalState(context, "ollamaModelId", ollamaModelId)
|
||||
await updateGlobalState(context, "ollamaBaseUrl", ollamaBaseUrl)
|
||||
await updateGlobalState(context, "ollamaApiOptionsCtxNum", ollamaApiOptionsCtxNum)
|
||||
await updateGlobalState(context, "lmStudioModelId", lmStudioModelId)
|
||||
await updateGlobalState(context, "lmStudioBaseUrl", lmStudioBaseUrl)
|
||||
await updateGlobalState(context, "anthropicBaseUrl", anthropicBaseUrl)
|
||||
await updateGlobalState(context, "geminiBaseUrl", geminiBaseUrl)
|
||||
await updateGlobalState(context, "azureApiVersion", azureApiVersion)
|
||||
await updateGlobalState(context, "openRouterProviderSorting", openRouterProviderSorting)
|
||||
await updateGlobalState(context, "liteLlmBaseUrl", liteLlmBaseUrl)
|
||||
await updateGlobalState(context, "liteLlmUsePromptCache", liteLlmUsePromptCache)
|
||||
await updateGlobalState(context, "qwenApiLine", qwenApiLine)
|
||||
await updateGlobalState(context, "asksageApiUrl", asksageApiUrl)
|
||||
await updateGlobalState(context, "favoritedModelIds", favoritedModelIds)
|
||||
await updateGlobalState(context, "requestTimeoutMs", apiConfiguration.requestTimeoutMs)
|
||||
await updateGlobalState(context, "fireworksModelMaxCompletionTokens", fireworksModelMaxCompletionTokens)
|
||||
await updateGlobalState(context, "fireworksModelMaxTokens", fireworksModelMaxTokens)
|
||||
|
||||
// Secret updates
|
||||
await storeSecret(context, "apiKey", apiKey)
|
||||
await storeSecret(context, "openRouterApiKey", openRouterApiKey)
|
||||
await storeSecret(context, "clineApiKey", clineApiKey)
|
||||
await storeSecret(context, "awsAccessKey", awsAccessKey)
|
||||
await storeSecret(context, "awsSecretKey", awsSecretKey)
|
||||
await storeSecret(context, "awsSessionToken", awsSessionToken)
|
||||
await storeSecret(context, "openAiApiKey", openAiApiKey)
|
||||
await storeSecret(context, "geminiApiKey", geminiApiKey)
|
||||
await updateGlobalState(context, "geminiBaseUrl", geminiBaseUrl)
|
||||
await storeSecret(context, "openAiNativeApiKey", openAiNativeApiKey)
|
||||
await storeSecret(context, "deepSeekApiKey", deepSeekApiKey)
|
||||
await storeSecret(context, "requestyApiKey", requestyApiKey)
|
||||
@@ -633,12 +502,30 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
await storeSecret(context, "doubaoApiKey", doubaoApiKey)
|
||||
await storeSecret(context, "mistralApiKey", mistralApiKey)
|
||||
await storeSecret(context, "liteLlmApiKey", liteLlmApiKey)
|
||||
await storeSecret(context, "fireworksApiKey", fireworksApiKey)
|
||||
await storeSecret(context, "asksageApiKey", asksageApiKey)
|
||||
await storeSecret(context, "xaiApiKey", xaiApiKey)
|
||||
await updateGlobalState(context, "azureApiVersion", azureApiVersion)
|
||||
await updateGlobalState(context, "openRouterModelId", openRouterModelId)
|
||||
await updateGlobalState(context, "openRouterModelInfo", openRouterModelInfo)
|
||||
await updateGlobalState(context, "openRouterProviderSorting", openRouterProviderSorting)
|
||||
await updateGlobalState(context, "vsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
await updateGlobalState(context, "liteLlmBaseUrl", liteLlmBaseUrl)
|
||||
await updateGlobalState(context, "liteLlmModelId", liteLlmModelId)
|
||||
await updateGlobalState(context, "liteLlmModelInfo", liteLlmModelInfo)
|
||||
await updateGlobalState(context, "liteLlmUsePromptCache", liteLlmUsePromptCache)
|
||||
await updateGlobalState(context, "qwenApiLine", qwenApiLine)
|
||||
await updateGlobalState(context, "requestyModelId", requestyModelId)
|
||||
await updateGlobalState(context, "requestyModelInfo", requestyModelInfo)
|
||||
await updateGlobalState(context, "togetherModelId", togetherModelId)
|
||||
await storeSecret(context, "asksageApiKey", asksageApiKey)
|
||||
await updateGlobalState(context, "asksageApiUrl", asksageApiUrl)
|
||||
await updateGlobalState(context, "thinkingBudgetTokens", thinkingBudgetTokens)
|
||||
await updateGlobalState(context, "reasoningEffort", reasoningEffort)
|
||||
await storeSecret(context, "clineApiKey", clineApiKey)
|
||||
await storeSecret(context, "sambanovaApiKey", sambanovaApiKey)
|
||||
await storeSecret(context, "cerebrasApiKey", cerebrasApiKey)
|
||||
await storeSecret(context, "nebiusApiKey", nebiusApiKey)
|
||||
await updateGlobalState(context, "favoritedModelIds", favoritedModelIds)
|
||||
await updateGlobalState(context, "requestTimeoutMs", apiConfiguration.requestTimeoutMs)
|
||||
}
|
||||
|
||||
export async function resetExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
+40
-46
@@ -105,7 +105,7 @@ import {
|
||||
getLocalCursorRules,
|
||||
} from "@core/context/instructions/user-instructions/external-rules"
|
||||
import { refreshWorkflowToggles } from "../context/instructions/user-instructions/workflows"
|
||||
import { getWorkspaceState } from "@core/storage/state"
|
||||
import { getGlobalState } from "@core/storage/state"
|
||||
import { parseSlashCommands } from "@core/slash-commands"
|
||||
import WorkspaceTracker from "@integrations/workspace/WorkspaceTracker"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
@@ -113,7 +113,6 @@ import { isInTestMode } from "../../services/test/TestMode"
|
||||
import { processFilesIntoText } from "@integrations/misc/extract-text"
|
||||
import { featureFlagsService } from "@services/posthog/feature-flags/FeatureFlagsService"
|
||||
import { StreamingJsonReplacer, ChangeLocation } from "@core/assistant-message/diff-json"
|
||||
import { isClaude4ModelFamily } from "@/utils/model-utils"
|
||||
|
||||
export const USE_EXPERIMENTAL_CLAUDE4_FEATURES = false
|
||||
|
||||
@@ -145,6 +144,7 @@ export class Task {
|
||||
browserSession: BrowserSession
|
||||
contextManager: ContextManager
|
||||
private didEditFile: boolean = false
|
||||
customInstructions?: string
|
||||
autoApprovalSettings: AutoApprovalSettings
|
||||
browserSettings: BrowserSettings
|
||||
chatSettings: ChatSettings
|
||||
@@ -204,6 +204,7 @@ export class Task {
|
||||
shellIntegrationTimeout: number,
|
||||
terminalReuseEnabled: boolean,
|
||||
enableCheckpointsSetting: boolean,
|
||||
customInstructions?: string,
|
||||
task?: string,
|
||||
images?: string[],
|
||||
files?: string[],
|
||||
@@ -226,17 +227,12 @@ export class Task {
|
||||
this.browserSession = new BrowserSession(context, browserSettings)
|
||||
this.contextManager = new ContextManager()
|
||||
this.diffViewProvider = new DiffViewProvider(cwd)
|
||||
this.customInstructions = customInstructions
|
||||
this.autoApprovalSettings = autoApprovalSettings
|
||||
this.browserSettings = browserSettings
|
||||
this.chatSettings = chatSettings
|
||||
this.enableCheckpoints = enableCheckpointsSetting
|
||||
|
||||
// Set up MCP notification callback for real-time notifications
|
||||
this.mcpHub.setNotificationCallback(async (serverName: string, level: string, message: string) => {
|
||||
// Display notification in chat immediately
|
||||
await this.say("mcp_notification", `[${serverName}] ${message}`)
|
||||
})
|
||||
|
||||
// Initialize taskId first
|
||||
if (historyItem) {
|
||||
this.taskId = historyItem.id
|
||||
@@ -1200,9 +1196,6 @@ export class Task {
|
||||
this.clineIgnoreController.dispose()
|
||||
this.fileContextTracker.dispose()
|
||||
await this.diffViewProvider.revertChanges() // need to await for when we want to make sure directories/files are reverted before re-starting the task from a checkpoint
|
||||
|
||||
// Clear the notification callback when task is aborted
|
||||
this.mcpHub.clearNotificationCallback()
|
||||
}
|
||||
|
||||
// Checkpoints
|
||||
@@ -1627,6 +1620,12 @@ export class Task {
|
||||
}
|
||||
}
|
||||
|
||||
private async isClaude4ModelFamily(): Promise<boolean> {
|
||||
const model = this.api.getModel()
|
||||
const modelId = model.id
|
||||
return modelId.includes("sonnet-4") || modelId.includes("opus-4")
|
||||
}
|
||||
|
||||
async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
|
||||
// Wait for MCP servers to be connected before generating system prompt
|
||||
await pWaitFor(() => this.mcpHub.isConnecting !== true, { timeout: 10_000 }).catch(() => {
|
||||
@@ -1640,9 +1639,10 @@ export class Task {
|
||||
|
||||
const supportsBrowserUse = modelSupportsBrowserUse && !disableBrowserTool // only enable browser use if the model supports it and the user hasn't disabled it
|
||||
|
||||
const isClaude4Model = isClaude4ModelFamily(this.api)
|
||||
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings, isClaude4Model)
|
||||
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
|
||||
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings, isClaude4ModelFamily)
|
||||
|
||||
let settingsCustomInstructions = this.customInstructions?.trim()
|
||||
await this.migratePreferredLanguageToolSetting()
|
||||
const preferredLanguage = getLanguageKey(this.chatSettings.preferredLanguage as LanguageDisplay)
|
||||
const preferredLanguageInstructions =
|
||||
@@ -1670,6 +1670,7 @@ export class Task {
|
||||
}
|
||||
|
||||
if (
|
||||
settingsCustomInstructions ||
|
||||
globalClineRulesFileInstructions ||
|
||||
localClineRulesFileInstructions ||
|
||||
localCursorRulesFileInstructions ||
|
||||
@@ -1680,6 +1681,7 @@ export class Task {
|
||||
) {
|
||||
// altering the system prompt mid-task will break the prompt cache, but in the grand scheme this will not change often so it's better to not pollute user messages with it the way we have to with <potentially relevant details>
|
||||
const userInstructions = addUserInstructions(
|
||||
settingsCustomInstructions,
|
||||
globalClineRulesFileInstructions,
|
||||
localClineRulesFileInstructions,
|
||||
localCursorRulesFileInstructions,
|
||||
@@ -1688,7 +1690,6 @@ export class Task {
|
||||
clineIgnoreInstructions,
|
||||
preferredLanguageInstructions,
|
||||
)
|
||||
console.log("[INSTRUCTIONS] User instructions:", userInstructions)
|
||||
systemPrompt += userInstructions
|
||||
}
|
||||
const contextManagementMetadata = await this.contextManager.getNewContextMessagesAndMetadata(
|
||||
@@ -1920,6 +1921,7 @@ export class Task {
|
||||
|
||||
// Get final list of replacements
|
||||
const allReplacements = this.streamingJsonReplacer.getSuccessfullyParsedItems()
|
||||
// console.log(`Total replacements applied: ${allReplacements.length}`)
|
||||
|
||||
// Cleanup
|
||||
this.streamingJsonReplacer = undefined
|
||||
@@ -1949,6 +1951,7 @@ export class Task {
|
||||
if (this.didCompleteReadingStream) {
|
||||
this.userMessageContentReady = true
|
||||
}
|
||||
// console.log("no more content blocks to stream! this shouldn't happen?")
|
||||
this.presentAssistantMessageLocked = false
|
||||
return
|
||||
//throw new Error("No more content blocks to stream! This shouldn't happen...") // remove and just return after testing
|
||||
@@ -2084,11 +2087,11 @@ export class Task {
|
||||
break
|
||||
}
|
||||
|
||||
const pushToolResult = (content: ToolResponse, isClaude4Model: boolean = false) => {
|
||||
const pushToolResult = (content: ToolResponse, isClaude4ModelFamily: boolean = false) => {
|
||||
if (typeof content === "string") {
|
||||
const resultText = content || "(tool did not return anything)"
|
||||
|
||||
if (isClaude4Model && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
if (isClaude4ModelFamily && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
// Claude 4 family: Use function_results format
|
||||
this.userMessageContent.push({
|
||||
type: "text",
|
||||
@@ -2174,7 +2177,7 @@ export class Task {
|
||||
}
|
||||
}
|
||||
|
||||
const handleError = async (action: string, error: Error, isClaude4Model: boolean = false) => {
|
||||
const handleError = async (action: string, error: Error, isClaude4ModelFamily: boolean = false) => {
|
||||
if (this.abandoned) {
|
||||
console.log("Ignoring error since task was abandoned (i.e. from task cancellation after resetting)")
|
||||
return
|
||||
@@ -2185,7 +2188,7 @@ export class Task {
|
||||
`Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`,
|
||||
)
|
||||
|
||||
pushToolResult(formatResponse.toolError(errorString), isClaude4Model)
|
||||
pushToolResult(formatResponse.toolError(errorString), isClaude4ModelFamily)
|
||||
}
|
||||
|
||||
// If block is partial, remove partial closing tag so its not presented to user
|
||||
@@ -2263,9 +2266,9 @@ export class Task {
|
||||
|
||||
const currentFullJson = block.params.diff
|
||||
// Check if we should use streaming (e.g., for specific models)
|
||||
const isClaude4Model = isClaude4ModelFamily(this.api)
|
||||
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
|
||||
// Going through claude family of models
|
||||
if (isClaude4Model && USE_EXPERIMENTAL_CLAUDE4_FEATURES && currentFullJson) {
|
||||
if (isClaude4ModelFamily && USE_EXPERIMENTAL_CLAUDE4_FEATURES && currentFullJson) {
|
||||
console.log("[EDIT] Streaming JSON replacement")
|
||||
const streamingResult = await this.handleStreamingJsonReplacement(
|
||||
block,
|
||||
@@ -2657,7 +2660,7 @@ export class Task {
|
||||
}
|
||||
}
|
||||
case "list_files": {
|
||||
const isClaude4Model = isClaude4ModelFamily(this.api)
|
||||
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
|
||||
const relDirPath: string | undefined = block.params.path
|
||||
const recursiveRaw: string | undefined = block.params.recursive
|
||||
const recursive = recursiveRaw?.toLowerCase() === "true"
|
||||
@@ -2683,7 +2686,10 @@ export class Task {
|
||||
} else {
|
||||
if (!relDirPath) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path"), isClaude4Model)
|
||||
pushToolResult(
|
||||
await this.sayAndCreateMissingParamError("list_files", "path"),
|
||||
isClaude4ModelFamily,
|
||||
)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -2734,12 +2740,12 @@ export class Task {
|
||||
true,
|
||||
)
|
||||
}
|
||||
pushToolResult(result, isClaude4Model)
|
||||
pushToolResult(result, isClaude4ModelFamily)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("listing files", error, isClaude4Model)
|
||||
await handleError("listing files", error, isClaude4ModelFamily)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -2827,7 +2833,7 @@ export class Task {
|
||||
}
|
||||
}
|
||||
case "search_files": {
|
||||
const isClaude4Model = isClaude4ModelFamily(this.api)
|
||||
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
|
||||
const relDirPath: string | undefined = block.params.path
|
||||
const regex: string | undefined = block.params.regex
|
||||
const filePattern: string | undefined = block.params.file_pattern
|
||||
@@ -2857,7 +2863,7 @@ export class Task {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(
|
||||
await this.sayAndCreateMissingParamError("search_files", "path"),
|
||||
isClaude4Model,
|
||||
isClaude4ModelFamily,
|
||||
)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
@@ -2866,7 +2872,7 @@ export class Task {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(
|
||||
await this.sayAndCreateMissingParamError("search_files", "regex"),
|
||||
isClaude4Model,
|
||||
isClaude4ModelFamily,
|
||||
)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
@@ -2917,12 +2923,12 @@ export class Task {
|
||||
true,
|
||||
)
|
||||
}
|
||||
pushToolResult(results, isClaude4Model)
|
||||
pushToolResult(results, isClaude4ModelFamily)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError("searching files", error, isClaude4Model)
|
||||
await handleError("searching files", error, isClaude4ModelFamily)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -3317,21 +3323,8 @@ export class Task {
|
||||
|
||||
// now execute the tool
|
||||
await this.say("mcp_server_request_started") // same as browser_action_result
|
||||
|
||||
// Check for any pending notifications before the tool call
|
||||
const notificationsBefore = this.mcpHub.getPendingNotifications()
|
||||
for (const notification of notificationsBefore) {
|
||||
await this.say("mcp_notification", `[${notification.serverName}] ${notification.message}`)
|
||||
}
|
||||
|
||||
const toolResult = await this.mcpHub.callTool(server_name, tool_name, parsedArguments)
|
||||
|
||||
// Check for any pending notifications after the tool call
|
||||
const notificationsAfter = this.mcpHub.getPendingNotifications()
|
||||
for (const notification of notificationsAfter) {
|
||||
await this.say("mcp_notification", `[${notification.serverName}] ${notification.message}`)
|
||||
}
|
||||
|
||||
// TODO: add progress indicator
|
||||
|
||||
const toolResultImages =
|
||||
@@ -3720,7 +3713,7 @@ export class Task {
|
||||
const clineVersion =
|
||||
vscode.extensions.getExtension("saoudrizwan.claude-dev")?.packageJSON.version || "Unknown"
|
||||
const systemInfo = `VSCode: ${vscode.version}, Node.js: ${process.version}, Architecture: ${os.arch()}`
|
||||
const providerAndModel = `${(await getWorkspaceState(this.getContext(), "apiProvider")) as string} / ${this.api.getModel().id}`
|
||||
const providerAndModel = `${(await getGlobalState(this.getContext(), "apiProvider")) as string} / ${this.api.getModel().id}`
|
||||
|
||||
// Ask user for confirmation
|
||||
const bugReportData = JSON.stringify({
|
||||
@@ -4234,7 +4227,7 @@ export class Task {
|
||||
}
|
||||
|
||||
// Used to know what models were used in the task if user wants to export metadata for error reporting purposes
|
||||
const currentProviderId = (await getWorkspaceState(this.getContext(), "apiProvider")) as string
|
||||
const currentProviderId = (await getGlobalState(this.getContext(), "apiProvider")) as string
|
||||
if (currentProviderId && this.api.getModel().id) {
|
||||
try {
|
||||
await this.modelContextTracker.recordModelUsage(currentProviderId, this.api.getModel().id, this.chatSettings.mode)
|
||||
@@ -4510,8 +4503,9 @@ export class Task {
|
||||
assistantMessage += chunk.text
|
||||
// parse raw assistant message into content blocks
|
||||
const prevLength = this.assistantMessageContent.length
|
||||
const isClaude4Model = isClaude4ModelFamily(this.api)
|
||||
if (isClaude4Model && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
|
||||
|
||||
if (isClaude4ModelFamily && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
this.assistantMessageContent = parseAssistantMessageV3(assistantMessage)
|
||||
} else {
|
||||
this.assistantMessageContent = parseAssistantMessageV2(assistantMessage)
|
||||
|
||||
@@ -9,7 +9,6 @@ import { readFile } from "fs/promises"
|
||||
import path from "node:path"
|
||||
import { WebviewProviderType } from "@/shared/webview/types"
|
||||
import { sendThemeEvent } from "@core/controller/ui/subscribeToTheme"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -20,11 +19,9 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
|
||||
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
|
||||
public static readonly tabPanelId = "claude-dev.TabPanelProvider"
|
||||
private static activeInstances: Set<WebviewProvider> = new Set()
|
||||
private static clientIdMap = new Map<WebviewProvider, string>()
|
||||
public view?: vscode.WebviewView | vscode.WebviewPanel
|
||||
private disposables: vscode.Disposable[] = []
|
||||
controller: Controller
|
||||
private clientId: string
|
||||
|
||||
constructor(
|
||||
readonly context: vscode.ExtensionContext,
|
||||
@@ -32,21 +29,9 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
|
||||
private readonly providerType: WebviewProviderType = WebviewProviderType.TAB, // Default to tab provider
|
||||
) {
|
||||
WebviewProvider.activeInstances.add(this)
|
||||
this.clientId = uuidv4()
|
||||
WebviewProvider.clientIdMap.set(this, this.clientId)
|
||||
this.controller = new Controller(context, outputChannel, (message) => this.view?.webview.postMessage(message))
|
||||
}
|
||||
|
||||
// Add a method to get the client ID
|
||||
public getClientId(): string {
|
||||
return this.clientId
|
||||
}
|
||||
|
||||
// Add a static method to get the client ID for a specific instance
|
||||
public static getClientIdForInstance(instance: WebviewProvider): string | undefined {
|
||||
return WebviewProvider.clientIdMap.get(instance)
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this.view && "dispose" in this.view) {
|
||||
this.view.dispose()
|
||||
@@ -59,8 +44,6 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
await this.controller.dispose()
|
||||
WebviewProvider.activeInstances.delete(this)
|
||||
// Remove from client ID map
|
||||
WebviewProvider.clientIdMap.delete(this)
|
||||
}
|
||||
|
||||
public static getVisibleInstance(): WebviewProvider | undefined {
|
||||
@@ -262,9 +245,6 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
|
||||
<script type="text/javascript" nonce="${nonce}">
|
||||
// Inject the provider type
|
||||
window.WEBVIEW_PROVIDER_TYPE = ${JSON.stringify(this.providerType)};
|
||||
|
||||
// Inject the client ID
|
||||
window.clineClientId = "${this.clientId}";
|
||||
</script>
|
||||
<script type="module" nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
@@ -378,9 +358,6 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
|
||||
<script type="text/javascript" nonce="${nonce}">
|
||||
// Inject the provider type
|
||||
window.WEBVIEW_PROVIDER_TYPE = ${JSON.stringify(this.providerType)};
|
||||
|
||||
// Inject the client ID
|
||||
window.clineClientId = "${this.clientId}";
|
||||
</script>
|
||||
${reactRefresh}
|
||||
<script type="module" src="${scriptUri}"></script>
|
||||
|
||||
@@ -18,6 +18,13 @@ The Cline extension exposes an API that can be used by other extensions. To use
|
||||
if (cline) {
|
||||
// Now you can use the API
|
||||
|
||||
// Set custom instructions
|
||||
await cline.setCustomInstructions("Talk like a pirate")
|
||||
|
||||
// Get custom instructions
|
||||
const instructions = await cline.getCustomInstructions()
|
||||
console.log("Current custom instructions:", instructions)
|
||||
|
||||
// Start a new task with an initial message
|
||||
await cline.startNewTask("Hello, Cline! Let's make a new project...")
|
||||
|
||||
|
||||
@@ -65,6 +65,59 @@ describe("ClineAPI Core Functionality", () => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe("setCustomInstructions", () => {
|
||||
it("should update custom instructions in controller", async () => {
|
||||
const testInstructions = "Test custom instructions"
|
||||
|
||||
await api.setCustomInstructions(testInstructions)
|
||||
|
||||
// Verify controller method was called
|
||||
sinon.assert.calledOnce(mockController.updateCustomInstructions)
|
||||
sinon.assert.calledWith(mockController.updateCustomInstructions, testInstructions)
|
||||
|
||||
// Verify output channel was updated
|
||||
sinon.assert.calledWith(mockOutputChannel.appendLine, "Custom instructions set")
|
||||
})
|
||||
|
||||
it("should handle empty instructions", async () => {
|
||||
await api.setCustomInstructions("")
|
||||
|
||||
sinon.assert.calledWith(mockController.updateCustomInstructions, "")
|
||||
sinon.assert.calledWith(mockOutputChannel.appendLine, "Custom instructions set")
|
||||
})
|
||||
|
||||
it("should handle very long instructions", async () => {
|
||||
const longInstructions = "a".repeat(10000)
|
||||
|
||||
await api.setCustomInstructions(longInstructions)
|
||||
|
||||
sinon.assert.calledWith(mockController.updateCustomInstructions, longInstructions)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getCustomInstructions", () => {
|
||||
it("should retrieve custom instructions from state", async () => {
|
||||
const testInstructions = "Retrieved instructions"
|
||||
// The real implementation uses getGlobalState from the state module
|
||||
getGlobalStateStub.resolves(testInstructions)
|
||||
|
||||
const result = await api.getCustomInstructions()
|
||||
|
||||
result!.should.equal(testInstructions)
|
||||
sinon.assert.calledWith(getGlobalStateStub, mockController.context, "customInstructions")
|
||||
})
|
||||
|
||||
it("should return undefined when no instructions set", async () => {
|
||||
// The real implementation uses getGlobalState from the state module
|
||||
getGlobalStateStub.resolves(undefined)
|
||||
|
||||
const result = await api.getCustomInstructions()
|
||||
|
||||
should.not.exist(result)
|
||||
sinon.assert.calledWith(getGlobalStateStub, mockController.context, "customInstructions")
|
||||
})
|
||||
})
|
||||
|
||||
describe("startNewTask", () => {
|
||||
it("should clear existing task and start new one with description", async () => {
|
||||
const taskDescription = "Create a test function"
|
||||
@@ -207,6 +260,17 @@ describe("ClineAPI Core Functionality", () => {
|
||||
})
|
||||
|
||||
describe("Error Handling", () => {
|
||||
it("should handle errors in setCustomInstructions", async () => {
|
||||
mockController.updateCustomInstructions.rejects(new Error("Update failed"))
|
||||
|
||||
try {
|
||||
await api.setCustomInstructions("test")
|
||||
should.fail("", "", "Should have thrown an error", "")
|
||||
} catch (error: any) {
|
||||
error.message.should.equal("Update failed")
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle errors in task initialization", async () => {
|
||||
mockController.initTask.rejects(new Error("Init failed"))
|
||||
|
||||
|
||||
Vendored
+12
@@ -1,4 +1,16 @@
|
||||
export interface ClineAPI {
|
||||
/**
|
||||
* Sets the custom instructions in the global storage.
|
||||
* @param value The custom instructions to be saved.
|
||||
*/
|
||||
setCustomInstructions(value: string): Promise<void>
|
||||
|
||||
/**
|
||||
* Retrieves the custom instructions from the global storage.
|
||||
* @returns The saved custom instructions, or undefined if not set.
|
||||
*/
|
||||
getCustomInstructions(): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Starts a new task with an optional initial message and images.
|
||||
* @param task Optional initial task message.
|
||||
|
||||
@@ -7,6 +7,15 @@ import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui
|
||||
|
||||
export function createClineAPI(outputChannel: vscode.OutputChannel, sidebarController: Controller): ClineAPI {
|
||||
const api: ClineAPI = {
|
||||
setCustomInstructions: async (value: string) => {
|
||||
await sidebarController.updateCustomInstructions(value)
|
||||
outputChannel.appendLine("Custom instructions set")
|
||||
},
|
||||
|
||||
getCustomInstructions: async () => {
|
||||
return (await getGlobalState(sidebarController.context, "customInstructions")) as string | undefined
|
||||
},
|
||||
|
||||
startNewTask: async (task?: string, images?: string[]) => {
|
||||
outputChannel.appendLine("Starting new task")
|
||||
await sidebarController.clearTask()
|
||||
|
||||
+4
-11
@@ -22,9 +22,7 @@ import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui
|
||||
import { WebviewProviderType } from "./shared/webview/types"
|
||||
import { sendHistoryButtonClickedEvent } from "./core/controller/ui/subscribeToHistoryButtonClicked"
|
||||
import { sendAccountButtonClickedEvent } from "./core/controller/ui/subscribeToAccountButtonClicked"
|
||||
import { migratePlanActGlobalToWorkspaceStorage, migrateCustomInstructionsToGlobalRules } from "./core/storage/state"
|
||||
|
||||
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
|
||||
/*
|
||||
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
|
||||
|
||||
@@ -46,12 +44,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
Logger.initialize(outputChannel)
|
||||
Logger.log("Cline extension activated")
|
||||
|
||||
// Migrate global storage values to workspace storage (one-time cleanup)
|
||||
await migratePlanActGlobalToWorkspaceStorage(context)
|
||||
|
||||
// Migrate custom instructions to global Cline rules (one-time cleanup)
|
||||
await migrateCustomInstructionsToGlobalRules(context)
|
||||
|
||||
// Version checking for autoupdate notification
|
||||
const currentVersion = context.extension.packageJSON.version
|
||||
const previousVersion = context.globalState.get<string>("clineVersion")
|
||||
@@ -593,9 +585,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// At this point, activeWebviewProvider should be the one we want to send the message to.
|
||||
// It could still be undefined if opening a new tab failed or timed out.
|
||||
if (activeWebviewProvider) {
|
||||
// Use the gRPC streaming method instead of postMessageToWebview
|
||||
const clientId = activeWebviewProvider.getClientId()
|
||||
sendFocusChatInputEvent(clientId)
|
||||
activeWebviewProvider.controller.postMessageToWebview({
|
||||
type: "action",
|
||||
action: "focusChatInput",
|
||||
})
|
||||
} else {
|
||||
console.error("FocusChatInput: Could not find or activate a Cline webview to focus.")
|
||||
vscode.window.showErrorMessage(
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { StringRequest } from "@/shared/proto/common"
|
||||
import { Uri } from "@/shared/proto/host/uri"
|
||||
import { FileChangeEvent_ChangeType, SubscribeToFileRequest } from "@/shared/proto/host/watch"
|
||||
|
||||
const UriServiceClient = {
|
||||
parse: function (_: StringRequest): Uri {
|
||||
throw Error("Unimplemented")
|
||||
},
|
||||
}
|
||||
const WatchServiceClient = {
|
||||
subscribeToFile: function (
|
||||
_r: SubscribeToFileRequest,
|
||||
_h: {
|
||||
onResponse?: (response: { type: FileChangeEvent_ChangeType }) => void | Promise<void>
|
||||
onError?: (error: any) => void
|
||||
onComplete?: () => void
|
||||
},
|
||||
) {
|
||||
throw Error("Unimplemented")
|
||||
},
|
||||
}
|
||||
|
||||
export { UriServiceClient, WatchServiceClient }
|
||||
@@ -1,8 +0,0 @@
|
||||
import * as VscodeClient from "./vscode/client/host-grpc-client"
|
||||
import * as ExternalClient from "./external/client/host-bridge-client"
|
||||
|
||||
const isHostBridgeExternal = process.env.HOST_BRIDGE_ADDRESS !== undefined && process.env.HOST_BRIDGE_ADDRESS !== "vscode"
|
||||
const Client = isHostBridgeExternal ? ExternalClient : VscodeClient
|
||||
|
||||
export const UriServiceClient = Client.UriServiceClient
|
||||
export const WatchServiceClient = Client.WatchServiceClient
|
||||
+1
-126
@@ -19,7 +19,7 @@ import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { z } from "zod"
|
||||
import { WatchServiceClient } from "@hosts/host-bridge-client"
|
||||
import { WatchServiceClient } from "../../standalone/services/host-grpc-client"
|
||||
import { FileChangeEvent_ChangeType, SubscribeToFileRequest } from "../../shared/proto/host/watch"
|
||||
import { Metadata } from "../../shared/proto/common"
|
||||
import {
|
||||
@@ -54,17 +54,6 @@ export class McpHub {
|
||||
connections: McpConnection[] = []
|
||||
isConnecting: boolean = false
|
||||
|
||||
// Store notifications for display in chat
|
||||
private pendingNotifications: Array<{
|
||||
serverName: string
|
||||
level: string
|
||||
message: string
|
||||
timestamp: number
|
||||
}> = []
|
||||
|
||||
// Callback for sending notifications to active task
|
||||
private notificationCallback?: (serverName: string, level: string, message: string) => void
|
||||
|
||||
constructor(
|
||||
getMcpServersPath: () => Promise<string>,
|
||||
getSettingsDirectoryPath: () => Promise<string>,
|
||||
@@ -332,88 +321,6 @@ export class McpHub {
|
||||
connection.server.status = "connected"
|
||||
connection.server.error = ""
|
||||
|
||||
// Register notification handler for real-time messages
|
||||
console.log(`[MCP Debug] Setting up notification handlers for server: ${name}`)
|
||||
console.log(`[MCP Debug] Client instance:`, connection.client)
|
||||
console.log(`[MCP Debug] Transport type:`, config.type)
|
||||
|
||||
// Try to set notification handler using the client's method
|
||||
try {
|
||||
// Import the notification schema from MCP SDK
|
||||
const { z } = await import("zod")
|
||||
|
||||
// Define the notification schema for notifications/message
|
||||
const NotificationMessageSchema = z.object({
|
||||
method: z.literal("notifications/message"),
|
||||
params: z
|
||||
.object({
|
||||
level: z.enum(["debug", "info", "warning", "error"]).optional(),
|
||||
logger: z.string().optional(),
|
||||
data: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
// Set the notification handler
|
||||
connection.client.setNotificationHandler(NotificationMessageSchema as any, async (notification: any) => {
|
||||
console.log(`[MCP Notification] ${name}:`, JSON.stringify(notification, null, 2))
|
||||
|
||||
const params = notification.params || {}
|
||||
const level = params.level || "info"
|
||||
const data = params.data || params.message || ""
|
||||
const logger = params.logger || ""
|
||||
|
||||
console.log(`[MCP Message Notification] ${name}: level=${level}, data=${data}, logger=${logger}`)
|
||||
|
||||
// Format the message
|
||||
const message = logger ? `[${logger}] ${data}` : data
|
||||
|
||||
// Send notification directly to active task if callback is set
|
||||
if (this.notificationCallback) {
|
||||
console.log(`[MCP Debug] Sending notification to active task: ${message}`)
|
||||
this.notificationCallback(name, level, message)
|
||||
} else {
|
||||
// Fallback: store for later retrieval
|
||||
console.log(`[MCP Debug] No active task, storing notification: ${message}`)
|
||||
this.pendingNotifications.push({
|
||||
serverName: name,
|
||||
level,
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
// Forward to webview if available
|
||||
if (this.postMessageToWebview) {
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpNotification",
|
||||
serverName: name,
|
||||
notification: {
|
||||
level,
|
||||
data,
|
||||
logger,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
})
|
||||
console.log(`[MCP Debug] Successfully set notifications/message handler for ${name}`)
|
||||
|
||||
// Also set a fallback handler for any other notification types
|
||||
connection.client.fallbackNotificationHandler = async (notification: any) => {
|
||||
console.log(`[MCP Fallback Notification] ${name}:`, JSON.stringify(notification, null, 2))
|
||||
|
||||
// Show in VS Code for visibility
|
||||
vscode.window.showInformationMessage(
|
||||
`MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
|
||||
)
|
||||
}
|
||||
console.log(`[MCP Debug] Successfully set fallback notification handler for ${name}`)
|
||||
} catch (error) {
|
||||
console.error(`[MCP Debug] Error setting notification handlers for ${name}:`, error)
|
||||
}
|
||||
|
||||
// Initial fetch of tools and resources
|
||||
connection.server.tools = await this.fetchToolsList(name)
|
||||
connection.server.resources = await this.fetchResourcesList(name)
|
||||
@@ -1042,38 +949,6 @@ export class McpHub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and clear pending notifications
|
||||
* @returns Array of pending notifications
|
||||
*/
|
||||
getPendingNotifications(): Array<{
|
||||
serverName: string
|
||||
level: string
|
||||
message: string
|
||||
timestamp: number
|
||||
}> {
|
||||
const notifications = [...this.pendingNotifications]
|
||||
this.pendingNotifications = []
|
||||
return notifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the notification callback for real-time notifications
|
||||
* @param callback Function to call when notifications arrive
|
||||
*/
|
||||
setNotificationCallback(callback: (serverName: string, level: string, message: string) => void): void {
|
||||
this.notificationCallback = callback
|
||||
console.log("[MCP Debug] Notification callback set")
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the notification callback
|
||||
*/
|
||||
clearNotificationCallback(): void {
|
||||
this.notificationCallback = undefined
|
||||
console.log("[MCP Debug] Notification callback cleared")
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.removeAllFileWatchers()
|
||||
for (const connection of this.connections) {
|
||||
|
||||
@@ -13,13 +13,7 @@ import {
|
||||
getFileChanges,
|
||||
calculateToolSuccessRate,
|
||||
} from "./GitHelper"
|
||||
import {
|
||||
updateGlobalState,
|
||||
getAllExtensionState,
|
||||
updateApiConfiguration,
|
||||
storeSecret,
|
||||
updateWorkspaceState,
|
||||
} from "@core/storage/state"
|
||||
import { updateGlobalState, getAllExtensionState, updateApiConfiguration, storeSecret } from "@core/storage/state"
|
||||
import { ClineAsk, ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import { ApiProvider } from "@shared/api"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
@@ -278,7 +272,7 @@ export function createTestServer(webviewProvider?: WebviewProvider): http.Server
|
||||
await updateApiConfiguration(visibleWebview.controller.context, updatedConfig)
|
||||
|
||||
// Update global state to use cline provider
|
||||
await updateWorkspaceState(visibleWebview.controller.context, "apiProvider", "cline" as ApiProvider)
|
||||
await updateGlobalState(visibleWebview.controller.context, "apiProvider", "cline" as ApiProvider)
|
||||
|
||||
// Post state to webview to reflect changes
|
||||
await visibleWebview.controller.postStateToWebview()
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface ExtensionMessage {
|
||||
| "userCreditsPayments"
|
||||
| "grpc_response" // New type for gRPC responses
|
||||
text?: string
|
||||
action?: "didBecomeVisible" | "accountLogoutClicked"
|
||||
action?: "didBecomeVisible" | "accountLogoutClicked" | "focusChatInput"
|
||||
state?: ExtensionState
|
||||
images?: string[]
|
||||
files?: string[]
|
||||
@@ -81,6 +81,7 @@ export interface ExtensionState {
|
||||
checkpointTrackerErrorMessage?: string
|
||||
clineMessages: ClineMessage[]
|
||||
currentTaskItem?: HistoryItem
|
||||
customInstructions?: string
|
||||
mcpMarketplaceEnabled?: boolean
|
||||
planActSeparateModelsSetting: boolean
|
||||
enableCheckpointsSetting?: boolean
|
||||
@@ -162,7 +163,6 @@ export type ClineSay =
|
||||
| "browser_action_result"
|
||||
| "mcp_server_request_started"
|
||||
| "mcp_server_response"
|
||||
| "mcp_notification"
|
||||
| "use_mcp_server"
|
||||
| "diff_error"
|
||||
| "deleted_api_reqs"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { McpViewTab } from "./mcp"
|
||||
|
||||
export interface WebviewMessage {
|
||||
type:
|
||||
| "apiConfiguration"
|
||||
| "requestVsCodeLmModels"
|
||||
| "authStateChanged"
|
||||
| "fetchMcpMarketplace"
|
||||
@@ -45,6 +46,7 @@ export interface WebviewMessage {
|
||||
mcpMarketplaceEnabled?: boolean
|
||||
mcpResponsesCollapsed?: boolean
|
||||
telemetrySetting?: TelemetrySetting
|
||||
customInstructionsSetting?: string
|
||||
mentionsRequestId?: string
|
||||
query?: string
|
||||
// For toggleFavoriteModel
|
||||
|
||||
+9
-3
@@ -598,6 +598,9 @@ export const vertexModels = {
|
||||
cacheReadsPrice: 0.625,
|
||||
},
|
||||
],
|
||||
thinkingConfig: {
|
||||
maxBudget: 32768,
|
||||
},
|
||||
},
|
||||
"gemini-2.5-flash-preview-04-17": {
|
||||
maxTokens: 65536,
|
||||
@@ -766,6 +769,9 @@ export const geminiModels = {
|
||||
cacheReadsPrice: 0.625,
|
||||
},
|
||||
],
|
||||
thinkingConfig: {
|
||||
maxBudget: 32768,
|
||||
},
|
||||
},
|
||||
"gemini-2.5-flash-preview-05-20": {
|
||||
maxTokens: 65536,
|
||||
@@ -917,9 +923,9 @@ export const openAiNativeModels = {
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 2.0,
|
||||
outputPrice: 8.0,
|
||||
cacheReadsPrice: 0.5,
|
||||
inputPrice: 10.0,
|
||||
outputPrice: 40.0,
|
||||
cacheReadsPrice: 2.5,
|
||||
},
|
||||
"o4-mini": {
|
||||
maxTokens: 100_000,
|
||||
|
||||
@@ -89,7 +89,6 @@ function convertClineSayToProtoEnum(say: AppClineSay | undefined): ClineSay | un
|
||||
browser_action_result: ClineSay.BROWSER_ACTION_RESULT,
|
||||
mcp_server_request_started: ClineSay.MCP_SERVER_REQUEST_STARTED,
|
||||
mcp_server_response: ClineSay.MCP_SERVER_RESPONSE,
|
||||
mcp_notification: ClineSay.MCP_NOTIFICATION,
|
||||
use_mcp_server: ClineSay.USE_MCP_SERVER_SAY,
|
||||
diff_error: ClineSay.DIFF_ERROR,
|
||||
deleted_api_reqs: ClineSay.DELETED_API_REQS,
|
||||
@@ -133,7 +132,6 @@ function convertProtoEnumToClineSay(say: ClineSay): AppClineSay | undefined {
|
||||
[ClineSay.BROWSER_ACTION_RESULT]: "browser_action_result",
|
||||
[ClineSay.MCP_SERVER_REQUEST_STARTED]: "mcp_server_request_started",
|
||||
[ClineSay.MCP_SERVER_RESPONSE]: "mcp_server_response",
|
||||
[ClineSay.MCP_NOTIFICATION]: "mcp_notification",
|
||||
[ClineSay.USE_MCP_SERVER_SAY]: "use_mcp_server",
|
||||
[ClineSay.DIFF_ERROR]: "diff_error",
|
||||
[ClineSay.DELETED_API_REQS]: "deleted_api_reqs",
|
||||
|
||||
@@ -1,442 +0,0 @@
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ApiProvider,
|
||||
BedrockModelId,
|
||||
ModelInfo,
|
||||
OpenAiCompatibleModelInfo as AppOpenAiCompatibleModelInfo,
|
||||
LiteLLMModelInfo as AppLiteLLMModelInfo,
|
||||
} from "../../api"
|
||||
import {
|
||||
ModelsApiConfiguration as ProtoApiConfiguration,
|
||||
ApiProvider as ProtoApiProvider,
|
||||
LiteLLMModelInfo,
|
||||
OpenAiCompatibleModelInfo,
|
||||
OpenRouterModelInfo,
|
||||
ThinkingConfig,
|
||||
} from "../../proto/models"
|
||||
|
||||
// Convert application ThinkingConfig to proto ThinkingConfig
|
||||
function convertThinkingConfigToProto(config: ModelInfo["thinkingConfig"]): ThinkingConfig | undefined {
|
||||
if (!config) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxBudget: config.maxBudget,
|
||||
outputPrice: config.outputPrice,
|
||||
outputPriceTiers: config.outputPriceTiers || [], // Provide empty array if undefined
|
||||
}
|
||||
}
|
||||
|
||||
// Convert proto ThinkingConfig to application ThinkingConfig
|
||||
function convertProtoToThinkingConfig(config: ThinkingConfig | undefined): ModelInfo["thinkingConfig"] | undefined {
|
||||
if (!config) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxBudget: config.maxBudget,
|
||||
outputPrice: config.outputPrice,
|
||||
outputPriceTiers: config.outputPriceTiers.length > 0 ? config.outputPriceTiers : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert application ModelInfo to proto OpenRouterModelInfo
|
||||
function convertModelInfoToProtoOpenRouter(info: ModelInfo | undefined): OpenRouterModelInfo | undefined {
|
||||
if (!info) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxTokens: info.maxTokens,
|
||||
contextWindow: info.contextWindow,
|
||||
supportsImages: info.supportsImages,
|
||||
supportsPromptCache: info.supportsPromptCache ?? false,
|
||||
inputPrice: info.inputPrice,
|
||||
outputPrice: info.outputPrice,
|
||||
cacheWritesPrice: info.cacheWritesPrice,
|
||||
cacheReadsPrice: info.cacheReadsPrice,
|
||||
description: info.description,
|
||||
thinkingConfig: convertThinkingConfigToProto(info.thinkingConfig),
|
||||
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
|
||||
tiers: info.tiers || [],
|
||||
}
|
||||
}
|
||||
|
||||
// Convert proto OpenRouterModelInfo to application ModelInfo
|
||||
function convertProtoToModelInfo(info: OpenRouterModelInfo | undefined): ModelInfo | undefined {
|
||||
if (!info) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxTokens: info.maxTokens,
|
||||
contextWindow: info.contextWindow,
|
||||
supportsImages: info.supportsImages,
|
||||
supportsPromptCache: info.supportsPromptCache,
|
||||
inputPrice: info.inputPrice,
|
||||
outputPrice: info.outputPrice,
|
||||
cacheWritesPrice: info.cacheWritesPrice,
|
||||
cacheReadsPrice: info.cacheReadsPrice,
|
||||
description: info.description,
|
||||
thinkingConfig: convertProtoToThinkingConfig(info.thinkingConfig),
|
||||
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
|
||||
tiers: info.tiers.length > 0 ? info.tiers : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert application LiteLLMModelInfo to proto LiteLLMModelInfo
|
||||
function convertLiteLLMModelInfoToProto(info: AppLiteLLMModelInfo | undefined): LiteLLMModelInfo | undefined {
|
||||
if (!info) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxTokens: info.maxTokens,
|
||||
contextWindow: info.contextWindow,
|
||||
supportsImages: info.supportsImages,
|
||||
supportsPromptCache: info.supportsPromptCache ?? false,
|
||||
inputPrice: info.inputPrice,
|
||||
outputPrice: info.outputPrice,
|
||||
thinkingConfig: convertThinkingConfigToProto(info.thinkingConfig),
|
||||
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
|
||||
cacheWritesPrice: info.cacheWritesPrice,
|
||||
cacheReadsPrice: info.cacheReadsPrice,
|
||||
description: info.description,
|
||||
tiers: info.tiers || [],
|
||||
temperature: info.temperature,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert proto LiteLLMModelInfo to application LiteLLMModelInfo
|
||||
function convertProtoToLiteLLMModelInfo(info: LiteLLMModelInfo | undefined): AppLiteLLMModelInfo | undefined {
|
||||
if (!info) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxTokens: info.maxTokens,
|
||||
contextWindow: info.contextWindow,
|
||||
supportsImages: info.supportsImages,
|
||||
supportsPromptCache: info.supportsPromptCache,
|
||||
inputPrice: info.inputPrice,
|
||||
outputPrice: info.outputPrice,
|
||||
thinkingConfig: convertProtoToThinkingConfig(info.thinkingConfig),
|
||||
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
|
||||
cacheWritesPrice: info.cacheWritesPrice,
|
||||
cacheReadsPrice: info.cacheReadsPrice,
|
||||
description: info.description,
|
||||
tiers: info.tiers.length > 0 ? info.tiers : undefined,
|
||||
temperature: info.temperature,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert application OpenAiCompatibleModelInfo to proto OpenAiCompatibleModelInfo
|
||||
function convertOpenAiCompatibleModelInfoToProto(
|
||||
info: AppOpenAiCompatibleModelInfo | undefined,
|
||||
): OpenAiCompatibleModelInfo | undefined {
|
||||
if (!info) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxTokens: info.maxTokens,
|
||||
contextWindow: info.contextWindow,
|
||||
supportsImages: info.supportsImages,
|
||||
supportsPromptCache: info.supportsPromptCache ?? false,
|
||||
inputPrice: info.inputPrice,
|
||||
outputPrice: info.outputPrice,
|
||||
thinkingConfig: convertThinkingConfigToProto(info.thinkingConfig),
|
||||
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
|
||||
cacheWritesPrice: info.cacheWritesPrice,
|
||||
cacheReadsPrice: info.cacheReadsPrice,
|
||||
description: info.description,
|
||||
tiers: info.tiers || [],
|
||||
temperature: info.temperature,
|
||||
isR1FormatRequired: info.isR1FormatRequired,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert proto OpenAiCompatibleModelInfo to application OpenAiCompatibleModelInfo
|
||||
function convertProtoToOpenAiCompatibleModelInfo(
|
||||
info: OpenAiCompatibleModelInfo | undefined,
|
||||
): AppOpenAiCompatibleModelInfo | undefined {
|
||||
if (!info) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
maxTokens: info.maxTokens,
|
||||
contextWindow: info.contextWindow,
|
||||
supportsImages: info.supportsImages,
|
||||
supportsPromptCache: info.supportsPromptCache,
|
||||
inputPrice: info.inputPrice,
|
||||
outputPrice: info.outputPrice,
|
||||
thinkingConfig: convertProtoToThinkingConfig(info.thinkingConfig),
|
||||
supportsGlobalEndpoint: info.supportsGlobalEndpoint,
|
||||
cacheWritesPrice: info.cacheWritesPrice,
|
||||
cacheReadsPrice: info.cacheReadsPrice,
|
||||
description: info.description,
|
||||
tiers: info.tiers.length > 0 ? info.tiers : undefined,
|
||||
temperature: info.temperature,
|
||||
isR1FormatRequired: info.isR1FormatRequired,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert application ApiProvider to proto ApiProvider
|
||||
function convertApiProviderToProto(provider: string | undefined): ProtoApiProvider {
|
||||
switch (provider) {
|
||||
case "anthropic":
|
||||
return ProtoApiProvider.ANTHROPIC
|
||||
case "openrouter":
|
||||
return ProtoApiProvider.OPENROUTER
|
||||
case "bedrock":
|
||||
return ProtoApiProvider.BEDROCK
|
||||
case "vertex":
|
||||
return ProtoApiProvider.VERTEX
|
||||
case "openai":
|
||||
return ProtoApiProvider.OPENAI
|
||||
case "ollama":
|
||||
return ProtoApiProvider.OLLAMA
|
||||
case "lmstudio":
|
||||
return ProtoApiProvider.LMSTUDIO
|
||||
case "gemini":
|
||||
return ProtoApiProvider.GEMINI
|
||||
case "openai-native":
|
||||
return ProtoApiProvider.OPENAI_NATIVE
|
||||
case "requesty":
|
||||
return ProtoApiProvider.REQUESTY
|
||||
case "together":
|
||||
return ProtoApiProvider.TOGETHER
|
||||
case "deepseek":
|
||||
return ProtoApiProvider.DEEPSEEK
|
||||
case "qwen":
|
||||
return ProtoApiProvider.QWEN
|
||||
case "doubao":
|
||||
return ProtoApiProvider.DOUBAO
|
||||
case "mistral":
|
||||
return ProtoApiProvider.MISTRAL
|
||||
case "vscode-lm":
|
||||
return ProtoApiProvider.VSCODE_LM
|
||||
case "cline":
|
||||
return ProtoApiProvider.CLINE
|
||||
case "litellm":
|
||||
return ProtoApiProvider.LITELLM
|
||||
case "nebius":
|
||||
return ProtoApiProvider.NEBIUS
|
||||
case "fireworks":
|
||||
return ProtoApiProvider.FIREWORKS
|
||||
case "asksage":
|
||||
return ProtoApiProvider.ASKSAGE
|
||||
case "xai":
|
||||
return ProtoApiProvider.XAI
|
||||
case "sambanova":
|
||||
return ProtoApiProvider.SAMBANOVA
|
||||
case "cerebras":
|
||||
return ProtoApiProvider.CEREBRAS
|
||||
default:
|
||||
return ProtoApiProvider.ANTHROPIC
|
||||
}
|
||||
}
|
||||
|
||||
// Convert proto ApiProvider to application ApiProvider
|
||||
function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
|
||||
switch (provider) {
|
||||
case ProtoApiProvider.ANTHROPIC:
|
||||
return "anthropic"
|
||||
case ProtoApiProvider.OPENROUTER:
|
||||
return "openrouter"
|
||||
case ProtoApiProvider.BEDROCK:
|
||||
return "bedrock"
|
||||
case ProtoApiProvider.VERTEX:
|
||||
return "vertex"
|
||||
case ProtoApiProvider.OPENAI:
|
||||
return "openai"
|
||||
case ProtoApiProvider.OLLAMA:
|
||||
return "ollama"
|
||||
case ProtoApiProvider.LMSTUDIO:
|
||||
return "lmstudio"
|
||||
case ProtoApiProvider.GEMINI:
|
||||
return "gemini"
|
||||
case ProtoApiProvider.OPENAI_NATIVE:
|
||||
return "openai-native"
|
||||
case ProtoApiProvider.REQUESTY:
|
||||
return "requesty"
|
||||
case ProtoApiProvider.TOGETHER:
|
||||
return "together"
|
||||
case ProtoApiProvider.DEEPSEEK:
|
||||
return "deepseek"
|
||||
case ProtoApiProvider.QWEN:
|
||||
return "qwen"
|
||||
case ProtoApiProvider.DOUBAO:
|
||||
return "doubao"
|
||||
case ProtoApiProvider.MISTRAL:
|
||||
return "mistral"
|
||||
case ProtoApiProvider.VSCODE_LM:
|
||||
return "vscode-lm"
|
||||
case ProtoApiProvider.CLINE:
|
||||
return "cline"
|
||||
case ProtoApiProvider.LITELLM:
|
||||
return "litellm"
|
||||
case ProtoApiProvider.NEBIUS:
|
||||
return "nebius"
|
||||
case ProtoApiProvider.FIREWORKS:
|
||||
return "fireworks"
|
||||
case ProtoApiProvider.ASKSAGE:
|
||||
return "asksage"
|
||||
case ProtoApiProvider.XAI:
|
||||
return "xai"
|
||||
case ProtoApiProvider.SAMBANOVA:
|
||||
return "sambanova"
|
||||
case ProtoApiProvider.CEREBRAS:
|
||||
return "cerebras"
|
||||
default:
|
||||
return "anthropic"
|
||||
}
|
||||
}
|
||||
|
||||
// Converts application ApiConfiguration to proto ApiConfiguration
|
||||
export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoApiConfiguration {
|
||||
return {
|
||||
apiModelId: config.apiModelId,
|
||||
apiKey: config.apiKey,
|
||||
clineApiKey: config.clineApiKey,
|
||||
taskId: config.taskId,
|
||||
liteLlmBaseUrl: config.liteLlmBaseUrl,
|
||||
liteLlmModelId: config.liteLlmModelId,
|
||||
liteLlmApiKey: config.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: config.liteLlmUsePromptCache,
|
||||
openAiHeaders: config.openAiHeaders || {},
|
||||
liteLlmModelInfo: convertLiteLLMModelInfoToProto(config.liteLlmModelInfo),
|
||||
anthropicBaseUrl: config.anthropicBaseUrl,
|
||||
openRouterApiKey: config.openRouterApiKey,
|
||||
openRouterModelId: config.openRouterModelId,
|
||||
openRouterModelInfo: convertModelInfoToProtoOpenRouter(config.openRouterModelInfo),
|
||||
openRouterProviderSorting: config.openRouterProviderSorting,
|
||||
awsAccessKey: config.awsAccessKey,
|
||||
awsSecretKey: config.awsSecretKey,
|
||||
awsSessionToken: config.awsSessionToken,
|
||||
awsRegion: config.awsRegion,
|
||||
awsUseCrossRegionInference: config.awsUseCrossRegionInference,
|
||||
awsBedrockUsePromptCache: config.awsBedrockUsePromptCache,
|
||||
awsUseProfile: config.awsUseProfile,
|
||||
awsProfile: config.awsProfile,
|
||||
awsBedrockEndpoint: config.awsBedrockEndpoint,
|
||||
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId as string | undefined,
|
||||
vertexProjectId: config.vertexProjectId,
|
||||
vertexRegion: config.vertexRegion,
|
||||
openAiBaseUrl: config.openAiBaseUrl,
|
||||
openAiApiKey: config.openAiApiKey,
|
||||
openAiModelId: config.openAiModelId,
|
||||
openAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.openAiModelInfo),
|
||||
ollamaModelId: config.ollamaModelId,
|
||||
ollamaBaseUrl: config.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId: config.lmStudioModelId,
|
||||
lmStudioBaseUrl: config.lmStudioBaseUrl,
|
||||
geminiApiKey: config.geminiApiKey,
|
||||
geminiBaseUrl: config.geminiBaseUrl,
|
||||
openAiNativeApiKey: config.openAiNativeApiKey,
|
||||
deepSeekApiKey: config.deepSeekApiKey,
|
||||
requestyApiKey: config.requestyApiKey,
|
||||
requestyModelId: config.requestyModelId,
|
||||
requestyModelInfo: convertModelInfoToProtoOpenRouter(config.requestyModelInfo),
|
||||
togetherApiKey: config.togetherApiKey,
|
||||
togetherModelId: config.togetherModelId,
|
||||
fireworksApiKey: config.fireworksApiKey,
|
||||
fireworksModelId: config.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: config.fireworksModelMaxTokens,
|
||||
qwenApiKey: config.qwenApiKey,
|
||||
doubaoApiKey: config.doubaoApiKey,
|
||||
mistralApiKey: config.mistralApiKey,
|
||||
azureApiVersion: config.azureApiVersion,
|
||||
vsCodeLmModelSelector: config.vsCodeLmModelSelector,
|
||||
qwenApiLine: config.qwenApiLine,
|
||||
nebiusApiKey: config.nebiusApiKey,
|
||||
asksageApiUrl: config.asksageApiUrl,
|
||||
asksageApiKey: config.asksageApiKey,
|
||||
xaiApiKey: config.xaiApiKey,
|
||||
thinkingBudgetTokens: config.thinkingBudgetTokens,
|
||||
reasoningEffort: config.reasoningEffort,
|
||||
sambanovaApiKey: config.sambanovaApiKey,
|
||||
cerebrasApiKey: config.cerebrasApiKey,
|
||||
requestTimeoutMs: config.requestTimeoutMs,
|
||||
apiProvider: config.apiProvider ? convertApiProviderToProto(config.apiProvider) : undefined,
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
}
|
||||
}
|
||||
|
||||
// Converts proto ApiConfiguration to application ApiConfiguration
|
||||
export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
|
||||
return {
|
||||
apiModelId: protoConfig.apiModelId,
|
||||
apiKey: protoConfig.apiKey,
|
||||
clineApiKey: protoConfig.clineApiKey,
|
||||
taskId: protoConfig.taskId,
|
||||
liteLlmBaseUrl: protoConfig.liteLlmBaseUrl,
|
||||
liteLlmModelId: protoConfig.liteLlmModelId,
|
||||
liteLlmApiKey: protoConfig.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: protoConfig.liteLlmUsePromptCache,
|
||||
openAiHeaders: Object.keys(protoConfig.openAiHeaders).length > 0 ? protoConfig.openAiHeaders : undefined,
|
||||
liteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.liteLlmModelInfo),
|
||||
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
|
||||
openRouterApiKey: protoConfig.openRouterApiKey,
|
||||
openRouterModelId: protoConfig.openRouterModelId,
|
||||
openRouterModelInfo: convertProtoToModelInfo(protoConfig.openRouterModelInfo),
|
||||
openRouterProviderSorting: protoConfig.openRouterProviderSorting,
|
||||
awsAccessKey: protoConfig.awsAccessKey,
|
||||
awsSecretKey: protoConfig.awsSecretKey,
|
||||
awsSessionToken: protoConfig.awsSessionToken,
|
||||
awsRegion: protoConfig.awsRegion,
|
||||
awsUseCrossRegionInference: protoConfig.awsUseCrossRegionInference,
|
||||
awsBedrockUsePromptCache: protoConfig.awsBedrockUsePromptCache,
|
||||
awsUseProfile: protoConfig.awsUseProfile,
|
||||
awsProfile: protoConfig.awsProfile,
|
||||
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
|
||||
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
vertexProjectId: protoConfig.vertexProjectId,
|
||||
vertexRegion: protoConfig.vertexRegion,
|
||||
openAiBaseUrl: protoConfig.openAiBaseUrl,
|
||||
openAiApiKey: protoConfig.openAiApiKey,
|
||||
openAiModelId: protoConfig.openAiModelId,
|
||||
openAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.openAiModelInfo),
|
||||
ollamaModelId: protoConfig.ollamaModelId,
|
||||
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId: protoConfig.lmStudioModelId,
|
||||
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
|
||||
geminiApiKey: protoConfig.geminiApiKey,
|
||||
geminiBaseUrl: protoConfig.geminiBaseUrl,
|
||||
openAiNativeApiKey: protoConfig.openAiNativeApiKey,
|
||||
deepSeekApiKey: protoConfig.deepSeekApiKey,
|
||||
requestyApiKey: protoConfig.requestyApiKey,
|
||||
requestyModelId: protoConfig.requestyModelId,
|
||||
requestyModelInfo: convertProtoToModelInfo(protoConfig.requestyModelInfo),
|
||||
togetherApiKey: protoConfig.togetherApiKey,
|
||||
togetherModelId: protoConfig.togetherModelId,
|
||||
fireworksApiKey: protoConfig.fireworksApiKey,
|
||||
fireworksModelId: protoConfig.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens,
|
||||
qwenApiKey: protoConfig.qwenApiKey,
|
||||
doubaoApiKey: protoConfig.doubaoApiKey,
|
||||
mistralApiKey: protoConfig.mistralApiKey,
|
||||
azureApiVersion: protoConfig.azureApiVersion,
|
||||
vsCodeLmModelSelector: protoConfig.vsCodeLmModelSelector,
|
||||
qwenApiLine: protoConfig.qwenApiLine,
|
||||
nebiusApiKey: protoConfig.nebiusApiKey,
|
||||
asksageApiUrl: protoConfig.asksageApiUrl,
|
||||
asksageApiKey: protoConfig.asksageApiKey,
|
||||
xaiApiKey: protoConfig.xaiApiKey,
|
||||
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens,
|
||||
reasoningEffort: protoConfig.reasoningEffort,
|
||||
sambanovaApiKey: protoConfig.sambanovaApiKey,
|
||||
cerebrasApiKey: protoConfig.cerebrasApiKey,
|
||||
requestTimeoutMs: protoConfig.requestTimeoutMs,
|
||||
apiProvider: protoConfig.apiProvider !== undefined ? convertProtoToApiProvider(protoConfig.apiProvider) : undefined,
|
||||
favoritedModelIds: protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LanguageModelChatSelector } from "../../proto/models"
|
||||
import { VsCodeLmModel } from "../../proto/models"
|
||||
|
||||
/**
|
||||
* Represents a VS Code language model in the native VS Code format
|
||||
@@ -13,7 +13,7 @@ export interface VsCodeNativeModel {
|
||||
/**
|
||||
* Converts VS Code native model format to protobuf format
|
||||
*/
|
||||
export function convertVsCodeNativeModelsToProtoModels(models: VsCodeNativeModel[]): LanguageModelChatSelector[] {
|
||||
export function convertVsCodeNativeModelsToProtoModels(models: VsCodeNativeModel[]): VsCodeLmModel[] {
|
||||
return (models || []).map((model) => ({
|
||||
vendor: model.vendor || "",
|
||||
family: model.family || "",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiConfiguration, ApiProvider, BedrockModelId } from "@shared/api"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import {
|
||||
ApiConfiguration as ProtoApiConfiguration,
|
||||
@@ -122,7 +122,7 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
|
||||
// eslint-disable-next-line eslint-rules/no-protobuf-object-literals
|
||||
const config: ApiConfiguration = {
|
||||
// Core API fields
|
||||
apiProvider: protoConfig.apiProvider as ApiProvider,
|
||||
apiProvider: protoConfig.apiProvider as any,
|
||||
apiModelId: protoConfig.apiModelId,
|
||||
apiKey: protoConfig.apiKey,
|
||||
|
||||
@@ -158,7 +158,7 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
|
||||
|
||||
// AWS Bedrock fields
|
||||
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as any,
|
||||
awsAccessKey: protoConfig.awsAccessKey,
|
||||
awsSecretKey: protoConfig.awsSecretKey,
|
||||
awsSessionToken: protoConfig.awsSessionToken,
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { GrpcHandler, StreamingCallbacks } from "../host-grpc-handler"
|
||||
import { GrpcHandler, StreamingCallbacks } from "../../../hosts/vscode/host-grpc-handler"
|
||||
|
||||
// Generic type for any protobuf service definition
|
||||
export type ProtoService = {
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ApiHandler } from "@api/index"
|
||||
|
||||
export function isClaude4ModelFamily(api: ApiHandler): boolean {
|
||||
const model = api.getModel()
|
||||
const modelId = model.id
|
||||
return modelId.includes("sonnet-4") || modelId.includes("opus-4")
|
||||
}
|
||||
@@ -24,7 +24,6 @@
|
||||
"@/*": ["src/*"],
|
||||
"@api/*": ["src/api/*"],
|
||||
"@core/*": ["src/core/*"],
|
||||
"@hosts/*": ["src/hosts/*"],
|
||||
"@integrations/*": ["src/integrations/*"],
|
||||
"@services/*": ["src/services/*"],
|
||||
"@shared/*": ["src/shared/*"],
|
||||
|
||||
@@ -1054,36 +1054,6 @@ export const ChatRowContent = ({
|
||||
return null // we should never see this message type
|
||||
case "mcp_server_response":
|
||||
return <McpResponseDisplay responseText={message.text || ""} />
|
||||
case "mcp_notification":
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "8px",
|
||||
padding: "8px 12px",
|
||||
backgroundColor: "var(--vscode-textBlockQuote-background)",
|
||||
borderRadius: "4px",
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-foreground)",
|
||||
opacity: 0.9,
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-bell"
|
||||
style={{
|
||||
marginTop: "2px",
|
||||
fontSize: "14px",
|
||||
color: "var(--vscode-notificationsInfoIcon-foreground)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<div style={{ flex: 1, wordBreak: "break-word" }}>
|
||||
<span style={{ fontWeight: 500 }}>MCP Notification: </span>
|
||||
<span className="ph-no-capture">{message.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case "text":
|
||||
return (
|
||||
<WithCopyButton ref={contentRef} onMouseUp={handleMouseUp} textToCopy={message.text}>
|
||||
|
||||
@@ -6,7 +6,7 @@ import Thumbnails from "@/components/common/Thumbnails"
|
||||
import Tooltip from "@/components/common/Tooltip"
|
||||
import ApiOptions, { normalizeApiConfiguration } from "@/components/settings/ApiOptions"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { FileServiceClient, StateServiceClient, ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { FileServiceClient, StateServiceClient } from "@/services/grpc-client"
|
||||
import {
|
||||
ContextMenuOptionType,
|
||||
getContextMenuOptions,
|
||||
@@ -34,8 +34,6 @@ import { mentionRegex, mentionRegexGlobal } from "@shared/context-mentions"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import { EmptyRequest, StringRequest } from "@shared/proto/common"
|
||||
import { FileSearchRequest, RelativePathsRequest } from "@shared/proto/file"
|
||||
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
|
||||
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
import { PlanActMode, TogglePlanActModeRequest } from "@shared/proto/state"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
|
||||
@@ -964,20 +962,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
)
|
||||
|
||||
// Separate the API config submission logic
|
||||
const submitApiConfig = useCallback(async () => {
|
||||
const submitApiConfig = useCallback(() => {
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration)
|
||||
const modelIdValidationResult = validateModelId(apiConfiguration, openRouterModels)
|
||||
|
||||
if (!apiValidationResult && !modelIdValidationResult && apiConfiguration) {
|
||||
try {
|
||||
await ModelsServiceClient.updateApiConfigurationProto(
|
||||
UpdateApiConfigurationRequest.create({
|
||||
apiConfiguration: convertApiConfigurationToProto(apiConfiguration),
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Failed to update API configuration:", error)
|
||||
}
|
||||
if (!apiValidationResult && !modelIdValidationResult) {
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
} else {
|
||||
StateServiceClient.getLatestState(EmptyRequest.create())
|
||||
.then(() => {
|
||||
|
||||
@@ -95,14 +95,7 @@ export const MAX_IMAGES_AND_FILES_PER_MESSAGE = 20
|
||||
const QUICK_WINS_HISTORY_THRESHOLD = 300
|
||||
|
||||
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
|
||||
const {
|
||||
version,
|
||||
clineMessages: messages,
|
||||
taskHistory,
|
||||
apiConfiguration,
|
||||
telemetrySetting,
|
||||
navigateToChat,
|
||||
} = useExtensionState()
|
||||
const { version, clineMessages: messages, taskHistory, apiConfiguration, telemetrySetting } = useExtensionState()
|
||||
const shouldShowQuickWins = false // !taskHistory || taskHistory.length < QUICK_WINS_HISTORY_THRESHOLD
|
||||
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
|
||||
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
|
||||
@@ -706,6 +699,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
textAreaRef.current?.focus()
|
||||
}
|
||||
break
|
||||
case "focusChatInput":
|
||||
textAreaRef.current?.focus()
|
||||
if (isHidden) {
|
||||
window.dispatchEvent(new CustomEvent("chatButtonClicked"))
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -716,22 +715,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
// Listen for local focusChatInput event
|
||||
useEffect(() => {
|
||||
const handleFocusChatInput = () => {
|
||||
if (isHidden) {
|
||||
navigateToChat()
|
||||
}
|
||||
textAreaRef.current?.focus()
|
||||
}
|
||||
|
||||
window.addEventListener("focusChatInput", handleFocusChatInput)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("focusChatInput", handleFocusChatInput)
|
||||
}
|
||||
}, [isHidden])
|
||||
|
||||
// Set up addToInput subscription
|
||||
useEffect(() => {
|
||||
const cleanup = UiServiceClient.subscribeToAddToInput(EmptyRequest.create({}), {
|
||||
|
||||
@@ -39,19 +39,66 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0">
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
<style>
|
||||
{`
|
||||
.history-preview-item {
|
||||
background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 65%, transparent);
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.history-preview-item:hover {
|
||||
background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 100%, transparent);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.history-header {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.history-header:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div
|
||||
className="flex items-center gap-2 mx-5 my-2 cursor-pointer select-none text-[var(--vscode-descriptionForeground)] hover:opacity-80 transition-all duration-200 rounded-lg px-2 py-1 hover:bg-[var(--vscode-toolbar-hoverBackground)]"
|
||||
onClick={toggleExpanded}>
|
||||
className="history-header"
|
||||
onClick={toggleExpanded}
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
margin: "10px 20px 10px 20px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<span
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"} scale-90 transition-transform duration-200`}
|
||||
/>
|
||||
<span className="codicon codicon-comment-discussion scale-90" />
|
||||
<span className="font-medium text-xs uppercase tracking-wide">Recent Tasks</span>
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
|
||||
style={{
|
||||
marginRight: "4px",
|
||||
transform: "scale(0.9)",
|
||||
}}></span>
|
||||
<span
|
||||
className="codicon codicon-comment-discussion"
|
||||
style={{
|
||||
marginRight: "4px",
|
||||
transform: "scale(0.9)",
|
||||
}}></span>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
fontSize: "0.85em",
|
||||
textTransform: "uppercase",
|
||||
}}>
|
||||
Recent Tasks
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="px-5 space-y-3">
|
||||
<div style={{ padding: "0px 20px 0 20px" }}>
|
||||
{taskHistory.filter((item) => item.ts && item.task).length > 0 ? (
|
||||
<>
|
||||
{taskHistory
|
||||
@@ -60,58 +107,61 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="relative rounded-xl p-3 cursor-pointer overflow-hidden transition-all duration-150 ease-out hover:scale-[1.02] hover:shadow-xl group hover:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_50%,transparent)] hover:border-[color-mix(in_srgb,var(--vscode-panel-border)_80%,transparent)]"
|
||||
style={{
|
||||
backgroundColor:
|
||||
"color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 30%, transparent)",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-panel-border) 50%, transparent)",
|
||||
backdropFilter: "blur(8px)",
|
||||
}}
|
||||
className="history-preview-item"
|
||||
onClick={() => handleHistorySelect(item.id)}>
|
||||
{/* Subtle gradient overlay for extra depth */}
|
||||
<div
|
||||
className="absolute inset-0 transition-all duration-150 rounded-xl opacity-0 group-hover:opacity-100"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(135deg, color-mix(in srgb, var(--vscode-button-background) 5%, transparent) 0%, color-mix(in srgb, var(--vscode-focusBorder) 3%, transparent) 100%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.isFavorited && (
|
||||
<div
|
||||
className="absolute top-3 right-3 z-20 drop-shadow-sm"
|
||||
style={{ color: "var(--vscode-button-background)" }}>
|
||||
<span className="codicon codicon-star-full" aria-label="Favorited" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="mb-2">
|
||||
<span className="text-[var(--vscode-descriptionForeground)] font-medium text-xs uppercase tracking-wider opacity-80">
|
||||
<div style={{ padding: "12px" }}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontWeight: 500,
|
||||
fontSize: "0.85em",
|
||||
textTransform: "uppercase",
|
||||
}}>
|
||||
{formatDate(item.ts)}
|
||||
</span>
|
||||
</div>
|
||||
{item.isFavorited && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "12px",
|
||||
right: "12px",
|
||||
color: "var(--vscode-button-background)",
|
||||
}}>
|
||||
<span className="codicon codicon-star-full" aria-label="Favorited" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
id={`history-preview-task-${item.id}`}
|
||||
className="text-[var(--vscode-descriptionForeground)] mb-2 line-clamp-3 whitespace-pre-wrap break-words"
|
||||
style={{ fontSize: "var(--vscode-font-size)" }}>
|
||||
className="history-preview-task"
|
||||
style={{
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
<span className="ph-no-capture">{item.task}</span>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[var(--vscode-descriptionForeground)] opacity-75 space-x-1">
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.85em",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<span>
|
||||
Tokens: ↑{formatLargeNumber(item.tokensIn || 0)} ↓
|
||||
{formatLargeNumber(item.tokensOut || 0)}
|
||||
</span>
|
||||
{!!item.cacheWrites && (
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
color: "color-mix(in srgb, var(--vscode-descriptionForeground) 40%, transparent)",
|
||||
}}>
|
||||
•
|
||||
</span>
|
||||
{" • "}
|
||||
<span>
|
||||
Cache: +{formatLargeNumber(item.cacheWrites || 0)} →{" "}
|
||||
{formatLargeNumber(item.cacheReads || 0)}
|
||||
@@ -120,12 +170,7 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
)}
|
||||
{!!item.totalCost && (
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
color: "color-mix(in srgb, var(--vscode-descriptionForeground) 40%, transparent)",
|
||||
}}>
|
||||
•
|
||||
</span>
|
||||
{" • "}
|
||||
<span>API Cost: ${item.totalCost?.toFixed(4)}</span>
|
||||
</>
|
||||
)}
|
||||
@@ -133,27 +178,35 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center justify-center pt-2">
|
||||
<button
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => showHistoryView()}
|
||||
className="cursor-pointer text-center transition-all duration-150 hover:opacity-80 flex items-center gap-1 bg-transparent border-none outline-none focus:outline-none"
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
opacity: 0.9,
|
||||
}}>
|
||||
<span className="codicon codicon-history scale-90"></span>
|
||||
<span className="font-medium">View all history</span>
|
||||
</button>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
View all history
|
||||
</div>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="text-center text-[var(--vscode-descriptionForeground)] py-4 rounded-xl"
|
||||
style={{
|
||||
textAlign: "center",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
backgroundColor: "color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 20%, transparent)",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-panel-border) 30%, transparent)",
|
||||
backdropFilter: "blur(8px)",
|
||||
padding: "10px 0",
|
||||
}}>
|
||||
No recent tasks
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ const McpMarketplaceView = () => {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
||||
const [sortBy, setSortBy] = useState<"newest" | "stars" | "name" | "downloadCount">("newest")
|
||||
const [sortBy, setSortBy] = useState<"newest" | "stars" | "name" | "downloadCount">("downloadCount")
|
||||
|
||||
const items = mcpMarketplaceCatalog?.items || []
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ import {
|
||||
xaiModels,
|
||||
} from "@shared/api"
|
||||
import { EmptyRequest, StringRequest } from "@shared/proto/common"
|
||||
import { OpenAiModelsRequest, UpdateApiConfigurationRequest } from "@shared/proto/models"
|
||||
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
import { OpenAiModelsRequest } from "@shared/proto/models"
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeCheckbox,
|
||||
@@ -119,7 +118,14 @@ const OpenRouterBalanceDisplay = ({ apiKey }: { apiKey: string }) => {
|
||||
|
||||
const SUPPORTED_THINKING_MODELS: Record<string, string[]> = {
|
||||
anthropic: ["claude-3-7-sonnet-20250219", "claude-sonnet-4-20250514", "claude-opus-4-20250514"],
|
||||
vertex: ["claude-3-7-sonnet@20250219", "claude-sonnet-4@20250514", "claude-opus-4@20250514"],
|
||||
vertex: [
|
||||
"claude-3-7-sonnet@20250219",
|
||||
"claude-sonnet-4@20250514",
|
||||
"claude-opus-4@20250514",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-flash-preview-04-17",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
],
|
||||
qwen: [
|
||||
"qwen3-235b-a22b",
|
||||
"qwen3-32b",
|
||||
@@ -132,6 +138,7 @@ const SUPPORTED_THINKING_MODELS: Record<string, string[]> = {
|
||||
"qwen-plus-latest",
|
||||
"qwen-turbo-latest",
|
||||
],
|
||||
gemini: ["gemini-2.5-flash-preview-05-20", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-06-05"],
|
||||
}
|
||||
|
||||
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
|
||||
@@ -193,19 +200,12 @@ const ApiOptions = ({
|
||||
if (saveImmediately && field === "apiProvider") {
|
||||
// Use apiConfiguration from the full extensionState context to send the most complete data
|
||||
const currentFullApiConfig = extensionState.apiConfiguration
|
||||
|
||||
// Convert to proto format and send via gRPC
|
||||
const updatedConfig = {
|
||||
...currentFullApiConfig,
|
||||
apiProvider: newValue,
|
||||
}
|
||||
const protoConfig = convertApiConfigurationToProto(updatedConfig)
|
||||
ModelsServiceClient.updateApiConfigurationProto(
|
||||
UpdateApiConfigurationRequest.create({
|
||||
apiConfiguration: protoConfig,
|
||||
}),
|
||||
).catch((error) => {
|
||||
console.error("Failed to update API configuration:", error)
|
||||
vscode.postMessage({
|
||||
type: "apiConfiguration",
|
||||
apiConfiguration: {
|
||||
...currentFullApiConfig, // Send the most complete config available
|
||||
apiProvider: newValue, // Override with the new provider
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1057,15 +1057,6 @@ const ApiOptions = ({
|
||||
</VSCodeLink>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{/* Add Thinking Budget Slider specifically for gemini-2.5-flash-preview-04-17 */}
|
||||
{selectedProvider === "gemini" && selectedModelId === "gemini-2.5-flash-preview-04-17" && (
|
||||
<ThinkingBudgetSlider
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfiguration={setApiConfiguration}
|
||||
maxBudget={selectedModelInfo.thinkingConfig?.maxBudget}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -115,6 +115,8 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
const {
|
||||
apiConfiguration,
|
||||
version,
|
||||
customInstructions,
|
||||
setCustomInstructions,
|
||||
openRouterModels,
|
||||
telemetrySetting,
|
||||
setTelemetrySetting,
|
||||
@@ -138,6 +140,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
// Store the original state to detect changes
|
||||
const originalState = useRef({
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
telemetrySetting,
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting,
|
||||
@@ -160,6 +163,10 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
if (!apiValidationResult && !modelIdValidationResult) {
|
||||
// vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
// vscode.postMessage({
|
||||
// type: "customInstructions",
|
||||
// text: customInstructions,
|
||||
// })
|
||||
// vscode.postMessage({
|
||||
// type: "telemetrySetting",
|
||||
// text: telemetrySetting,
|
||||
// })
|
||||
@@ -177,6 +184,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
await StateServiceClient.updateSettings(
|
||||
UpdateSettingsRequest.create({
|
||||
planActSeparateModelsSetting,
|
||||
customInstructionsSetting: customInstructions,
|
||||
telemetrySetting,
|
||||
enableCheckpointsSetting,
|
||||
mcpMarketplaceEnabled,
|
||||
@@ -207,6 +215,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
useEffect(() => {
|
||||
const hasChanges =
|
||||
JSON.stringify(apiConfiguration) !== JSON.stringify(originalState.current.apiConfiguration) ||
|
||||
customInstructions !== originalState.current.customInstructions ||
|
||||
telemetrySetting !== originalState.current.telemetrySetting ||
|
||||
planActSeparateModelsSetting !== originalState.current.planActSeparateModelsSetting ||
|
||||
enableCheckpointsSetting !== originalState.current.enableCheckpointsSetting ||
|
||||
@@ -219,6 +228,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
setHasUnsavedChanges(hasChanges)
|
||||
}, [
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
telemetrySetting,
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting,
|
||||
@@ -236,6 +246,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
setIsUnsavedChangesDialogOpen(true)
|
||||
pendingAction.current = () => {
|
||||
// Reset all tracked state to original values
|
||||
setCustomInstructions(originalState.current.customInstructions)
|
||||
setTelemetrySetting(originalState.current.telemetrySetting)
|
||||
setPlanActSeparateModelsSetting(originalState.current.planActSeparateModelsSetting)
|
||||
setChatSettings(originalState.current.chatSettings)
|
||||
@@ -276,6 +287,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
}, [
|
||||
hasUnsavedChanges,
|
||||
onDone,
|
||||
setCustomInstructions,
|
||||
setTelemetrySetting,
|
||||
setPlanActSeparateModelsSetting,
|
||||
setChatSettings,
|
||||
@@ -573,6 +585,24 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
architect a plan for a cheaper coding model to act on.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
className="w-full"
|
||||
resize="vertical"
|
||||
rows={4}
|
||||
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 className="font-medium">Custom Instructions</span>
|
||||
</VSCodeTextArea>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
These instructions are added to the end of the system prompt sent with every
|
||||
request.
|
||||
</p>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react"
|
||||
import { VSCodeTextField, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { StateServiceClient } from "@/services/grpc-client"
|
||||
import { Int64, Int64Request } from "@shared/proto/common"
|
||||
|
||||
export const TerminalSettingsSection: React.FC = () => {
|
||||
const { shellIntegrationTimeout, setShellIntegrationTimeout, terminalReuseEnabled, setTerminalReuseEnabled } =
|
||||
@@ -25,6 +27,18 @@ export const TerminalSettingsSection: React.FC = () => {
|
||||
|
||||
// Update local state
|
||||
setShellIntegrationTimeout(timeout)
|
||||
|
||||
// Send to extension using gRPC
|
||||
StateServiceClient.updateTerminalConnectionTimeout({
|
||||
value: timeout,
|
||||
} as Int64Request)
|
||||
.then((response: Int64) => {
|
||||
setShellIntegrationTimeout(response.value)
|
||||
setInputValue((response.value / 1000).toString())
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to update terminal connection timeout:", error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputBlur = () => {
|
||||
|
||||
@@ -5,10 +5,8 @@ import { validateApiConfiguration } from "@/utils/validate"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import ApiOptions from "@/components/settings/ApiOptions"
|
||||
import ClineLogoWhite from "@/assets/ClineLogoWhite"
|
||||
import { AccountServiceClient, ModelsServiceClient } from "@/services/grpc-client"
|
||||
import { AccountServiceClient } from "@/services/grpc-client"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { UpdateApiConfigurationRequest } from "@shared/proto/models"
|
||||
import { convertApiConfigurationToProto } from "@shared/proto-conversions/models/api-configuration-conversion"
|
||||
|
||||
const WelcomeView = memo(() => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
@@ -23,18 +21,8 @@ const WelcomeView = memo(() => {
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (apiConfiguration) {
|
||||
try {
|
||||
await ModelsServiceClient.updateApiConfigurationProto(
|
||||
UpdateApiConfigurationRequest.create({
|
||||
apiConfiguration: convertApiConfigurationToProto(apiConfiguration),
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Failed to update API configuration:", error)
|
||||
}
|
||||
}
|
||||
const handleSubmit = () => {
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -7,11 +7,10 @@ import {
|
||||
FileServiceClient,
|
||||
McpServiceClient,
|
||||
} from "../services/grpc-client"
|
||||
import { EmptyRequest, StringRequest } from "@shared/proto/common"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { UpdateSettingsRequest } from "@shared/proto/state"
|
||||
import { WebviewProviderType as WebviewProviderTypeEnum, WebviewProviderTypeRequest } from "@shared/proto/ui"
|
||||
import { convertProtoToClineMessage } from "@shared/proto-conversions/cline-message"
|
||||
import { convertProtoMcpServersToMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
@@ -27,7 +26,9 @@ import {
|
||||
requestyDefaultModelInfo,
|
||||
} from "../../../src/shared/api"
|
||||
import { McpMarketplaceCatalog, McpServer, McpViewTab } from "../../../src/shared/mcp"
|
||||
import { convertProtoMcpServersToMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { OpenRouterCompatibleModelInfo } from "@shared/proto/models"
|
||||
|
||||
interface ExtensionStateContextType extends ExtensionState {
|
||||
@@ -52,6 +53,7 @@ interface ExtensionStateContextType extends ExtensionState {
|
||||
|
||||
// Setters
|
||||
setApiConfiguration: (config: ApiConfiguration) => void
|
||||
setCustomInstructions: (value?: string) => void
|
||||
setTelemetrySetting: (value: TelemetrySetting) => void
|
||||
setShowAnnouncement: (value: boolean) => void
|
||||
setShouldShowAnnouncement: (value: boolean) => void
|
||||
@@ -229,9 +231,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
|
||||
// References to store subscription cancellation functions
|
||||
const stateSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
|
||||
// Reference for focusChatInput subscription
|
||||
const focusChatInputUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
const mcpButtonUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
const historyButtonClickedSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
const chatButtonUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
@@ -553,24 +552,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
onComplete: () => {},
|
||||
})
|
||||
|
||||
// Subscribe to focus chat input events
|
||||
const clientId = (window as any).clineClientId
|
||||
if (clientId) {
|
||||
const request = StringRequest.create({ value: clientId })
|
||||
focusChatInputUnsubscribeRef.current = UiServiceClient.subscribeToFocusChatInput(request, {
|
||||
onResponse: () => {
|
||||
// Dispatch a local DOM event within this webview only
|
||||
window.dispatchEvent(new CustomEvent("focusChatInput"))
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
console.error("Error in focusChatInput subscription:", error)
|
||||
},
|
||||
onComplete: () => {},
|
||||
})
|
||||
} else {
|
||||
console.error("Client ID not found in window object")
|
||||
}
|
||||
|
||||
// Clean up subscriptions when component unmounts
|
||||
return () => {
|
||||
if (stateSubscriptionRef.current) {
|
||||
@@ -621,10 +602,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
relinquishControlUnsubscribeRef.current()
|
||||
relinquishControlUnsubscribeRef.current = null
|
||||
}
|
||||
if (focusChatInputUnsubscribeRef.current) {
|
||||
focusChatInputUnsubscribeRef.current()
|
||||
focusChatInputUnsubscribeRef.current = null
|
||||
}
|
||||
|
||||
if (mcpServersSubscriptionRef.current) {
|
||||
mcpServersSubscriptionRef.current()
|
||||
mcpServersSubscriptionRef.current = null
|
||||
@@ -687,6 +665,11 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
...prevState,
|
||||
apiConfiguration: value,
|
||||
})),
|
||||
setCustomInstructions: (value) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
customInstructions: value,
|
||||
})),
|
||||
setTelemetrySetting: (value) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
@@ -754,6 +737,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
apiConfiguration: state.apiConfiguration
|
||||
? convertApiConfigurationToProtoApiConfiguration(state.apiConfiguration)
|
||||
: undefined,
|
||||
customInstructionsSetting: state.customInstructions,
|
||||
telemetrySetting: state.telemetrySetting,
|
||||
planActSeparateModelsSetting: state.planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting: state.enableCheckpointsSetting,
|
||||
|
||||
Reference in New Issue
Block a user