mirror of
https://github.com/cline/cline.git
synced 2026-09-17 17:45:33 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21da404f5b |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
added focusBorder coloring to Approve / Reject buttons
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add support for Gemini 2.5 Pro and Flash to SAP AI Core Provider
|
||||
@@ -1,75 +0,0 @@
|
||||
# Git Diff Analysis Workflow
|
||||
|
||||
## Objective
|
||||
Analyze the current branch's changes against main to provide informed insights and context for development decisions.
|
||||
|
||||
## Step 1: Gather Git Information
|
||||
<important>Do not return any text or conversation other than what is necessary to run these commands</important>
|
||||
|
||||
**First, check the expected output size:**
|
||||
```shell
|
||||
(git branch --show-current && echo "=== STATUS ===" && git status --porcelain | cat && echo "=== COMMIT MESSAGES ===" && git log main..HEAD --oneline | cat && echo "=== CHANGED FILES ===" && git diff main --name-only | cat && echo "=== FULL DIFF ===" && git diff main | cat) | wc -l
|
||||
```
|
||||
|
||||
**If the expected line count is greater than 500 lines, use the file-based approach:**
|
||||
```shell
|
||||
git branch --show-current > cline-git-analysis.temp && echo "=== STATUS ===" >> cline-git-analysis.temp && git status --porcelain >> cline-git-analysis.temp && echo "=== COMMIT MESSAGES ===" >> cline-git-analysis.temp && git log main..HEAD --oneline >> cline-git-analysis.temp && echo "=== CHANGED FILES ===" >> cline-git-analysis.temp && git diff main --name-only >> cline-git-analysis.temp && echo "=== FULL DIFF ===" >> cline-git-analysis.temp && git diff main >> cline-git-analysis.temp
|
||||
```
|
||||
|
||||
Then, read the file using the read_file tool. After you have read the file but before you proceed with subsequent steps, delete it:
|
||||
```shell
|
||||
rm cline-git-analysis.temp
|
||||
```
|
||||
|
||||
**If the expected line count is 500 lines or fewer, use the direct approach:**
|
||||
```shell
|
||||
git branch --show-current && echo "=== STATUS ===" && git status --porcelain | cat && echo "=== COMMIT MESSAGES ===" && git log main..HEAD --oneline | cat && echo "=== CHANGED FILES ===" && git diff main --name-only | cat && echo "=== FULL DIFF ===" && git diff main | cat
|
||||
```
|
||||
|
||||
<important>If using the direct approach, pipe outputs through `cat` to avoid interactive terminals. If the user's shell is not bash/zsh, adjust the command and chaining
|
||||
syntax accordingly.</important>
|
||||
|
||||
## Step 2: Silent, Structured Analysis Phase
|
||||
- Analyze all git output without providing commentary or narration
|
||||
- Read the full diff to understand the scope and nature of changes
|
||||
- Identify patterns, architectural modifications, or potential impacts
|
||||
- Use `read_file` to examine any related files providing additional context on the changes you have observed
|
||||
|
||||
## Step 3: Context Gathering
|
||||
- Analyze related code without providing commentary or narration
|
||||
- Read relevant related source files if needed for complete understanding
|
||||
- Check dependencies, imports, or cross-references spanning the changes
|
||||
- Understand the broader codebase context around modifications
|
||||
- This additional context gathering should include related backend code, as well as related ui/frontend code
|
||||
- You will typically need to analyze at least several files, potentially many, in order to fully complete this step
|
||||
- You should not continue reading additional context if you have exhausted more than 60% of your available context window
|
||||
- If you have exhausted less than 40% of your context window, you should continue reviewing additional context
|
||||
|
||||
## Step 4: Ready for User Interaction
|
||||
**Only after completing the full analysis:**
|
||||
- Engage with the user based on comprehensive understanding
|
||||
- Provide insights about specific modifications and their impacts
|
||||
- If you are certain they exist, note potential breaking changes or compatibility issues
|
||||
- Answer questions with informed context from the complete change set and context gathering
|
||||
- If the user has not provided a question, or the question is insufficient to provide a quality response, ask brief (one sentence) clarifying questions.
|
||||
- Only offer recommendations if they are applicable to the user's request and relevant to the changes that you have observed
|
||||
|
||||
## Key Rules
|
||||
- **No prose or conversation during git research phase**
|
||||
- **No prose or conversation during context gathering phase**
|
||||
- **Complete all analysis before any user interaction**
|
||||
- **Use gathered information for all subsequent questions and insights**
|
||||
- **Focus on understanding the complete picture before discussing**
|
||||
|
||||
## Optional: Additional Analysis Commands
|
||||
For deeper investigation when needed:
|
||||
|
||||
```shell
|
||||
# Detailed commit history with author info
|
||||
git log main..HEAD --format="%h %s (%an)" | cat
|
||||
|
||||
# Change statistics
|
||||
git diff main --stat | cat
|
||||
|
||||
# Specific file type changes
|
||||
git diff main --name-only | grep -E '\.(ts|js|tsx|jsx|py|md)$' | cat
|
||||
@@ -22,7 +22,6 @@
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"eslint-rules/no-protobuf-object-literals": "error",
|
||||
"eslint-rules/no-grpc-client-object-literals": "error",
|
||||
"eslint-rules/no-direct-vscode-api": "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
|
||||
Vendored
+3
-4
@@ -23,20 +23,19 @@
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--user-data-dir=${workspaceFolder}/dist/tmp/user",
|
||||
"--profile-temp",
|
||||
"--sync=off",
|
||||
"--sync",
|
||||
"off",
|
||||
"--disable-extensions",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "clean-tmp-user",
|
||||
"preLaunchTask": "clean-sandbox",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"postDebugTask": "stop",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"TEMP_PROFILE": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
|
||||
Vendored
+2
-2
@@ -233,10 +233,10 @@
|
||||
"type": "shell"
|
||||
},
|
||||
{
|
||||
"label": "clean-tmp-user",
|
||||
"label": "clean-sandbox",
|
||||
"type": "shell",
|
||||
"dependsOn": ["watch"],
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
"command": "rm -rf .vscode-dev"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
+2
-7
@@ -2,24 +2,20 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/**
|
||||
dist-standalone/**
|
||||
node_modules/**
|
||||
src/**
|
||||
standalone/**
|
||||
.gitignore
|
||||
.yarnrc
|
||||
esbuild.js
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/.vscode-test.*
|
||||
eslint-rules/**
|
||||
.github/**
|
||||
.husky/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
demo.gif
|
||||
.nvmrc
|
||||
.gitattributes
|
||||
.prettierignore
|
||||
@@ -36,7 +32,6 @@ webview-ui/node_modules/**
|
||||
|
||||
# Ignore docs
|
||||
docs/**
|
||||
old_docs/**
|
||||
|
||||
# Fix issue where codicons don't get packaged (https://github.com/microsoft/vscode-extension-samples/issues/692)
|
||||
!node_modules/@vscode/codicons/dist/codicon.css
|
||||
|
||||
@@ -1,54 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.18.13]
|
||||
|
||||
- Fix authentication issue where Cline accounts users would keep getting logged out or seeing 'Unexpected API response' errors
|
||||
|
||||
## [3.18.12]
|
||||
|
||||
- Fix flaky organization switching behavior in Cline provider that caused UI inconsistencies and double loading
|
||||
- Fix insufficient credits error display to properly show error messages when account balance is too low
|
||||
- Improve credit balance validation and error handling for Cline provider requests
|
||||
|
||||
## [3.18.11]
|
||||
|
||||
- Fix authentication issues with Cline provider by ensuring the client always uses the latest auth token
|
||||
|
||||
## [3.18.10]
|
||||
|
||||
- Update recommended fast & cheap model to Grok 4 in OpenRouter model picker
|
||||
- Fix Gemini 2.5 Pro thinking budget slider and add support for Gemini 2.5 Flash Lite Preview model (Thanks @arafatkatze!)
|
||||
|
||||
## [3.18.9]
|
||||
|
||||
- Fix streaming reliability issues with Cline provider that could cause connection problems during long conversations
|
||||
- Fix authentication error handling for Cline provider to show clearer error messages when not signed in and prevent recursive failed requests
|
||||
- Remove incorrect pricing display for SAP AI Core provider since it uses non-USD "Capacity Units" that cannot be directly converted (Thanks @ncryptedV1!)
|
||||
|
||||
## [3.18.8]
|
||||
|
||||
- Update pricing for Grok 3 model because the promotion ended
|
||||
|
||||
## [3.18.7]
|
||||
|
||||
- Remove promotional "free" messaging for Grok 3 model in UI
|
||||
|
||||
## [3.18.6]
|
||||
|
||||
- Update request header to include `"ai-client-type": "Cline"` to SAP Api Provider
|
||||
- Add organization organization accounts
|
||||
|
||||
## [3.18.5]
|
||||
|
||||
- Fix Plan/Act mode persistence across sessions and multi-workspace conflicts
|
||||
- Improve provider switching performance by 18x (from 550ms to 30ms) with batched storage operations
|
||||
- Improve SAP AI Core provider model organization and fix exception handling (Thanks @schardosin!)
|
||||
|
||||
## [3.18.4]
|
||||
|
||||
- Add support for Gemini 2.5 Pro and Flash to SAP AI Core Provider
|
||||
- Fix logging in with Cline account not getting past welcome screen
|
||||
|
||||
## [3.18.3]
|
||||
|
||||
- Improve Cerebras Qwen model performance by removing thinking tokens from model input (Thanks @kevint-cerebras!)
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
const { RuleTester: DirectApiRuleTester } = require("eslint")
|
||||
const noDirectVscodeApiRule = require("../no-direct-vscode-api")
|
||||
|
||||
const directApiRuleTester = new DirectApiRuleTester({
|
||||
parser: require.resolve("@typescript-eslint/parser"),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
directApiRuleTester.run("no-direct-vscode-api", noDirectVscodeApiRule, {
|
||||
valid: [
|
||||
// Should allow vscode.postMessage in grpc-client-base.ts
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request", data: {} })`,
|
||||
filename: "grpc-client-base.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request_cancel" })`,
|
||||
filename: "/path/to/grpc-client-base.ts",
|
||||
},
|
||||
// Should allow in exception directories
|
||||
{
|
||||
code: `vscode.workspace.workspaceFolders`,
|
||||
filename: "/src/hosts/vscode/host-bridge.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.workspace.fs.stat(uri)`,
|
||||
filename: "/standalone/runtime-files/helpers.ts",
|
||||
},
|
||||
// Should allow other vscode API calls
|
||||
{
|
||||
code: `vscode.window.showInformationMessage("Hello")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow postMessage calls on other objects
|
||||
{
|
||||
code: `window.postMessage({ type: "test" }, "*")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow variables named vscode but not calling postMessage
|
||||
{
|
||||
code: `const vscode = { other: "method" }; vscode.other()`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Should disallow vscode.postMessage in regular files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "test", data: {} })`,
|
||||
filename: "test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow vscode.postMessage in components
|
||||
{
|
||||
code: `vscode.postMessage({ type: "apiConfiguration", apiConfiguration })`,
|
||||
filename: "ApiOptions.tsx",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow vscode.postMessage in test files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "newTask", text: message.text })`,
|
||||
filename: "test.test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow property access for disallowed APIs
|
||||
{
|
||||
code: `const folders = vscode.workspace.workspaceFolders;`,
|
||||
filename: "workspace.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow method calls for disallowed APIs
|
||||
{
|
||||
code: `const relativePath = vscode.workspace.asRelativePath(filePath);`,
|
||||
filename: "path-utils.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow nested property access
|
||||
{
|
||||
code: `const stats = await vscode.workspace.fs.stat(uri);`,
|
||||
filename: "file-utils.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow getting a workspace folder
|
||||
{
|
||||
code: `const folder = vscode.workspace.getWorkspaceFolder(uri);`,
|
||||
filename: "path-helper.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
const { RuleTester: VscodeRuleTester } = require("eslint")
|
||||
const vscodePostmessageRule = require("../no-vscode-postmessage")
|
||||
|
||||
const vscodeRuleTester = new VscodeRuleTester({
|
||||
parser: require.resolve("@typescript-eslint/parser"),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
vscodeRuleTester.run("no-vscode-postmessage", vscodePostmessageRule, {
|
||||
valid: [
|
||||
// Should allow vscode.postMessage in grpc-client-base.ts
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request", data: {} })`,
|
||||
filename: "grpc-client-base.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request_cancel" })`,
|
||||
filename: "/path/to/grpc-client-base.ts",
|
||||
},
|
||||
// Should allow other vscode API calls
|
||||
{
|
||||
code: `vscode.window.showInformationMessage("Hello")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow postMessage calls on other objects
|
||||
{
|
||||
code: `window.postMessage({ type: "test" }, "*")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow variables named vscode but not calling postMessage
|
||||
{
|
||||
code: `const vscode = { other: "method" }; vscode.other()`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Should ban vscode.postMessage in regular files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "test", data: {} })`,
|
||||
filename: "test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should ban vscode.postMessage in components
|
||||
{
|
||||
code: `vscode.postMessage({ type: "apiConfiguration", apiConfiguration })`,
|
||||
filename: "ApiOptions.tsx",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should ban vscode.postMessage in test files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "newTask", text: message.text })`,
|
||||
filename: "test.test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1,13 +1,13 @@
|
||||
// eslint-rules/index.js
|
||||
const noProtobufObjectLiterals = require("./no-protobuf-object-literals")
|
||||
const noGrpcClientObjectLiterals = require("./no-grpc-client-object-literals")
|
||||
const noDirectVscodeApi = require("./no-direct-vscode-api")
|
||||
const noVscodePostmessage = require("./no-vscode-postmessage")
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"no-protobuf-object-literals": noProtobufObjectLiterals,
|
||||
"no-grpc-client-object-literals": noGrpcClientObjectLiterals,
|
||||
"no-direct-vscode-api": noDirectVscodeApi,
|
||||
"no-vscode-postmessage": noVscodePostmessage,
|
||||
},
|
||||
configs: {
|
||||
recommended: {
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
rules: {
|
||||
"local/no-protobuf-object-literals": "error",
|
||||
"local/no-grpc-client-object-literals": "error",
|
||||
"local/no-direct-vscode-api": "warn",
|
||||
"local/no-vscode-postmessage": "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
const { ESLintUtils } = require("@typescript-eslint/utils")
|
||||
const path = require("path")
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://cline.bot/eslint-rules/${name}`)
|
||||
|
||||
// Configuration of disallowed VSCode APIs and their recommended alternatives
|
||||
const disallowedApis = {
|
||||
"vscode.postMessage": {
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
"vscode.workspace.fs.stat": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.workspaceFolders": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.asRelativePath": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
"vscode.workspace.getWorkspaceFolder": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = createRule({
|
||||
name: "no-direct-vscode-api",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Disallow direct VSCode API usage in favor of Cline's abstraction layers, except in src/hosts/vscode and standalone/runtime-files directories",
|
||||
recommended: "error",
|
||||
},
|
||||
messages: {
|
||||
useGrpcClient:
|
||||
"Use gRPC service clients instead of vscode.postMessage().\n" +
|
||||
"Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).\n" +
|
||||
"Found: {{code}}",
|
||||
useFsUtils:
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs.stat.\n" +
|
||||
"Example: import { isDirectory } from '@/utils/fs' or use the file system methods from the host bridge provider.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridge:
|
||||
"Use getHostBridgeProvider().workspaceClient.getWorkspacePaths({}) instead of vscode.workspace.workspaceFolders.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of direct VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
const filename = context.filename
|
||||
const isGrpcClientBase = path.basename(filename) === "grpc-client-base.ts"
|
||||
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
const isExceptionDirectory = filename.includes("/src/hosts/vscode/") || filename.includes("/standalone/runtime-files/")
|
||||
|
||||
// Pattern for checking memberExpressions like vscode.workspace.fs.stat
|
||||
function checkMemberExpression(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
// For handling nested properties like vscode.workspace.fs.stat
|
||||
function getFullPropertyPath(node) {
|
||||
if (node.type !== "MemberExpression") {
|
||||
return node.name || ""
|
||||
}
|
||||
|
||||
const objectPart = getFullPropertyPath(node.object)
|
||||
const propertyPart = node.property.name || ""
|
||||
|
||||
return objectPart ? `${objectPart}.${propertyPart}` : propertyPart
|
||||
}
|
||||
|
||||
// Check if the expression matches one of our disallowed patterns
|
||||
if (node.object && node.object.type === "Identifier" && node.object.name === "vscode") {
|
||||
const fullPath = `vscode.${node.property.name}`
|
||||
checkDisallowedApi(fullPath, node)
|
||||
}
|
||||
// Handle nested expressions like vscode.workspace.fs.stat
|
||||
else if (node.object && node.object.type === "MemberExpression") {
|
||||
const fullPath = getFullPropertyPath(node)
|
||||
|
||||
// Only proceed if it starts with vscode
|
||||
if (fullPath.startsWith("vscode.")) {
|
||||
checkDisallowedApi(fullPath, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an expression matches a disallowed API and report if it does
|
||||
function checkDisallowedApi(expressionPath, node) {
|
||||
// Check exact matches
|
||||
if (disallowedApis[expressionPath]) {
|
||||
reportViolation(expressionPath, node)
|
||||
return
|
||||
}
|
||||
|
||||
// Check prefix matches (for nested properties)
|
||||
for (const disallowedApi in disallowedApis) {
|
||||
// For direct property access like vscode.workspace.workspaceFolders
|
||||
if (expressionPath === disallowedApi) {
|
||||
reportViolation(disallowedApi, node)
|
||||
return
|
||||
}
|
||||
|
||||
// For method calls like vscode.workspace.asRelativePath(...)
|
||||
if (expressionPath.startsWith(`${disallowedApi}.`) || expressionPath.startsWith(`${disallowedApi}(`)) {
|
||||
reportViolation(disallowedApi, node)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report a violation with the appropriate message
|
||||
function reportViolation(disallowedApi, node) {
|
||||
const sourceCode = context.sourceCode
|
||||
const config = disallowedApis[disallowedApi]
|
||||
|
||||
// For method calls, get the whole call expression
|
||||
let reportNode = node
|
||||
let parentNode = sourceCode.getAncestors(node).pop()
|
||||
if (parentNode && parentNode.type === "CallExpression" && parentNode.callee === node) {
|
||||
reportNode = parentNode
|
||||
}
|
||||
|
||||
const callText = sourceCode.getText(reportNode).trim()
|
||||
|
||||
context.report({
|
||||
node: reportNode,
|
||||
messageId: config.messageId,
|
||||
data: {
|
||||
code: callText,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
// Detect basic member expressions (e.g., vscode.postMessage)
|
||||
MemberExpression(node) {
|
||||
checkMemberExpression(node)
|
||||
},
|
||||
|
||||
// Detect property access through destructuring
|
||||
VariableDeclarator(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
// Destructuring pattern checks removed as developers don't use the API this way
|
||||
// They always use direct imports: import * as vscode from "vscode" and direct access: vscode.thing.foo
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
const { ESLintUtils } = require("@typescript-eslint/utils")
|
||||
const path = require("path")
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://cline.bot/eslint-rules/${name}`)
|
||||
|
||||
module.exports = createRule({
|
||||
name: "no-vscode-postmessage",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Ban vscode.postMessage() calls in favor of gRPC service clients, except in grpc-client-base.ts",
|
||||
recommended: "error",
|
||||
},
|
||||
messages: {
|
||||
useGrpcClient:
|
||||
"Use gRPC service clients instead of vscode.postMessage().\n" +
|
||||
"Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).\n" +
|
||||
"Found: {{code}}",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Check if current file is grpc-client-base.ts (exception case)
|
||||
const filename = context.filename
|
||||
const isGrpcClientBase = path.basename(filename) === "grpc-client-base.ts"
|
||||
|
||||
return {
|
||||
// Detect vscode.postMessage calls
|
||||
"CallExpression[callee.type='MemberExpression']"(node) {
|
||||
// Skip if this is grpc-client-base.ts
|
||||
if (isGrpcClientBase) {
|
||||
return
|
||||
}
|
||||
|
||||
const callee = node.callee
|
||||
|
||||
// Check for vscode.postMessage pattern
|
||||
if (
|
||||
callee.object &&
|
||||
callee.object.type === "Identifier" &&
|
||||
callee.object.name === "vscode" &&
|
||||
callee.property &&
|
||||
callee.property.name === "postMessage"
|
||||
) {
|
||||
const sourceCode = context.sourceCode
|
||||
const callText = sourceCode.getText(node).trim()
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useGrpcClient",
|
||||
data: {
|
||||
code: callText,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
Generated
+436
-198
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.18.12",
|
||||
"version": "3.18.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.18.12",
|
||||
"version": "3.18.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
@@ -51,7 +51,6 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.8.0",
|
||||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"nice-grpc": "^2.1.12",
|
||||
@@ -105,7 +104,6 @@
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
"minimatch": "^3.0.3",
|
||||
"mintlify": "^4.0.515",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
@@ -2067,6 +2065,17 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@@ -2076,6 +2085,19 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
|
||||
@@ -2923,6 +2945,30 @@
|
||||
"node": ">=10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
@@ -4003,6 +4049,17 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
@@ -4075,6 +4132,19 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -7403,6 +7473,16 @@
|
||||
"ajv": "^8.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@@ -7418,6 +7498,18 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -7711,21 +7803,6 @@
|
||||
"path-browserify": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ts-morph/common/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
@@ -8237,22 +8314,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@@ -8454,22 +8515,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@@ -8595,22 +8640,6 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||
@@ -8704,22 +8733,6 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/test-cli/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/test-cli/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@@ -12037,6 +12050,17 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -12076,6 +12100,19 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
@@ -12417,6 +12454,16 @@
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exceljs/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/exceljs/node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
@@ -12490,6 +12537,18 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/exceljs/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/exceljs/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -13197,6 +13256,17 @@
|
||||
"node": "^10.12.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -13219,6 +13289,19 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache/node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -13440,6 +13523,16 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fstream/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fstream/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -13461,6 +13554,18 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/fstream/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/fstream/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
@@ -13813,21 +13918,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "13.24.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
|
||||
@@ -15786,15 +15876,6 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.22",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
|
||||
@@ -17846,25 +17927,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch/node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
@@ -18558,6 +18632,17 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@@ -18627,6 +18712,19 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
@@ -22302,6 +22400,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -22324,6 +22433,19 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/text-decoder": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz",
|
||||
@@ -25587,11 +25709,30 @@
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -26326,6 +26467,27 @@
|
||||
"@humanwhocodes/object-schema": "^2.0.2",
|
||||
"debug": "^4.3.1",
|
||||
"minimatch": "^3.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/module-importer": {
|
||||
@@ -26952,6 +27114,16 @@
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
@@ -26999,6 +27171,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -29527,6 +29708,16 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@@ -29539,6 +29730,15 @@
|
||||
"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -29789,16 +29989,6 @@
|
||||
"fast-glob": "^3.3.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"path-browserify": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
@@ -30198,15 +30388,6 @@
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@@ -30319,15 +30500,6 @@
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@@ -30398,15 +30570,6 @@
|
||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"ts-api-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||
@@ -30470,15 +30633,6 @@
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@@ -32671,6 +32825,16 @@
|
||||
"text-table": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -32696,6 +32860,15 @@
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
@@ -32972,6 +33145,15 @@
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
@@ -33014,6 +33196,14 @@
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -33499,6 +33689,16 @@
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -33513,6 +33713,15 @@
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -33658,6 +33867,15 @@
|
||||
"rimraf": "2"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -33671,6 +33889,14 @@
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
@@ -33899,16 +34125,6 @@
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
@@ -35238,11 +35454,6 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="
|
||||
},
|
||||
"katex": {
|
||||
"version": "0.16.22",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
|
||||
@@ -36617,22 +36828,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
}
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
@@ -37106,6 +37306,16 @@
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@@ -37157,6 +37367,15 @@
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
@@ -39712,6 +39931,16 @@
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -39725,6 +39954,15 @@
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+1
-3
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.18.13",
|
||||
"version": "3.18.3",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -392,7 +392,6 @@
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
"minimatch": "^3.0.3",
|
||||
"mintlify": "^4.0.515",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
@@ -449,7 +448,6 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.8.0",
|
||||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"nice-grpc": "^2.1.12",
|
||||
|
||||
+43
-96
@@ -1,129 +1,76 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Service for account-related operations
|
||||
service AccountService {
|
||||
// Handles the user clicking the login link in the UI.
|
||||
// Generates a secure nonce for state validation, stores it in secrets,
|
||||
// and opens the authentication URL in the external browser.
|
||||
rpc accountLoginClicked(EmptyRequest) returns (String);
|
||||
// Handles the user clicking the login link in the UI.
|
||||
// Generates a secure nonce for state validation, stores it in secrets,
|
||||
// and opens the authentication URL in the external browser.
|
||||
rpc accountLoginClicked(EmptyRequest) returns (String);
|
||||
|
||||
// Handles the user clicking the logout button in the UI.
|
||||
// Clears API keys and user state.
|
||||
rpc accountLogoutClicked(EmptyRequest) returns (Empty);
|
||||
// Handles the user clicking the logout button in the UI.
|
||||
// Clears API keys and user state.
|
||||
rpc accountLogoutClicked(EmptyRequest) returns (Empty);
|
||||
|
||||
// Subscribe to auth status update events (when authentication state changes)
|
||||
rpc subscribeToAuthStatusUpdate(EmptyRequest)
|
||||
returns (stream AuthState);
|
||||
// Subscribe to auth callback events (when authentication tokens are received)
|
||||
rpc subscribeToAuthCallback(EmptyRequest) returns (stream String);
|
||||
|
||||
// Handles authentication state changes from the Firebase context.
|
||||
// Updates the user info in global state and returns the updated value.
|
||||
rpc authStateChanged(AuthStateChangedRequest)
|
||||
returns (AuthState);
|
||||
// Handles authentication state changes from the Firebase context.
|
||||
// Updates the user info in global state and returns the updated value.
|
||||
rpc authStateChanged(AuthStateChangedRequest) returns (AuthStateChanged);
|
||||
|
||||
// Fetches all user credits data
|
||||
// (balance, usage transactions, payment transactions)
|
||||
rpc getUserCredits(EmptyRequest) returns (UserCreditsData);
|
||||
|
||||
rpc getOrganizationCredits(GetOrganizationCreditsRequest) returns (OrganizationCreditsData);
|
||||
|
||||
// Fetches all user organizations data
|
||||
// Returns a list of UserOrganization objects
|
||||
rpc getUserOrganizations(EmptyRequest) returns (UserOrganizationsResponse);
|
||||
|
||||
rpc setUserOrganization(UserOrganizationUpdateRequest) returns (Empty);
|
||||
// Fetches all user credits data (balance, usage transactions, payment transactions)
|
||||
rpc fetchUserCreditsData(EmptyRequest) returns (UserCreditsData);
|
||||
}
|
||||
|
||||
message AuthStateChangedRequest {
|
||||
Metadata metadata = 1;
|
||||
UserInfo user = 2;
|
||||
Metadata metadata = 1;
|
||||
UserInfo user = 2;
|
||||
}
|
||||
|
||||
message AuthState {
|
||||
optional UserInfo user = 1;
|
||||
message AuthStateChanged {
|
||||
optional UserInfo user = 1;
|
||||
}
|
||||
|
||||
// User's information
|
||||
message UserInfo {
|
||||
string uid = 1;
|
||||
optional string display_name = 2;
|
||||
optional string email = 3;
|
||||
optional string photo_url = 4;
|
||||
}
|
||||
|
||||
message UserOrganization {
|
||||
bool active = 1;
|
||||
string member_id = 2;
|
||||
string name = 3;
|
||||
string organization_id = 4;
|
||||
repeated string roles = 5; // ["admin", "member", "owner"]
|
||||
}
|
||||
|
||||
message UserOrganizationsResponse {
|
||||
repeated UserOrganization organizations = 1;
|
||||
}
|
||||
|
||||
message UserOrganizationUpdateRequest {
|
||||
optional string organization_id = 1;
|
||||
optional string display_name = 1;
|
||||
optional string email = 2;
|
||||
optional string photo_url = 3;
|
||||
}
|
||||
|
||||
// Response containing all user credits data
|
||||
message UserCreditsData {
|
||||
UserCreditsBalance balance = 1;
|
||||
repeated UsageTransaction usage_transactions = 2;
|
||||
repeated PaymentTransaction payment_transactions = 3;
|
||||
}
|
||||
|
||||
message GetOrganizationCreditsRequest {
|
||||
string organization_id = 1;
|
||||
}
|
||||
|
||||
message OrganizationCreditsData {
|
||||
UserCreditsBalance balance = 1;
|
||||
string organization_id = 2;
|
||||
repeated OrganizationUsageTransaction usage_transactions = 3;
|
||||
UserCreditsBalance balance = 1;
|
||||
repeated UsageTransaction usage_transactions = 2;
|
||||
repeated PaymentTransaction payment_transactions = 3;
|
||||
}
|
||||
|
||||
// User's current credit balance
|
||||
message UserCreditsBalance {
|
||||
double current_balance = 1;
|
||||
double current_balance = 1;
|
||||
}
|
||||
|
||||
// Usage transaction record
|
||||
message UsageTransaction {
|
||||
string ai_inference_provider_name = 1;
|
||||
string ai_model_name = 2;
|
||||
string ai_model_type_name = 3;
|
||||
int32 completion_tokens = 4;
|
||||
double cost_usd = 5;
|
||||
string created_at = 6;
|
||||
double credits_used = 7;
|
||||
string generation_id = 8;
|
||||
string organization_id = 9;
|
||||
int32 prompt_tokens = 10;
|
||||
int32 total_tokens = 11;
|
||||
string user_id = 12;
|
||||
string spent_at = 1;
|
||||
string creator_id = 2;
|
||||
double credits = 3;
|
||||
string model_provider = 4;
|
||||
string model = 5;
|
||||
int32 prompt_tokens = 6;
|
||||
int32 completion_tokens = 7;
|
||||
int32 total_tokens = 8;
|
||||
}
|
||||
|
||||
// Payment transaction record
|
||||
message PaymentTransaction {
|
||||
string paid_at = 1;
|
||||
string creator_id = 2;
|
||||
int32 amount_cents = 3;
|
||||
double credits = 4;
|
||||
string paid_at = 1;
|
||||
string creator_id = 2;
|
||||
int32 amount_cents = 3;
|
||||
double credits = 4;
|
||||
}
|
||||
|
||||
message OrganizationUsageTransaction {
|
||||
string ai_inference_provider_name = 1;
|
||||
string ai_model_name = 2;
|
||||
string ai_model_type_name = 3;
|
||||
int32 completion_tokens = 4;
|
||||
double cost_usd = 5;
|
||||
string created_at = 6;
|
||||
double credits_used = 7;
|
||||
string generation_id = 8;
|
||||
string organization_id = 9;
|
||||
int32 prompt_tokens = 10;
|
||||
int32 total_tokens = 11;
|
||||
string user_id = 12;
|
||||
}
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service BrowserService {
|
||||
rpc getBrowserConnectionInfo(EmptyRequest) returns (BrowserConnectionInfo);
|
||||
rpc testBrowserConnection(StringRequest) returns (BrowserConnection);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service CheckpointsService {
|
||||
rpc checkpointDiff(Int64Request) returns (Empty);
|
||||
rpc checkpointRestore(CheckpointRestoreRequest) returns (Empty);
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Service for file-related operations
|
||||
service FileService {
|
||||
// Copies text to clipboard
|
||||
|
||||
@@ -13,7 +13,4 @@ service EnvService {
|
||||
|
||||
// Reads text from the system clipboard.
|
||||
rpc clipboardReadText(cline.EmptyRequest) returns (cline.String);
|
||||
|
||||
// Opens a URL in the user's default browser or application.
|
||||
rpc openExternal(cline.StringRequest) returns (cline.Empty);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package host;
|
||||
option java_package = "bot.cline.host.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// UriService provides methods for working with URIs in the IDE
|
||||
service UriService {
|
||||
// Create a new file URI from a file path
|
||||
rpc file(cline.StringRequest) returns (Uri);
|
||||
|
||||
// Join a URI with additional path segments
|
||||
rpc joinPath(JoinPathRequest) returns (Uri);
|
||||
|
||||
// Parse a string URI into a Uri object
|
||||
rpc parse(cline.StringRequest) returns (Uri);
|
||||
}
|
||||
|
||||
// Uri represents a URI in the IDE
|
||||
message Uri {
|
||||
string scheme = 1;
|
||||
string authority = 2;
|
||||
string path = 3;
|
||||
string query = 4;
|
||||
string fragment = 5;
|
||||
string fs_path = 6;
|
||||
}
|
||||
|
||||
// Request for joining path segments to a URI
|
||||
message JoinPathRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
Uri base = 2;
|
||||
repeated string path_segments = 3;
|
||||
}
|
||||
@@ -11,7 +11,6 @@ service WindowService {
|
||||
// Opens a text document in the editor and returns editor information.
|
||||
rpc showTextDocument(ShowTextDocumentRequest) returns (TextEditorInfo);
|
||||
rpc showOpenDialogue(ShowOpenDialogueRequest) returns (SelectedResources);
|
||||
rpc showMessage(ShowMessageRequest) returns (SelectedResponse);
|
||||
}
|
||||
|
||||
message ShowTextDocumentRequest {
|
||||
@@ -47,27 +46,3 @@ message ShowOpenDialogueFilterOption {
|
||||
message SelectedResources {
|
||||
repeated string paths = 1;
|
||||
}
|
||||
|
||||
enum ShowMessageType {
|
||||
ERROR = 0;
|
||||
INFORMATION = 1;
|
||||
WARNING = 2;
|
||||
}
|
||||
|
||||
message ShowMessageRequest {
|
||||
cline.Metadata metadata = 1;
|
||||
ShowMessageType type = 2;
|
||||
string message = 3;
|
||||
optional ShowMessageRequestOptions options = 4;
|
||||
}
|
||||
|
||||
message ShowMessageRequestOptions {
|
||||
repeated string items = 1;
|
||||
optional bool modal = 2;
|
||||
optional string detail = 3;
|
||||
|
||||
}
|
||||
|
||||
message SelectedResponse {
|
||||
optional string selected_option = 1;
|
||||
}
|
||||
+3
-14
@@ -1,15 +1,16 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service McpService {
|
||||
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
|
||||
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
|
||||
rpc addRemoteMcpServer(AddRemoteMcpServerRequest) returns (McpServers);
|
||||
rpc downloadMcp(StringRequest) returns (McpDownloadResponse);
|
||||
rpc downloadMcp(StringRequest) returns (Empty);
|
||||
rpc restartMcpServer(StringRequest) returns (McpServers);
|
||||
rpc deleteMcpServer(StringRequest) returns (McpServers);
|
||||
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
|
||||
@@ -118,15 +119,3 @@ message McpMarketplaceItem {
|
||||
message McpMarketplaceCatalog {
|
||||
repeated McpMarketplaceItem items = 1;
|
||||
}
|
||||
|
||||
message McpDownloadResponse {
|
||||
string mcp_id = 1;
|
||||
string github_url = 2;
|
||||
string name = 3;
|
||||
string author = 4;
|
||||
string description = 5;
|
||||
string readme_content = 6;
|
||||
string llms_installation_content = 7;
|
||||
bool requires_api_key = 8;
|
||||
optional string error = 9;
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Service for model-related operations
|
||||
service ModelsService {
|
||||
// Fetches available models from Ollama
|
||||
@@ -164,7 +165,7 @@ message ModelsApiConfiguration {
|
||||
// From ApiHandlerOptions (excluding onRetryAttempt function)
|
||||
optional string api_model_id = 1;
|
||||
optional string api_key = 2;
|
||||
optional string cline_account_id = 3;
|
||||
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;
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// SlashService provides methods for managing slash
|
||||
service SlashService {
|
||||
// Sends button click message
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
syntax = "proto3";
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service StateService {
|
||||
rpc getLatestState(EmptyRequest) returns (State);
|
||||
rpc updateTerminalConnectionTimeout(Int64Request) returns (Int64);
|
||||
@@ -125,7 +126,7 @@ message ApiConfiguration {
|
||||
optional string api_base_url = 4;
|
||||
|
||||
// Provider-specific API keys
|
||||
optional string cline_account_id = 5;
|
||||
optional string cline_api_key = 5;
|
||||
optional string openrouter_api_key = 6;
|
||||
optional string anthropic_base_url = 7;
|
||||
optional string openai_api_key = 8;
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service TaskService {
|
||||
// Cancels the currently running task
|
||||
rpc cancelTask(EmptyRequest) returns (Empty);
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// Enum for webview provider types
|
||||
enum WebviewProviderType {
|
||||
SIDEBAR = 0;
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
service WebService {
|
||||
rpc checkIsImageUrl(StringRequest) returns (IsImageUrl);
|
||||
rpc fetchOpenGraphData(StringRequest) returns (OpenGraphData);
|
||||
|
||||
Executable → Regular
+67
-159
@@ -1,175 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import archiver from "archiver"
|
||||
import { execSync } from "child_process"
|
||||
import fs from "fs"
|
||||
import { cp } from "fs/promises"
|
||||
import { glob } from "glob"
|
||||
import minimatch from "minimatch"
|
||||
import path from "path"
|
||||
import { glob } from "glob"
|
||||
import archiver from "archiver"
|
||||
import { cp } from "fs/promises"
|
||||
import { execSync } from "child_process"
|
||||
|
||||
const BUILD_DIR = "dist-standalone"
|
||||
const RUNTIME_DEPS_DIR = "standalone/runtime-files"
|
||||
const SOURCE_DIR = "standalone/runtime-files"
|
||||
|
||||
async function main() {
|
||||
await installNodeDependencies()
|
||||
await zipDistribution()
|
||||
await cp(SOURCE_DIR, BUILD_DIR, { recursive: true })
|
||||
|
||||
// Run npm install in the distribution directory
|
||||
console.log("Running npm install in distribution directory...")
|
||||
const cwd = process.cwd()
|
||||
process.chdir(BUILD_DIR)
|
||||
try {
|
||||
execSync("npm install", { stdio: "inherit" })
|
||||
// Move the vscode directory into node_modules.
|
||||
// It can't be installed using npm because it will create a symlink which is not portable.
|
||||
fs.renameSync("vscode", path.join("node_modules", "vscode"))
|
||||
} catch (error) {
|
||||
console.error("Error during setup:", error)
|
||||
process.exit(1)
|
||||
} finally {
|
||||
process.chdir(cwd)
|
||||
}
|
||||
|
||||
async function installNodeDependencies() {
|
||||
await cpr(RUNTIME_DEPS_DIR, BUILD_DIR)
|
||||
|
||||
console.log("Running npm install in distribution directory...")
|
||||
const cwd = process.cwd()
|
||||
process.chdir(BUILD_DIR)
|
||||
|
||||
try {
|
||||
execSync("npm install", { stdio: "inherit" })
|
||||
// Move the vscode directory into node_modules.
|
||||
// It can't be installed using npm because it will create a symlink which cannot be unzipped correctly on windows.
|
||||
fs.renameSync("vscode", path.join("node_modules", "vscode"))
|
||||
} catch (error) {
|
||||
console.error("Error during setup:", error)
|
||||
process.exit(1)
|
||||
} finally {
|
||||
process.chdir(cwd)
|
||||
}
|
||||
|
||||
// Check for native .node modules.
|
||||
const nativeModules = await glob("**/*.node", { cwd: BUILD_DIR, nodir: true })
|
||||
if (nativeModules.length > 0) {
|
||||
console.error("Native node modules cannot be included in the standalone distribution:\n", nativeModules.join("\n"))
|
||||
process.exit(1)
|
||||
}
|
||||
// Check for native .node modules.
|
||||
const nativeModules = await glob("**/*.node", { cwd: BUILD_DIR, nodir: true })
|
||||
if (nativeModules.length > 0) {
|
||||
console.error("Native node modules cannot be included in the standalone distribution:\n", nativeModules.join("\n"))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
async function zipDistribution() {
|
||||
// Zip the build directory (excluding any pre-existing output zip).
|
||||
const zipPath = path.join(BUILD_DIR, "standalone.zip")
|
||||
const output = fs.createWriteStream(zipPath)
|
||||
const archive = archiver("zip", { zlib: { level: 3 } })
|
||||
// Zip the build directory (excluding any pre-existing output zip).
|
||||
const zipPath = path.join(BUILD_DIR, "standalone.zip")
|
||||
const output = fs.createWriteStream(zipPath)
|
||||
const archive = archiver("zip", { zlib: { level: 3 } })
|
||||
|
||||
output.on("close", () => {
|
||||
console.log(`Created ${zipPath} (${(archive.pointer() / 1024 / 1024).toFixed(1)} MB)`)
|
||||
})
|
||||
archive.on("warning", (err) => {
|
||||
console.warn(`Warning: ${err}`)
|
||||
})
|
||||
archive.on("error", (err) => {
|
||||
throw err
|
||||
})
|
||||
output.on("close", () => {
|
||||
console.log(`Created ${zipPath} (${(archive.pointer() / 1024 / 1024).toFixed(1)} MB)`)
|
||||
})
|
||||
archive.on("warning", (err) => {
|
||||
console.warn(`Warning: ${err}`)
|
||||
})
|
||||
archive.on("error", (err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
archive.pipe(output)
|
||||
// Add all the files from the standalone build dir.
|
||||
archive.glob("**/*", {
|
||||
cwd: BUILD_DIR,
|
||||
ignore: ["standalone.zip"],
|
||||
})
|
||||
archive.pipe(output)
|
||||
archive.glob("**/*", {
|
||||
cwd: BUILD_DIR,
|
||||
ignore: ["standalone.zip"],
|
||||
})
|
||||
|
||||
// Exclude the same files as the VCE vscode extension packager.
|
||||
// Also ignore the dist directory, the build directory for the extension.
|
||||
const isIgnored = createIsIgnored(["dist/**"])
|
||||
// Add the whole cline directory under "extension"
|
||||
archive.directory(process.cwd(), "extension", (entry) => {
|
||||
// Skip certain directories.
|
||||
const exclude = [
|
||||
BUILD_DIR + "/",
|
||||
"node_modules/", // node_modules nearly 1GB.
|
||||
"webview-ui/node_modules/", // node_modules nearly 1GB.
|
||||
]
|
||||
// These node modules are used at runtime as assets, they need to be included.
|
||||
const include = ["node_modules/@vscode/", "webview-ui/node_modules/katex"]
|
||||
const name = entry.name
|
||||
|
||||
// Add the whole cline directory under "extension", except the for the ignored files.
|
||||
archive.directory(process.cwd(), "extension", (entry) => {
|
||||
if (isIgnored(entry.name)) {
|
||||
log_verbose("Ignoring", entry.name)
|
||||
return false
|
||||
}
|
||||
if (include.some((prefix) => name.startsWith(prefix))) {
|
||||
return entry
|
||||
})
|
||||
|
||||
console.log("Zipping package...")
|
||||
await archive.finalize()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is based on https://github.com/microsoft/vscode-vsce/blob/fafad8a63e9cf31179f918eb7a4eeb376834c904/src/package.ts#L1695
|
||||
* because the .vscodeignore format is not compatible with the `ignore` npm module.
|
||||
*/
|
||||
function createIsIgnored(standaloneIgnores) {
|
||||
const MinimatchOptions = { dot: true }
|
||||
const defaultIgnore = [
|
||||
".vscodeignore",
|
||||
"package-lock.json",
|
||||
"npm-debug.log",
|
||||
"yarn.lock",
|
||||
"yarn-error.log",
|
||||
"npm-shrinkwrap.json",
|
||||
".editorconfig",
|
||||
".npmrc",
|
||||
".yarnrc",
|
||||
".gitattributes",
|
||||
"*.todo",
|
||||
"tslint.yaml",
|
||||
".eslintrc*",
|
||||
".babelrc*",
|
||||
".prettierrc*",
|
||||
".cz-config.js",
|
||||
".commitlintrc*",
|
||||
"webpack.config.js",
|
||||
"ISSUE_TEMPLATE.md",
|
||||
"CONTRIBUTING.md",
|
||||
"PULL_REQUEST_TEMPLATE.md",
|
||||
"CODE_OF_CONDUCT.md",
|
||||
".github",
|
||||
".travis.yml",
|
||||
"appveyor.yml",
|
||||
"**/.git",
|
||||
"**/.git/**",
|
||||
"**/*.vsix",
|
||||
"**/.DS_Store",
|
||||
"**/*.vsixmanifest",
|
||||
"**/.vscode-test/**",
|
||||
"**/.vscode-test-web/**",
|
||||
]
|
||||
|
||||
const rawIgnore = fs.readFileSync(".vscodeignore", "utf8")
|
||||
|
||||
// Parse raw ignore by splitting output into lines and filtering out empty lines and comments
|
||||
const parsedIgnore = rawIgnore
|
||||
.split(/[\n\r]/)
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => !!s)
|
||||
.filter((i) => !/^\s*#/.test(i))
|
||||
|
||||
// Add '/**' to possible folder names
|
||||
const expandedIgnore = [
|
||||
...parsedIgnore,
|
||||
...parsedIgnore.filter((i) => !/(^|\/)[^/]*\*[^/]*$/.test(i)).map((i) => (/\/$/.test(i) ? `${i}**` : `${i}/**`)),
|
||||
]
|
||||
|
||||
// Combine with default ignore list
|
||||
// Also ignore the dist directory- the build directory for the extension.
|
||||
const allIgnore = [...defaultIgnore, ...expandedIgnore, ...standaloneIgnores]
|
||||
|
||||
// Split into ignore and negate list
|
||||
const [ignore, negate] = allIgnore.reduce(
|
||||
(r, e) => (!/^\s*!/.test(e) ? [[...r[0], e], r[1]] : [r[0], [...r[1], e]]),
|
||||
[[], []],
|
||||
)
|
||||
|
||||
function isIgnored(f) {
|
||||
return (
|
||||
ignore.some((i) => minimatch(f, i, MinimatchOptions)) &&
|
||||
!negate.some((i) => minimatch(f, i.substr(1), MinimatchOptions))
|
||||
)
|
||||
}
|
||||
return isIgnored
|
||||
}
|
||||
|
||||
/* cp -r */
|
||||
async function cpr(source, dest) {
|
||||
await cp(source, dest, {
|
||||
recursive: true,
|
||||
preserveTimestamps: true,
|
||||
dereference: false, // preserve symlinks instead of following them
|
||||
})
|
||||
}
|
||||
|
||||
function log_verbose(...args) {
|
||||
if (process.argv.includes("-v") || process.argv.includes("--verbose")) {
|
||||
console.log(...args)
|
||||
if (exclude.some((prefix) => name.startsWith(prefix))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (name.match(/(^|\/)\./)) {
|
||||
// exclude dot directories
|
||||
return false
|
||||
}
|
||||
return entry
|
||||
})
|
||||
|
||||
await main()
|
||||
console.log("Zipping package...")
|
||||
await archive.finalize()
|
||||
|
||||
+28
-168
@@ -38,202 +38,62 @@ export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
}
|
||||
|
||||
function createHandlerForProvider(apiProvider: string | undefined, options: Omit<ApiConfiguration, "apiProvider">): ApiHandler {
|
||||
function createHandlerForProvider(apiProvider: string | undefined, options: any): ApiHandler {
|
||||
switch (apiProvider) {
|
||||
case "anthropic":
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
anthropicBaseUrl: options.anthropicBaseUrl,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
return new AnthropicHandler(options)
|
||||
case "openrouter":
|
||||
return new OpenRouterHandler({
|
||||
openRouterApiKey: options.openRouterApiKey,
|
||||
openRouterModelId: options.openRouterModelId,
|
||||
openRouterModelInfo: options.openRouterModelInfo,
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
return new OpenRouterHandler(options)
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler({
|
||||
apiModelId: options.apiModelId,
|
||||
awsAccessKey: options.awsAccessKey,
|
||||
awsSecretKey: options.awsSecretKey,
|
||||
awsSessionToken: options.awsSessionToken,
|
||||
awsRegion: options.awsRegion,
|
||||
awsUseCrossRegionInference: options.awsUseCrossRegionInference,
|
||||
awsBedrockUsePromptCache: options.awsBedrockUsePromptCache,
|
||||
awsUseProfile: options.awsUseProfile,
|
||||
awsProfile: options.awsProfile,
|
||||
awsBedrockEndpoint: options.awsBedrockEndpoint,
|
||||
awsBedrockCustomSelected: options.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: options.awsBedrockCustomModelBaseId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
return new AwsBedrockHandler(options)
|
||||
case "vertex":
|
||||
return new VertexHandler({
|
||||
vertexProjectId: options.vertexProjectId,
|
||||
vertexRegion: options.vertexRegion,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
return new VertexHandler(options)
|
||||
case "openai":
|
||||
return new OpenAiHandler({
|
||||
openAiApiKey: options.openAiApiKey,
|
||||
openAiBaseUrl: options.openAiBaseUrl,
|
||||
azureApiVersion: options.azureApiVersion,
|
||||
openAiHeaders: options.openAiHeaders,
|
||||
openAiModelId: options.openAiModelId,
|
||||
openAiModelInfo: options.openAiModelInfo,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
})
|
||||
return new OpenAiHandler(options)
|
||||
case "ollama":
|
||||
return new OllamaHandler({
|
||||
ollamaBaseUrl: options.ollamaBaseUrl,
|
||||
ollamaModelId: options.ollamaModelId,
|
||||
ollamaApiOptionsCtxNum: options.ollamaApiOptionsCtxNum,
|
||||
requestTimeoutMs: options.requestTimeoutMs,
|
||||
})
|
||||
return new OllamaHandler(options)
|
||||
case "lmstudio":
|
||||
return new LmStudioHandler({
|
||||
lmStudioBaseUrl: options.lmStudioBaseUrl,
|
||||
lmStudioModelId: options.lmStudioModelId,
|
||||
})
|
||||
return new LmStudioHandler(options)
|
||||
case "gemini":
|
||||
return new GeminiHandler({
|
||||
vertexProjectId: options.vertexProjectId,
|
||||
vertexRegion: options.vertexRegion,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
return new GeminiHandler(options)
|
||||
case "openai-native":
|
||||
return new OpenAiNativeHandler({
|
||||
openAiNativeApiKey: options.openAiNativeApiKey,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new OpenAiNativeHandler(options)
|
||||
case "deepseek":
|
||||
return new DeepSeekHandler({
|
||||
deepSeekApiKey: options.deepSeekApiKey,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new DeepSeekHandler(options)
|
||||
case "requesty":
|
||||
return new RequestyHandler({
|
||||
requestyApiKey: options.requestyApiKey,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
requestyModelId: options.requestyModelId,
|
||||
requestyModelInfo: options.requestyModelInfo,
|
||||
})
|
||||
return new RequestyHandler(options)
|
||||
case "fireworks":
|
||||
return new FireworksHandler({
|
||||
fireworksApiKey: options.fireworksApiKey,
|
||||
fireworksModelId: options.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: options.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: options.fireworksModelMaxTokens,
|
||||
})
|
||||
return new FireworksHandler(options)
|
||||
case "together":
|
||||
return new TogetherHandler({
|
||||
togetherApiKey: options.togetherApiKey,
|
||||
togetherModelId: options.togetherModelId,
|
||||
})
|
||||
return new TogetherHandler(options)
|
||||
case "qwen":
|
||||
return new QwenHandler({
|
||||
qwenApiKey: options.qwenApiKey,
|
||||
qwenApiLine: options.qwenApiLine,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
return new QwenHandler(options)
|
||||
case "doubao":
|
||||
return new DoubaoHandler({
|
||||
doubaoApiKey: options.doubaoApiKey,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new DoubaoHandler(options)
|
||||
case "mistral":
|
||||
return new MistralHandler({
|
||||
mistralApiKey: options.mistralApiKey,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new MistralHandler(options)
|
||||
case "vscode-lm":
|
||||
return new VsCodeLmHandler({
|
||||
vsCodeLmModelSelector: options.vsCodeLmModelSelector,
|
||||
})
|
||||
return new VsCodeLmHandler(options)
|
||||
case "cline":
|
||||
return new ClineHandler({
|
||||
taskId: options.taskId,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
openRouterModelId: options.openRouterModelId,
|
||||
openRouterModelInfo: options.openRouterModelInfo,
|
||||
})
|
||||
return new ClineHandler(options)
|
||||
case "litellm":
|
||||
return new LiteLlmHandler({
|
||||
liteLlmApiKey: options.liteLlmApiKey,
|
||||
liteLlmBaseUrl: options.liteLlmBaseUrl,
|
||||
liteLlmModelId: options.liteLlmModelId,
|
||||
liteLlmModelInfo: options.liteLlmModelInfo,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
liteLlmUsePromptCache: options.liteLlmUsePromptCache,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
return new LiteLlmHandler(options)
|
||||
case "nebius":
|
||||
return new NebiusHandler({
|
||||
nebiusApiKey: options.nebiusApiKey,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new NebiusHandler(options)
|
||||
case "asksage":
|
||||
return new AskSageHandler({
|
||||
asksageApiKey: options.asksageApiKey,
|
||||
asksageApiUrl: options.asksageApiUrl,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new AskSageHandler(options)
|
||||
case "xai":
|
||||
return new XAIHandler({
|
||||
xaiApiKey: options.xaiApiKey,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new XAIHandler(options)
|
||||
case "sambanova":
|
||||
return new SambanovaHandler({
|
||||
sambanovaApiKey: options.sambanovaApiKey,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new SambanovaHandler(options)
|
||||
case "cerebras":
|
||||
return new CerebrasHandler({
|
||||
cerebrasApiKey: options.cerebrasApiKey,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new CerebrasHandler(options)
|
||||
case "sapaicore":
|
||||
return new SapAiCoreHandler({
|
||||
sapAiCoreClientId: options.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: options.sapAiCoreClientSecret,
|
||||
sapAiCoreTokenUrl: options.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: options.sapAiResourceGroup,
|
||||
sapAiCoreBaseUrl: options.sapAiCoreBaseUrl,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
return new SapAiCoreHandler(options)
|
||||
case "claude-code":
|
||||
return new ClaudeCodeHandler({
|
||||
claudeCodePath: options.claudeCodePath,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
return new ClaudeCodeHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
anthropicBaseUrl: options.anthropicBaseUrl,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,11 @@ import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerO
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AnthropicHandlerOptions {
|
||||
apiKey?: string
|
||||
anthropicBaseUrl?: string
|
||||
apiModelId?: string
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
export class AnthropicHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic | undefined
|
||||
|
||||
constructor(options: AnthropicHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandler } from ".."
|
||||
import { ModelInfo, AskSageModelId, askSageModels, askSageDefaultModelId, askSageDefaultURL } from "@shared/api"
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
ModelInfo,
|
||||
AskSageModelId,
|
||||
askSageModels,
|
||||
askSageDefaultModelId,
|
||||
askSageDefaultURL,
|
||||
} from "@shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface AskSageHandlerOptions {
|
||||
asksageApiKey?: string
|
||||
asksageApiUrl?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
type AskSageRequest = {
|
||||
system_prompt: string
|
||||
message: {
|
||||
@@ -30,11 +31,11 @@ type AskSageResponse = {
|
||||
}
|
||||
|
||||
export class AskSageHandler implements ApiHandler {
|
||||
private options: AskSageHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private apiUrl: string
|
||||
private apiKey: string
|
||||
|
||||
constructor(options: AskSageHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
console.log("init api url", options.asksageApiUrl, askSageDefaultURL)
|
||||
this.options = options
|
||||
this.apiKey = options.asksageApiKey || ""
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
@@ -16,22 +16,6 @@ import {
|
||||
// Import proper AWS SDK types
|
||||
import type { Message, ContentBlock } from "@aws-sdk/client-bedrock-runtime"
|
||||
|
||||
interface AwsBedrockHandlerOptions {
|
||||
apiModelId?: string
|
||||
awsAccessKey?: string
|
||||
awsSecretKey?: string
|
||||
awsSessionToken?: string
|
||||
awsRegion?: string
|
||||
awsUseCrossRegionInference?: boolean
|
||||
awsBedrockUsePromptCache?: boolean
|
||||
awsUseProfile?: boolean
|
||||
awsProfile?: string
|
||||
awsBedrockEndpoint?: string
|
||||
awsBedrockCustomSelected?: boolean
|
||||
awsBedrockCustomModelBaseId?: BedrockModelId
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
// Extend AWS SDK types to include additionalModelResponseFields
|
||||
interface ExtendedMetadata {
|
||||
usage?: {
|
||||
@@ -106,9 +90,9 @@ interface ProviderChainOptions {
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
|
||||
export class AwsBedrockHandler implements ApiHandler {
|
||||
private options: AwsBedrockHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
constructor(options: AwsBedrockHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ModelInfo, CerebrasModelId, cerebrasDefaultModelId, cerebrasModels } from "@shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, CerebrasModelId, cerebrasDefaultModelId, cerebrasModels } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
|
||||
interface CerebrasHandlerOptions {
|
||||
cerebrasApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class CerebrasHandler implements ApiHandler {
|
||||
private options: CerebrasHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: Cerebras | undefined
|
||||
|
||||
constructor(options: CerebrasHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { claudeCodeDefaultModelId, ClaudeCodeModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { claudeCodeDefaultModelId, ClaudeCodeModelId, claudeCodeModels, type ApiHandlerOptions } from "@/shared/api"
|
||||
import { type ApiHandler } from ".."
|
||||
import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { filterMessagesForClaudeCode } from "@/integrations/claude-code/message-filter"
|
||||
|
||||
interface ClaudeCodeHandlerOptions {
|
||||
claudeCodePath?: string
|
||||
apiModelId?: string
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
export class ClaudeCodeHandler implements ApiHandler {
|
||||
private options: ClaudeCodeHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
constructor(options: ClaudeCodeHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
+104
-149
@@ -1,205 +1,157 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
import axios from "axios"
|
||||
import { OpenRouterErrorResponse } from "./types"
|
||||
import { withRetry } from "../retry"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import OpenAI from "openai"
|
||||
import { version as extensionVersion } from "../../../package.json"
|
||||
|
||||
interface ClineHandlerOptions {
|
||||
taskId?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
openRouterProviderSorting?: string
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
clineAccountId?: string
|
||||
}
|
||||
|
||||
export class ClineHandler implements ApiHandler {
|
||||
private options: ClineHandlerOptions
|
||||
private clineAccountService = ClineAccountService.getInstance()
|
||||
private _authService: AuthService
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
// TODO: replace this with a global API Host
|
||||
private readonly _baseUrl = "https://api.cline.bot"
|
||||
// private readonly _baseUrl = "https://core-api.staging.int.cline.bot"
|
||||
// private readonly _baseUrl = "http://localhost:7777"
|
||||
lastGenerationId?: string
|
||||
private counter = 0
|
||||
|
||||
constructor(options: ClineHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this._authService = AuthService.getInstance()
|
||||
}
|
||||
|
||||
private async ensureClient(): Promise<OpenAI> {
|
||||
const clineAccountAuthToken = await this._authService.getAuthToken()
|
||||
if (!clineAccountAuthToken) {
|
||||
throw new Error("Cline account authentication token is required")
|
||||
}
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
if (!this.options.clineApiKey) {
|
||||
throw new Error("You don't seem to be logged in to a Cline account.")
|
||||
}
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: `${this._baseUrl}/api/v1`,
|
||||
apiKey: clineAccountAuthToken,
|
||||
baseURL: "https://api.cline.bot/v1",
|
||||
apiKey: this.options.clineApiKey || "",
|
||||
defaultHeaders: {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
"X-Title": "Cline",
|
||||
"X-Task-ID": this.options.taskId || "",
|
||||
"X-Cline-Version": extensionVersion,
|
||||
"HTTP-Referer": "https://cline.bot", // Optional, for including your app on cline.bot rankings.
|
||||
"X-Title": "Cline", // Optional. Shows in rankings on cline.bot.
|
||||
"X-Task-ID": this.options.taskId || "", // Include the task ID in the request headers
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Cline client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
// Ensure the client is always using the latest auth token
|
||||
this.client.apiKey = clineAccountAuthToken
|
||||
return this.client
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
try {
|
||||
// Only continue the request if the user:
|
||||
// 1. Has signed in to Cline with a token
|
||||
// 2. Has more than 0 credits
|
||||
// Or an error is thrown.
|
||||
await this.clineAccountService.validateRequest()
|
||||
const client = this.ensureClient()
|
||||
this.lastGenerationId = undefined
|
||||
|
||||
const client = await this.ensureClient()
|
||||
const stream = await createOpenRouterStream(
|
||||
client,
|
||||
systemPrompt,
|
||||
messages,
|
||||
this.getModel(),
|
||||
this.options.reasoningEffort,
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
)
|
||||
|
||||
this.lastGenerationId = undefined
|
||||
let didOutputUsage: boolean = false
|
||||
|
||||
let didOutputUsage: boolean = false
|
||||
for await (const chunk of stream) {
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
console.error(`Cline API Error: ${error?.code} - ${error?.message}`)
|
||||
// Include metadata in the error message if available
|
||||
const metadataStr = error.metadata ? `\nMetadata: ${JSON.stringify(error.metadata, null, 2)}` : ""
|
||||
throw new Error(`Cline API Error ${error.code}: ${error.message}${metadataStr}`)
|
||||
}
|
||||
|
||||
const stream = await createOpenRouterStream(
|
||||
client,
|
||||
systemPrompt,
|
||||
messages,
|
||||
this.getModel(),
|
||||
this.options.reasoningEffort,
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
)
|
||||
if (!this.lastGenerationId && chunk.id) {
|
||||
this.lastGenerationId = chunk.id
|
||||
}
|
||||
|
||||
for await (const chunk of stream) {
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
console.error(`Cline API Error: ${error?.code} - ${error?.message}`)
|
||||
// Include metadata in the error message if available
|
||||
const metadataStr = error.metadata ? `\nMetadata: ${JSON.stringify(error.metadata, null, 2)}` : ""
|
||||
throw new Error(`Cline API Error ${error.code}: ${error.message}${metadataStr}`)
|
||||
}
|
||||
if (!this.lastGenerationId && chunk.id) {
|
||||
this.lastGenerationId = chunk.id
|
||||
// Check for mid-stream error via finish_reason
|
||||
const choice = chunk.choices?.[0]
|
||||
// OpenRouter may return finish_reason = "error" with error details
|
||||
if ((choice?.finish_reason as string) === "error") {
|
||||
const choiceWithError = choice as any
|
||||
if (choiceWithError.error) {
|
||||
const error = choiceWithError.error
|
||||
console.error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
throw new Error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
} else {
|
||||
throw new Error("Cline Mid-Stream Error: Stream terminated with error status but no error details provided")
|
||||
}
|
||||
}
|
||||
|
||||
// Check for mid-stream error via finish_reason
|
||||
const choice = chunk.choices?.[0]
|
||||
// OpenRouter may return finish_reason = "error" with error details
|
||||
if ((choice?.finish_reason as string) === "error") {
|
||||
const choiceWithError = choice as any
|
||||
if (choiceWithError.error) {
|
||||
const error = choiceWithError.error
|
||||
console.error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
throw new Error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
} else {
|
||||
throw new Error(
|
||||
"Cline Mid-Stream Error: Stream terminated with error status but no error details provided",
|
||||
)
|
||||
}
|
||||
const delta = choice?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
const delta = choice?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
// Reasoning tokens are returned separately from the content
|
||||
if ("reasoning" in delta && delta.reasoning) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
// Reasoning tokens are returned separately from the content
|
||||
if ("reasoning" in delta && delta.reasoning) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
const modelId = this.getModel().id
|
||||
reasoning: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
// const provider = modelId.split("/")[0]
|
||||
// // If provider is x-ai, set totalCost to 0 (we're doing a promo)
|
||||
// if (provider === "x-ai") {
|
||||
// totalCost = 0
|
||||
// }
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
// @ts-ignore-next-line
|
||||
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
const modelId = this.getModel().id
|
||||
const provider = modelId.split("/")[0]
|
||||
|
||||
if (modelId.includes("gemini")) {
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens:
|
||||
(chunk.usage.prompt_tokens || 0) - (chunk.usage.prompt_tokens_details?.cached_tokens || 0),
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
totalCost,
|
||||
}
|
||||
} else {
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
totalCost,
|
||||
}
|
||||
// If provider is x-ai, set totalCost to 0 (we're doing a promo)
|
||||
if (provider === "x-ai") {
|
||||
totalCost = 0
|
||||
}
|
||||
|
||||
if (modelId.includes("gemini")) {
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: (chunk.usage.prompt_tokens || 0) - (chunk.usage.prompt_tokens_details?.cached_tokens || 0),
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
totalCost,
|
||||
}
|
||||
} else {
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
// @ts-ignore-next-line
|
||||
totalCost,
|
||||
}
|
||||
didOutputUsage = true
|
||||
}
|
||||
didOutputUsage = true
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to generation endpoint if usage chunk not returned
|
||||
if (!didOutputUsage) {
|
||||
console.warn("Cline API did not return usage chunk, fetching from generation endpoint")
|
||||
const apiStreamUsage = await this.getApiStreamUsage()
|
||||
if (apiStreamUsage) {
|
||||
yield apiStreamUsage
|
||||
}
|
||||
// Fallback to generation endpoint if usage chunk not returned
|
||||
if (!didOutputUsage) {
|
||||
const apiStreamUsage = await this.getApiStreamUsage()
|
||||
if (apiStreamUsage) {
|
||||
yield apiStreamUsage
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === "ERR_BAD_REQUEST" || error.status === 401) {
|
||||
throw new Error("Unauthorized: Please sign in to Cline before trying again.") // match with webview-ui/src/components/chat/ChatRow.tsx
|
||||
} else if (error.code === "insufficient_credits" || error.status === 402) {
|
||||
throw new Error(error.error ? JSON.stringify(error.error) : "Insufficient credits or unknown error.")
|
||||
}
|
||||
console.error("Cline API Error:", error)
|
||||
throw error instanceof Error ? error : new Error(String(error))
|
||||
}
|
||||
}
|
||||
|
||||
async getApiStreamUsage(): Promise<ApiStreamUsageChunk | undefined> {
|
||||
if (this.lastGenerationId) {
|
||||
try {
|
||||
// TODO: replace this with firebase auth
|
||||
// TODO: use global API Host
|
||||
|
||||
const response = await axios.get(`${this.clineAccountService.baseUrl}/generation?id=${this.lastGenerationId}`, {
|
||||
const response = await axios.get(`https://api.cline.bot/v1/generation?id=${this.lastGenerationId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.options.clineAccountId}`,
|
||||
Authorization: `Bearer ${this.options.clineApiKey}`,
|
||||
},
|
||||
timeout: 15_000, // this request hangs sometimes
|
||||
})
|
||||
@@ -237,6 +189,9 @@ export class ClineHandler implements ApiHandler {
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
let modelId = this.options.openRouterModelId
|
||||
if (modelId === "x-ai/grok-3") {
|
||||
modelId = "x-ai/grok-3-beta"
|
||||
}
|
||||
const modelInfo = this.options.openRouterModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
|
||||
@@ -8,16 +8,11 @@ import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
|
||||
interface DeepSeekHandlerOptions {
|
||||
deepSeekApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class DeepSeekHandler implements ApiHandler {
|
||||
private options: DeepSeekHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: DeepSeekHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { ApiHandler } from ".."
|
||||
import { doubaoDefaultModelId, DoubaoModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, doubaoDefaultModelId, DoubaoModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface DoubaoHandlerOptions {
|
||||
doubaoApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class DoubaoHandler implements ApiHandler {
|
||||
private options: DoubaoHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
constructor(options: DoubaoHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,22 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from ".."
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
DeepSeekModelId,
|
||||
ModelInfo,
|
||||
deepSeekDefaultModelId,
|
||||
deepSeekModels,
|
||||
openAiModelInfoSaneDefaults,
|
||||
} from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface FireworksHandlerOptions {
|
||||
fireworksApiKey?: string
|
||||
fireworksModelId?: string
|
||||
fireworksModelMaxCompletionTokens?: number
|
||||
fireworksModelMaxTokens?: number
|
||||
}
|
||||
|
||||
export class FireworksHandler implements ApiHandler {
|
||||
private options: FireworksHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: FireworksHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,8 @@ import { telemetryService } from "@services/posthog/telemetry/TelemetryService"
|
||||
// Define a default TTL for the cache (e.g., 15 minutes in seconds)
|
||||
const DEFAULT_CACHE_TTL_SECONDS = 900
|
||||
|
||||
interface GeminiHandlerOptions {
|
||||
interface GeminiHandlerOptions extends ApiHandlerOptions {
|
||||
isVertex?: boolean
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
thinkingBudgetTokens?: number
|
||||
apiModelId?: string
|
||||
taskId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults, LiteLLMModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandler } from ".."
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface LiteLlmHandlerOptions {
|
||||
liteLlmApiKey?: string
|
||||
liteLlmBaseUrl?: string
|
||||
liteLlmModelId?: string
|
||||
liteLlmModelInfo?: LiteLLMModelInfo
|
||||
thinkingBudgetTokens?: number
|
||||
liteLlmUsePromptCache?: boolean
|
||||
taskId?: string
|
||||
}
|
||||
|
||||
export class LiteLlmHandler implements ApiHandler {
|
||||
private options: LiteLlmHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: LiteLlmHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,11 @@ import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface LmStudioHandlerOptions {
|
||||
lmStudioBaseUrl?: string
|
||||
lmStudioModelId?: string
|
||||
}
|
||||
|
||||
export class LmStudioHandler implements ApiHandler {
|
||||
private options: LmStudioHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: LmStudioHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,15 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Mistral } from "@mistralai/mistralai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { mistralDefaultModelId, MistralModelId, mistralModels, ModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, mistralDefaultModelId, MistralModelId, mistralModels, ModelInfo } from "@shared/api"
|
||||
import { convertToMistralMessages } from "../transform/mistral-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface MistralHandlerOptions {
|
||||
mistralApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class MistralHandler implements ApiHandler {
|
||||
private options: MistralHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: Mistral | undefined
|
||||
|
||||
constructor(options: MistralHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,12 @@ import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { nebiusDefaultModelId, nebiusModels, type ModelInfo, type NebiusModelId } from "../../shared/api"
|
||||
|
||||
interface NebiusHandlerOptions {
|
||||
nebiusApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
import { nebiusDefaultModelId, nebiusModels, type ModelInfo, type ApiHandlerOptions, type NebiusModelId } from "../../shared/api"
|
||||
|
||||
export class NebiusHandler implements ApiHandler {
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(private readonly options: NebiusHandlerOptions) {}
|
||||
constructor(private readonly options: ApiHandlerOptions) {}
|
||||
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
|
||||
@@ -6,18 +6,11 @@ import { convertToOllamaMessages } from "../transform/ollama-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface OllamaHandlerOptions {
|
||||
ollamaBaseUrl?: string
|
||||
ollamaModelId?: string
|
||||
ollamaApiOptionsCtxNum?: string
|
||||
requestTimeoutMs?: number
|
||||
}
|
||||
|
||||
export class OllamaHandler implements ApiHandler {
|
||||
private options: OllamaHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: Ollama | undefined
|
||||
|
||||
constructor(options: OllamaHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -8,17 +8,11 @@ import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
|
||||
interface OpenAiNativeHandlerOptions {
|
||||
openAiNativeApiKey?: string
|
||||
reasoningEffort?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class OpenAiNativeHandler implements ApiHandler {
|
||||
private options: OpenAiNativeHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: OpenAiNativeHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI, { AzureOpenAI } from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { azureOpenAiDefaultApiVersion, ModelInfo, openAiModelInfoSaneDefaults, OpenAiCompatibleModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, azureOpenAiDefaultApiVersion, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
|
||||
interface OpenAiHandlerOptions {
|
||||
openAiApiKey?: string
|
||||
openAiBaseUrl?: string
|
||||
azureApiVersion?: string
|
||||
openAiHeaders?: Record<string, string>
|
||||
openAiModelId?: string
|
||||
openAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
reasoningEffort?: string
|
||||
}
|
||||
|
||||
export class OpenAiHandler implements ApiHandler {
|
||||
private options: OpenAiHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: OpenAiHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -3,27 +3,18 @@ import axios from "axios"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
import { OpenRouterErrorResponse } from "./types"
|
||||
|
||||
interface OpenRouterHandlerOptions {
|
||||
openRouterApiKey?: string
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
openRouterProviderSorting?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
export class OpenRouterHandler implements ApiHandler {
|
||||
private options: OpenRouterHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
lastGenerationId?: string
|
||||
|
||||
constructor(options: OpenRouterHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
@@ -213,6 +204,9 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
let modelId = this.options.openRouterModelId
|
||||
if (modelId === "x-ai/grok-3") {
|
||||
modelId = "x-ai/grok-3-beta"
|
||||
}
|
||||
const modelInfo = this.options.openRouterModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
ModelInfo,
|
||||
mainlandQwenModels,
|
||||
internationalQwenModels,
|
||||
@@ -15,18 +16,11 @@ import { ApiStream } from "../transform/stream"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface QwenHandlerOptions {
|
||||
qwenApiKey?: string
|
||||
qwenApiLine?: string
|
||||
apiModelId?: string
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
export class QwenHandler implements ApiHandler {
|
||||
private options: QwenHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: QwenHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,6 @@ import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
|
||||
interface RequestyHandlerOptions {
|
||||
requestyApiKey?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
requestyModelId?: string
|
||||
requestyModelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
// Requesty usage includes an extra field for Anthropic use cases.
|
||||
// Safely cast the prompt token details section to the appropriate structure.
|
||||
interface RequestyUsage extends OpenAI.CompletionUsage {
|
||||
@@ -26,10 +18,10 @@ interface RequestyUsage extends OpenAI.CompletionUsage {
|
||||
}
|
||||
|
||||
export class RequestyHandler implements ApiHandler {
|
||||
private options: RequestyHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: RequestyHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "@shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "@/api/transform/openai-format"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { convertToR1Format } from "@api/transform/r1-format"
|
||||
|
||||
interface SambanovaHandlerOptions {
|
||||
sambanovaApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class SambanovaHandler implements ApiHandler {
|
||||
private options: SambanovaHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: SambanovaHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,10 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { ModelInfo, sapAiCoreDefaultModelId, SapAiCoreModelId, sapAiCoreModels } from "../../shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, sapAiCoreDefaultModelId, SapAiCoreModelId, sapAiCoreModels } from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface SapAiCoreHandlerOptions {
|
||||
sapAiCoreClientId?: string
|
||||
sapAiCoreClientSecret?: string
|
||||
sapAiCoreTokenUrl?: string
|
||||
sapAiResourceGroup?: string
|
||||
sapAiCoreBaseUrl?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
interface Deployment {
|
||||
id: string
|
||||
name: string
|
||||
@@ -28,11 +19,11 @@ interface Token {
|
||||
expires_at: number
|
||||
}
|
||||
export class SapAiCoreHandler implements ApiHandler {
|
||||
private options: SapAiCoreHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private token?: Token
|
||||
private deployments?: Deployment[]
|
||||
|
||||
constructor(options: SapAiCoreHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
@@ -69,7 +60,6 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"AI-Resource-Group": this.options.sapAiResourceGroup || "default",
|
||||
"Content-Type": "application/json",
|
||||
"AI-Client-Type": "Cline",
|
||||
}
|
||||
|
||||
const url = `${this.options.sapAiCoreBaseUrl}/v2/lm/deployments?$top=10000&$skip=0`
|
||||
@@ -126,7 +116,6 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"AI-Resource-Group": this.options.sapAiResourceGroup || "default",
|
||||
"Content-Type": "application/json",
|
||||
"AI-Client-Type": "Cline",
|
||||
}
|
||||
|
||||
const model = this.getModel()
|
||||
@@ -294,6 +283,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
const jsonData = line.slice(6)
|
||||
try {
|
||||
const data = JSON.parse(jsonData)
|
||||
console.log("Received data:", data)
|
||||
if (data.type === "message_start") {
|
||||
usage.input_tokens = data.message.usage.input_tokens
|
||||
yield {
|
||||
@@ -356,6 +346,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
try {
|
||||
// Parse the incoming JSON data from the stream
|
||||
const data = JSON.parse(toStrictJson(jsonData))
|
||||
console.log("Received data:", data)
|
||||
|
||||
// Handle metadata (token usage)
|
||||
if (data.metadata?.usage) {
|
||||
@@ -431,6 +422,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
const jsonData = line.slice(6)
|
||||
try {
|
||||
const data = JSON.parse(jsonData)
|
||||
console.log("Received GPT data:", data)
|
||||
|
||||
if (data.choices && data.choices.length > 0) {
|
||||
const choice = data.choices[0]
|
||||
@@ -454,7 +446,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (data.choices?.[0]?.finish_reason === "stop") {
|
||||
if (data.choices && data.choices[0].finish_reason === "stop") {
|
||||
// Final usage yield, if not already provided
|
||||
if (!data.usage) {
|
||||
yield {
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { convertToR1Format } from "@api/transform/r1-format"
|
||||
|
||||
interface TogetherHandlerOptions {
|
||||
togetherApiKey?: string
|
||||
togetherModelId?: string
|
||||
}
|
||||
|
||||
export class TogetherHandler implements ApiHandler {
|
||||
private options: TogetherHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: TogetherHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -6,22 +6,12 @@ import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vert
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { GeminiHandler } from "./gemini"
|
||||
|
||||
interface VertexHandlerOptions {
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
apiModelId?: string
|
||||
thinkingBudgetTokens?: number
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
taskId?: string
|
||||
}
|
||||
|
||||
export class VertexHandler implements ApiHandler {
|
||||
private geminiHandler: GeminiHandler | undefined
|
||||
private clientAnthropic: AnthropicVertex | undefined
|
||||
private options: VertexHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
constructor(options: VertexHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,10 @@ import { calculateApiCostAnthropic } from "@utils/cost"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { convertToVsCodeLmMessages } from "@api/transform/vscode-lm-format"
|
||||
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "@shared/vsCodeSelectorUtils"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import type { LanguageModelChatSelector as LanguageModelChatSelectorFromTypes } from "./types"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface VsCodeLmHandlerOptions {
|
||||
vsCodeLmModelSelector?: any
|
||||
}
|
||||
|
||||
// Cline does not update VSCode type definitions or engine requirements to maintain compatibility.
|
||||
// This declaration (as seen in src/integrations/TerminalManager.ts) provides types for the Language Model API in newer versions of VSCode.
|
||||
// Extracted from https://github.com/microsoft/vscode/blob/131ee0ef660d600cd0a7e6058375b281553abe20/src/vscode-dts/vscode.d.ts
|
||||
@@ -128,12 +124,12 @@ declare module "vscode" {
|
||||
* ```
|
||||
*/
|
||||
export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: VsCodeLmHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: vscode.LanguageModelChat | null
|
||||
private disposable: vscode.Disposable | null
|
||||
private currentRequestCancellation: vscode.CancellationTokenSource | null
|
||||
|
||||
constructor(options: VsCodeLmHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = null
|
||||
this.disposable = null
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler } from "../"
|
||||
import { XAIModelId, ModelInfo, xaiDefaultModelId, xaiModels } from "@shared/api"
|
||||
import { ApiHandlerOptions, XAIModelId, ModelInfo, xaiDefaultModelId, xaiModels } from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { withRetry } from "../retry"
|
||||
|
||||
interface XAIHandlerOptions {
|
||||
xaiApiKey?: string
|
||||
reasoningEffort?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class XAIHandler implements ApiHandler {
|
||||
private options: XAIHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: XAIHandlerOptions) {
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as vscode from "vscode"
|
||||
import crypto from "crypto"
|
||||
import { Controller } from "../index"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { storeSecret } from "../../storage/state"
|
||||
import { EmptyRequest, String } from "../../../shared/proto/common"
|
||||
import { openExternal } from "@utils/env"
|
||||
|
||||
const authService = AuthService.getInstance()
|
||||
|
||||
/**
|
||||
* Handles the user clicking the login link in the UI.
|
||||
@@ -14,5 +13,21 @@ const authService = AuthService.getInstance()
|
||||
* @returns The login URL as a string.
|
||||
*/
|
||||
export async function accountLoginClicked(controller: Controller, _: EmptyRequest): Promise<String> {
|
||||
return await authService.createAuthRequest()
|
||||
// Generate nonce for state validation
|
||||
const nonce = crypto.randomBytes(32).toString("hex")
|
||||
await storeSecret(controller.context, "authNonce", nonce)
|
||||
|
||||
// Open browser for authentication with state param
|
||||
console.log("Login button clicked in account page")
|
||||
console.log("Opening auth page with state param")
|
||||
|
||||
const uriScheme = vscode.env.uriScheme
|
||||
|
||||
const authUrl = vscode.Uri.parse(
|
||||
`https://app.cline.bot/auth?state=${encodeURIComponent(nonce)}&callback_url=${encodeURIComponent(`${uriScheme || "vscode"}://saoudrizwan.claude-dev/auth`)}`,
|
||||
)
|
||||
await vscode.env.openExternal(authUrl)
|
||||
return String.create({
|
||||
value: authUrl.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import type { EmptyRequest } from "../../../shared/proto/common"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
const authService = AuthService.getInstance()
|
||||
/**
|
||||
* Handles the account logout action
|
||||
* @param controller The controller instance
|
||||
@@ -12,6 +10,5 @@ const authService = AuthService.getInstance()
|
||||
*/
|
||||
export async function accountLogoutClicked(controller: Controller, _request: EmptyRequest): Promise<Empty> {
|
||||
await controller.handleSignOut()
|
||||
await authService.handleDeauth()
|
||||
return Empty.create({})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AuthStateChangedRequest, AuthState } from "@shared/proto/account"
|
||||
import { AuthStateChangedRequest, AuthStateChanged } from "@shared/proto/account"
|
||||
import type { Controller } from "../index"
|
||||
import { updateGlobalState } from "../../storage/state"
|
||||
|
||||
@@ -9,13 +9,13 @@ import { updateGlobalState } from "../../storage/state"
|
||||
* @param request The auth state change request
|
||||
* @returns The updated user info
|
||||
*/
|
||||
export async function authStateChanged(controller: Controller, request: AuthStateChangedRequest): Promise<AuthState> {
|
||||
export async function authStateChanged(controller: Controller, request: AuthStateChangedRequest): Promise<AuthStateChanged> {
|
||||
try {
|
||||
// Store the user info directly in global state
|
||||
await updateGlobalState(controller.context, "userInfo", request.user)
|
||||
|
||||
// Return the same user info
|
||||
return AuthState.create({ user: request.user })
|
||||
return AuthStateChanged.create({ user: request.user })
|
||||
} catch (error) {
|
||||
console.error(`Failed to update auth state: ${error}`)
|
||||
throw error
|
||||
|
||||
+5
-4
@@ -8,7 +8,7 @@ import { UserCreditsData } from "@shared/proto/account"
|
||||
* @param request Empty request
|
||||
* @returns User credits data response
|
||||
*/
|
||||
export async function getUserCredits(controller: Controller, request: EmptyRequest): Promise<UserCreditsData> {
|
||||
export async function fetchUserCreditsData(controller: Controller, request: EmptyRequest): Promise<UserCreditsData> {
|
||||
try {
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
@@ -21,10 +21,11 @@ export async function getUserCredits(controller: Controller, request: EmptyReque
|
||||
controller.accountService.fetchPaymentTransactionsRPC(),
|
||||
])
|
||||
|
||||
// Since generated types match exactly, no conversion needed!
|
||||
return UserCreditsData.create({
|
||||
balance: balance ? { currentBalance: balance.balance / 100 } : { currentBalance: 0 },
|
||||
usageTransactions: usageTransactions,
|
||||
paymentTransactions: paymentTransactions,
|
||||
balance: balance ? { currentBalance: balance.currentBalance } : { currentBalance: 0 },
|
||||
usageTransactions: usageTransactions || [],
|
||||
paymentTransactions: paymentTransactions || [],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch user credits data: ${error}`)
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { Controller } from "../index"
|
||||
import { GetOrganizationCreditsRequest, OrganizationCreditsData, OrganizationUsageTransaction } from "@shared/proto/account"
|
||||
|
||||
/**
|
||||
* Handles fetching all organization credits data (balance, usage, payments)
|
||||
* @param controller The controller instance
|
||||
* @param request Organization credits request
|
||||
* @returns Organization credits data response
|
||||
*/
|
||||
export async function getOrganizationCredits(
|
||||
controller: Controller,
|
||||
request: GetOrganizationCreditsRequest,
|
||||
): Promise<OrganizationCreditsData> {
|
||||
try {
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
|
||||
// Call the individual RPC variants in parallel
|
||||
const [balanceData, usageTransactions] = await Promise.all([
|
||||
controller.accountService.fetchOrganizationCreditsRPC(request.organizationId),
|
||||
controller.accountService.fetchOrganizationUsageTransactionsRPC(request.organizationId),
|
||||
])
|
||||
|
||||
return OrganizationCreditsData.create({
|
||||
balance: balanceData ? { currentBalance: balanceData.balance / 100 } : { currentBalance: 0 },
|
||||
organizationId: balanceData?.organizationId || "",
|
||||
usageTransactions:
|
||||
usageTransactions?.map((tx) =>
|
||||
OrganizationUsageTransaction.create({
|
||||
aiInferenceProviderName: tx.aiInferenceProviderName,
|
||||
aiModelName: tx.aiModelName,
|
||||
aiModelTypeName: tx.aiModelTypeName,
|
||||
completionTokens: tx.completionTokens,
|
||||
costUsd: tx.costUsd,
|
||||
createdAt: tx.createdAt,
|
||||
creditsUsed: tx.creditsUsed,
|
||||
generationId: tx.generationId,
|
||||
organizationId: tx.organizationId,
|
||||
promptTokens: tx.promptTokens,
|
||||
totalTokens: tx.totalTokens,
|
||||
userId: tx.userId,
|
||||
}),
|
||||
) || [],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch organization credits data: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { Controller } from "../index"
|
||||
import type { EmptyRequest } from "@shared/proto/common"
|
||||
import { UserOrganization, UserOrganizationsResponse } from "@shared/proto/account"
|
||||
|
||||
/**
|
||||
* Handles fetching all user credits data (balance, usage, payments)
|
||||
* @param controller The controller instance
|
||||
* @param request Empty request
|
||||
* @returns User credits data response
|
||||
*/
|
||||
export async function getUserOrganizations(controller: Controller, request: EmptyRequest): Promise<UserOrganizationsResponse> {
|
||||
try {
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
|
||||
// Fetch user organizations from the account service
|
||||
const organizations = await controller.accountService.fetchUserOrganizationsRPC()
|
||||
|
||||
return UserOrganizationsResponse.create({
|
||||
organizations:
|
||||
organizations?.map((org) =>
|
||||
UserOrganization.create({
|
||||
active: org.active,
|
||||
memberId: org.memberId,
|
||||
name: org.name,
|
||||
organizationId: org.organizationId,
|
||||
roles: org.roles ? [...org.roles] : [],
|
||||
}),
|
||||
) || [],
|
||||
})
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { Controller } from "../index"
|
||||
import { Empty } from "@shared/proto/common"
|
||||
import { UserOrganizationUpdateRequest } from "@shared/proto/account"
|
||||
|
||||
/**
|
||||
* Handles setting the user's active organization
|
||||
* @param controller The controller instance
|
||||
* @param request UserOrganization to set as active
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function setUserOrganization(controller: Controller, request: UserOrganizationUpdateRequest): Promise<Empty> {
|
||||
try {
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
|
||||
// Switch to the specified organization using the account service
|
||||
await controller.accountService.switchAccount(request.organizationId)
|
||||
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Controller } from "../index"
|
||||
import { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { String as ProtoString } from "../../../shared/proto/common"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
|
||||
// Keep track of active authCallback subscriptions
|
||||
const activeAuthCallbackSubscriptions = new Set<StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to authCallback events
|
||||
* @param controller The controller instance
|
||||
* @param request The empty request
|
||||
* @param responseStream The streaming response handler
|
||||
* @param requestId The ID of the request (passed by the gRPC handler)
|
||||
*/
|
||||
export async function subscribeToAuthCallback(
|
||||
controller: Controller,
|
||||
request: EmptyRequest,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
// Add this subscription to the active subscriptions
|
||||
activeAuthCallbackSubscriptions.add(responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activeAuthCallbackSubscriptions.delete(responseStream)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
if (requestId) {
|
||||
getRequestRegistry().registerRequest(requestId, cleanup, { type: "authCallback_subscription" }, responseStream)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an authCallback event to all active subscribers
|
||||
* @param customToken The custom token for authentication
|
||||
*/
|
||||
export async function sendAuthCallbackEvent(customToken: string): Promise<void> {
|
||||
// Send the event to all active subscribers
|
||||
const promises = Array.from(activeAuthCallbackSubscriptions).map(async (responseStream) => {
|
||||
try {
|
||||
const event: ProtoString = {
|
||||
value: customToken,
|
||||
}
|
||||
await responseStream(
|
||||
event,
|
||||
false, // Not the last message
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error sending authCallback event:", error)
|
||||
// Remove the subscription if there was an error
|
||||
activeAuthCallbackSubscriptions.delete(responseStream)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { AuthService } from "../../../services/auth/AuthService"
|
||||
|
||||
const authService = AuthService.getInstance()
|
||||
export const subscribeToAuthStatusUpdate = authService.subscribeToAuthStatusUpdate.bind(authService)
|
||||
export const sendAuthStatusUpdateEvent = authService.sendAuthStatusUpdate.bind(authService)
|
||||
@@ -3,12 +3,11 @@ import { RuleFileRequest, RuleFile } from "@shared/proto/file"
|
||||
import { FileMethodHandler } from "./index"
|
||||
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
|
||||
import { createRuleFile as createRuleFileImpl } from "@core/context/instructions/user-instructions/rule-helpers"
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { handleFileServiceRequest } from "./index"
|
||||
import { refreshWorkflowToggles } from "@/core/context/instructions/user-instructions/workflows"
|
||||
import { getCwd, getDesktopDir } from "@/utils/path"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
/**
|
||||
* Creates a rule file in either global or workspace rules directory
|
||||
@@ -43,13 +42,7 @@ export const createRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
const fileTypeName = request.type === "workflow" ? "workflow" : "rule"
|
||||
|
||||
if (fileExists) {
|
||||
const message = `${fileTypeName} file "${request.filename}" already exists.`
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showWarningMessage(`${fileTypeName} file "${request.filename}" already exists.`)
|
||||
// Still open it for editing
|
||||
await handleFileServiceRequest(controller, "openFile", { value: filePath })
|
||||
} else {
|
||||
@@ -62,12 +55,8 @@ export const createRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
|
||||
await handleFileServiceRequest(controller, "openFile", { value: filePath })
|
||||
|
||||
const message = `Created new ${request.isGlobal ? "global" : "workspace"} ${fileTypeName} file: ${request.filename}`
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
vscode.window.showInformationMessage(
|
||||
`Created new ${request.isGlobal ? "global" : "workspace"} ${fileTypeName} file: ${request.filename}`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { deleteRuleFile as deleteRuleFileImpl } from "@core/context/instructions/user-instructions/rule-helpers"
|
||||
import { RuleFile, RuleFileRequest } from "@shared/proto/file"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { Controller } from ".."
|
||||
import { FileMethodHandler } from "./index"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
/**
|
||||
* Deletes a rule file from either global or workspace rules directory
|
||||
@@ -45,13 +44,7 @@ export const deleteRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
|
||||
const fileTypeName = request.type === "workflow" ? "workflow" : "rule"
|
||||
|
||||
const message = `${fileTypeName} file "${fileName}" deleted successfully`
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage(`${fileTypeName} file "${fileName}" deleted successfully`)
|
||||
|
||||
return RuleFile.create({
|
||||
filePath: request.rulePath,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { asRelativePath } from "@/utils/path"
|
||||
import { RelativePaths, RelativePathsRequest } from "@shared/proto/file"
|
||||
import * as path from "path"
|
||||
import { URI } from "vscode-uri"
|
||||
import { Controller } from ".."
|
||||
import { RelativePathsRequest, RelativePaths } from "@shared/proto/file"
|
||||
import { FileMethodHandler } from "./index"
|
||||
import { isDirectory } from "@/utils/fs"
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { StringRequest } from "@shared/proto/common"
|
||||
import { getHostBridgeProvider } from "@hosts/host-providers"
|
||||
|
||||
/**
|
||||
* Converts a list of URIs to workspace-relative paths
|
||||
@@ -13,32 +13,47 @@ import { isDirectory } from "@/utils/fs"
|
||||
* @returns Response with resolved relative paths
|
||||
*/
|
||||
export const getRelativePaths: FileMethodHandler = async (
|
||||
_controller: Controller,
|
||||
controller: Controller,
|
||||
request: RelativePathsRequest,
|
||||
): Promise<RelativePaths> => {
|
||||
const result = []
|
||||
for (const uriString of request.uris) {
|
||||
try {
|
||||
result.push(await getRelativePath(uriString))
|
||||
} catch (error) {
|
||||
console.error(`Error calculating relative path for ${uriString}:`, error)
|
||||
}
|
||||
}
|
||||
return RelativePaths.create({ paths: result })
|
||||
}
|
||||
const resolvedPaths = await Promise.all(
|
||||
request.uris.map(async (uriString) => {
|
||||
try {
|
||||
// Use the host URI service client instead of directly using vscode.Uri.parse
|
||||
const parseResponse = await getHostBridgeProvider().uriServiceClient.parse(
|
||||
StringRequest.create({
|
||||
value: uriString,
|
||||
}),
|
||||
)
|
||||
const fileUri = vscode.Uri.parse(`${parseResponse.scheme}://${parseResponse.authority}${parseResponse.path}`)
|
||||
console.log("[DEBUG] UriServiceClient.parse:", fileUri)
|
||||
const relativePathToGet = vscode.workspace.asRelativePath(fileUri, false)
|
||||
|
||||
async function getRelativePath(uriString: string): Promise<string> {
|
||||
const filePath = URI.parse(uriString, true).fsPath
|
||||
const relativePath = await asRelativePath(filePath)
|
||||
// If the path is still absolute, it's outside the workspace
|
||||
if (path.isAbsolute(relativePathToGet)) {
|
||||
console.warn(`Dropped file ${relativePathToGet} is outside the workspace. Sending original path.`)
|
||||
return fileUri.fsPath.replace(/\\/g, "/")
|
||||
} else {
|
||||
let finalPath = "/" + relativePathToGet.replace(/\\/g, "/")
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(fileUri)
|
||||
if (stat.type === vscode.FileType.Directory) {
|
||||
finalPath += "/"
|
||||
}
|
||||
} catch (statError) {
|
||||
console.error(`Error stating file ${fileUri.fsPath}:`, statError)
|
||||
}
|
||||
return finalPath
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error calculating relative path for ${uriString}:`, error)
|
||||
return null
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
// If the path is still absolute, it's outside the workspace
|
||||
if (path.isAbsolute(relativePath)) {
|
||||
throw new Error(`Dropped file ${relativePath} is outside the workspace.`)
|
||||
}
|
||||
// Filter out any null values from errors
|
||||
const validPaths = resolvedPaths.filter((path): path is string => path !== null)
|
||||
|
||||
let result = "/" + relativePath.replace(/\\/g, "/")
|
||||
if (await isDirectory(filePath)) {
|
||||
result += "/"
|
||||
}
|
||||
return result
|
||||
return RelativePaths.create({ paths: validPaths })
|
||||
}
|
||||
|
||||
+56
-116
@@ -27,15 +27,21 @@ import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { ensureMcpServersDirectoryExists, ensureSettingsDirectoryExists, GlobalFileNames } from "../storage/disk"
|
||||
import { getAllExtensionState, getGlobalState, getWorkspaceState, storeSecret, updateGlobalState } from "../storage/state"
|
||||
import {
|
||||
getAllExtensionState,
|
||||
getGlobalState,
|
||||
getSecret,
|
||||
getWorkspaceState,
|
||||
storeSecret,
|
||||
updateGlobalState,
|
||||
updateWorkspaceState,
|
||||
} from "../storage/state"
|
||||
import { Task } from "../task"
|
||||
import { sendAuthCallbackEvent } from "./account/subscribeToAuthCallback"
|
||||
import { handleGrpcRequest, handleGrpcRequestCancel } from "./grpc-handler"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { sendStateUpdate } from "./state/subscribeToState"
|
||||
import { sendAddToInputEvent } from "./ui/subscribeToAddToInput"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -48,11 +54,11 @@ export class Controller {
|
||||
private postMessage: (message: ExtensionMessage) => Thenable<boolean> | undefined
|
||||
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private mode: "plan" | "act" = "plan" // In-memory plan/act mode state
|
||||
task?: Task
|
||||
workspaceTracker: WorkspaceTracker
|
||||
mcpHub: McpHub
|
||||
accountService: ClineAccountService
|
||||
authService: AuthService
|
||||
latestAnnouncementId = "june-25-2025_16:11:00" // update to some unique identifier when we add a new announcement
|
||||
|
||||
constructor(
|
||||
@@ -72,9 +78,10 @@ export class Controller {
|
||||
(msg) => this.postMessageToWebview(msg),
|
||||
this.context.extension?.packageJSON?.version ?? "1.0.0",
|
||||
)
|
||||
this.accountService = ClineAccountService.getInstance()
|
||||
this.authService = AuthService.getInstance(context)
|
||||
this.authService.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
this.accountService = new ClineAccountService(async () => {
|
||||
const { apiConfiguration } = await this.getStateToPostToWebview()
|
||||
return apiConfiguration?.clineApiKey
|
||||
})
|
||||
|
||||
// Clean up legacy checkpoints
|
||||
cleanupLegacyCheckpoints(this.context.globalStorageUri.fsPath, this.outputChannel).catch((error) => {
|
||||
@@ -82,10 +89,6 @@ export class Controller {
|
||||
})
|
||||
}
|
||||
|
||||
private async getCurrentMode(): Promise<"plan" | "act"> {
|
||||
return ((await getGlobalState(this.context, "mode")) as "plan" | "act" | undefined) || "act"
|
||||
}
|
||||
|
||||
/*
|
||||
VSCode extensions use the disposable pattern to clean up resources when the sidebar/editor tab is closed by the user or system. This applies to event listening, commands, interacting with the UI, etc.
|
||||
- https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/
|
||||
@@ -108,24 +111,13 @@ export class Controller {
|
||||
// Auth methods
|
||||
async handleSignOut() {
|
||||
try {
|
||||
// TODO: update to clineAccountId and then move clineApiKey to a clear function.
|
||||
await storeSecret(this.context, "clineAccountId", undefined)
|
||||
await storeSecret(this.context, "clineApiKey", undefined)
|
||||
await updateGlobalState(this.context, "userInfo", undefined)
|
||||
await updateGlobalState(this.context, "apiProvider", "openrouter")
|
||||
await this.postStateToWebview()
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Successfully logged out of Cline",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Successfully logged out of Cline")
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Logout failed",
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("Logout failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,13 +141,10 @@ export class Controller {
|
||||
taskHistory,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
// Get current mode using helper function
|
||||
const currentMode = await this.getCurrentMode()
|
||||
|
||||
// Reconstruct ChatSettings with mode from global state and stored preferences
|
||||
// Reconstruct ChatSettings with in-memory mode and stored preferences
|
||||
const chatSettings: ChatSettings = {
|
||||
...storedChatSettings, // Spread stored preferences (preferredLanguage, openAIReasoningEffort)
|
||||
mode: currentMode, // Use mode from global state
|
||||
mode: this.mode, // Use in-memory mode (override any stored mode)
|
||||
}
|
||||
|
||||
const NEW_USER_TASK_COUNT_THRESHOLD = 10
|
||||
@@ -250,8 +239,8 @@ export class Controller {
|
||||
async togglePlanActModeWithChatSettings(chatSettings: ChatSettings, chatContent?: ChatContent): Promise<boolean> {
|
||||
const didSwitchToActMode = chatSettings.mode === "act"
|
||||
|
||||
// Store mode to global state
|
||||
await updateGlobalState(this.context, "mode", chatSettings.mode)
|
||||
// Store mode in-memory only
|
||||
this.mode = chatSettings.mode
|
||||
|
||||
// Capture mode switch telemetry | Capture regardless of if we know the taskId
|
||||
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", chatSettings.mode)
|
||||
@@ -405,7 +394,7 @@ export class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Save only non-mode properties to global storage
|
||||
// Save only non-mode properties to workspace storage
|
||||
const { mode, ...persistentChatSettings }: { mode: string } & StoredChatSettings = chatSettings
|
||||
await updateGlobalState(this.context, "chatSettings", persistentChatSettings)
|
||||
await this.postStateToWebview()
|
||||
@@ -462,24 +451,33 @@ export class Controller {
|
||||
}
|
||||
|
||||
// Auth
|
||||
|
||||
public async validateAuthState(state: string | null): Promise<boolean> {
|
||||
return state === this.authService.authNonce
|
||||
const storedNonce = await getSecret(this.context, "authNonce")
|
||||
if (!state || state !== storedNonce) {
|
||||
return false
|
||||
}
|
||||
await storeSecret(this.context, "authNonce", undefined) // Clear after use
|
||||
return true
|
||||
}
|
||||
|
||||
async handleAuthCallback(customToken: string, provider: string | null = null) {
|
||||
async handleAuthCallback(customToken: string, apiKey: string) {
|
||||
try {
|
||||
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
|
||||
// Store API key for API calls
|
||||
await storeSecret(this.context, "clineApiKey", apiKey)
|
||||
|
||||
// Send custom token to webview for Firebase auth
|
||||
await sendAuthCallbackEvent(customToken)
|
||||
|
||||
const clineProvider: ApiProvider = "cline"
|
||||
await updateGlobalState(this.context, "apiProvider", clineProvider)
|
||||
|
||||
// Mark welcome view as completed since user has successfully logged in
|
||||
await updateGlobalState(this.context, "welcomeViewCompleted", true)
|
||||
|
||||
// Update API configuration with the new provider and API key
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
const updatedConfig = {
|
||||
...apiConfiguration,
|
||||
apiProvider: clineProvider,
|
||||
clineApiKey: apiKey,
|
||||
}
|
||||
|
||||
if (this.task) {
|
||||
@@ -487,20 +485,17 @@ export class Controller {
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
// vscode.window.showInformationMessage("Successfully logged in to Cline")
|
||||
} catch (error) {
|
||||
console.error("Failed to handle auth callback:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to log in to Cline",
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("Failed to log in to Cline")
|
||||
// Even on login failure, we preserve any existing tokens
|
||||
// Only clear tokens on explicit logout
|
||||
}
|
||||
}
|
||||
|
||||
// MCP Marketplace
|
||||
|
||||
private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
|
||||
try {
|
||||
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
|
||||
@@ -529,12 +524,7 @@ export class Controller {
|
||||
console.error("Failed to fetch MCP marketplace:", error)
|
||||
if (!silent) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
@@ -618,12 +608,7 @@ export class Controller {
|
||||
} catch (error) {
|
||||
console.error("Failed to handle cached MCP marketplace:", error)
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,13 +834,10 @@ export class Controller {
|
||||
terminalOutputLineLimit,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
// Get current mode using helper function
|
||||
const currentMode = await this.getCurrentMode()
|
||||
|
||||
// Reconstruct ChatSettings with mode from global state and stored preferences
|
||||
// Reconstruct ChatSettings with in-memory mode and stored preferences
|
||||
const chatSettings: ChatSettings = {
|
||||
...storedChatSettings, // Spread stored preferences (preferredLanguage, openAIReasoningEffort)
|
||||
mode: currentMode, // Use mode from global state
|
||||
mode: this.mode, // Use in-memory mode (override any stored mode)
|
||||
}
|
||||
|
||||
const localClineRulesToggles =
|
||||
@@ -987,24 +969,14 @@ export class Controller {
|
||||
// Check if there's a workspace folder open
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No workspace folder open",
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("No workspace folder open")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the git diff
|
||||
const gitDiff = await getWorkingState(cwd)
|
||||
if (gitDiff === "No changes in working directory") {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "No changes in workspace for commit message",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("No changes in workspace for commit message")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1071,59 +1043,27 @@ Commit message:`
|
||||
if (api && api.repositories.length > 0) {
|
||||
const repo = api.repositories[0]
|
||||
repo.inputBox.value = commitMessage
|
||||
const message = "Commit message generated and applied"
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Commit message generated and applied")
|
||||
} else {
|
||||
const message = "No Git repositories found"
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("No Git repositories found")
|
||||
}
|
||||
} else {
|
||||
const message = "Git extension not found"
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("Git extension not found")
|
||||
}
|
||||
} else {
|
||||
const message = "Failed to generate commit message"
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("Failed to generate commit message")
|
||||
}
|
||||
} catch (innerError) {
|
||||
const innerErrorMessage = innerError instanceof Error ? innerError.message : String(innerError)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${innerErrorMessage}`,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(`Failed to generate commit message: ${innerErrorMessage}`)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${errorMessage}`,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(`Failed to generate commit message: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
// dev
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Controller } from ".."
|
||||
import { StringRequest } from "../../../shared/proto/common"
|
||||
import { McpDownloadResponse } from "../../../shared/proto/mcp"
|
||||
import { McpServer } from "@shared/mcp"
|
||||
import { Empty, StringRequest } from "../../../shared/proto/common"
|
||||
import { McpServer, McpDownloadResponse } from "@shared/mcp"
|
||||
import axios from "axios"
|
||||
import * as vscode from "vscode"
|
||||
import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
|
||||
@@ -10,9 +9,9 @@ import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
|
||||
* Download an MCP server from the marketplace
|
||||
* @param controller The controller instance
|
||||
* @param request The request containing the MCP ID
|
||||
* @returns MCP download response with details or error
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function downloadMcp(controller: Controller, request: StringRequest): Promise<McpDownloadResponse> {
|
||||
export async function downloadMcp(controller: Controller, request: StringRequest): Promise<Empty> {
|
||||
try {
|
||||
// Check if mcpId is provided
|
||||
if (!request.value) {
|
||||
@@ -55,6 +54,12 @@ export async function downloadMcp(controller: Controller, request: StringRequest
|
||||
throw new Error("Missing README content in MCP download response")
|
||||
}
|
||||
|
||||
// Send details to webview
|
||||
await controller.postMessageToWebview({
|
||||
type: "mcpDownloadDetails",
|
||||
mcpDownloadDetails: mcpDetails,
|
||||
})
|
||||
|
||||
// Create task with context from README and added guidelines for MCP server installation
|
||||
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
|
||||
- Start by loading the MCP documentation.
|
||||
@@ -75,17 +80,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
await controller.initTask(task)
|
||||
await sendChatButtonClickedEvent(controller.id)
|
||||
|
||||
// Return the download details directly
|
||||
return McpDownloadResponse.create({
|
||||
mcpId: mcpDetails.mcpId,
|
||||
githubUrl: mcpDetails.githubUrl,
|
||||
name: mcpDetails.name,
|
||||
author: mcpDetails.author,
|
||||
description: mcpDetails.description,
|
||||
readmeContent: mcpDetails.readmeContent,
|
||||
llmsInstallationContent: mcpDetails.llmsInstallationContent,
|
||||
requiresApiKey: mcpDetails.requiresApiKey,
|
||||
})
|
||||
// Return an empty response - the client only cares if the call succeeded
|
||||
return Empty.create()
|
||||
} catch (error) {
|
||||
console.error("Failed to download MCP:", error)
|
||||
let errorMessage = "Failed to download MCP"
|
||||
@@ -104,17 +100,13 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
// Return error in the response instead of throwing
|
||||
return McpDownloadResponse.create({
|
||||
mcpId: "",
|
||||
githubUrl: "",
|
||||
name: "",
|
||||
author: "",
|
||||
description: "",
|
||||
readmeContent: "",
|
||||
llmsInstallationContent: "",
|
||||
requiresApiKey: false,
|
||||
// Show error in both notification and marketplace UI
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
await controller.postMessageToWebview({
|
||||
type: "mcpDownloadDetails",
|
||||
error: errorMessage,
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export async function refreshOpenRouterModels(
|
||||
break
|
||||
case "x-ai/grok-3-beta":
|
||||
modelInfo.supportsPromptCache = true
|
||||
modelInfo.cacheWritesPrice = 0.75
|
||||
modelInfo.cacheWritesPrice = 0
|
||||
modelInfo.cacheReadsPrice = 0
|
||||
break
|
||||
default:
|
||||
@@ -122,6 +122,11 @@ export async function refreshOpenRouterModels(
|
||||
break
|
||||
}
|
||||
|
||||
// add new model id
|
||||
if (rawModel.id === "x-ai/grok-3-beta") {
|
||||
models["x-ai/grok-3"] = modelInfo
|
||||
}
|
||||
|
||||
models[rawModel.id] = modelInfo
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,9 +2,8 @@ import { Controller } from ".."
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { ResetStateRequest } from "../../../shared/proto/state"
|
||||
import { resetGlobalState, resetWorkspaceState } from "../../../core/storage/state"
|
||||
import * as vscode from "vscode"
|
||||
import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
|
||||
/**
|
||||
* Resets the extension state to its defaults
|
||||
@@ -15,20 +14,10 @@ import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
export async function resetState(controller: Controller, request: ResetStateRequest): Promise<Empty> {
|
||||
try {
|
||||
if (request.global) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting global state...",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Resetting global state...")
|
||||
await resetGlobalState(controller.context)
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting workspace state...",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Resetting workspace state...")
|
||||
await resetWorkspaceState(controller.context)
|
||||
}
|
||||
|
||||
@@ -37,12 +26,7 @@ export async function resetState(controller: Controller, request: ResetStateRequ
|
||||
controller.task = undefined
|
||||
}
|
||||
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "State reset",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("State reset")
|
||||
await controller.postStateToWebview()
|
||||
|
||||
await sendChatButtonClickedEvent(controller.id)
|
||||
@@ -50,12 +34,7 @@ export async function resetState(controller: Controller, request: ResetStateRequ
|
||||
return Empty.create()
|
||||
} catch (error) {
|
||||
console.error("Error resetting state:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(`Failed to reset state: ${error instanceof Error ? error.message : String(error)}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Controller } from "../index"
|
||||
import * as proto from "@/shared/proto"
|
||||
import { updateGlobalState } from "../../storage/state"
|
||||
import { TerminalInfo } from "@/integrations/terminal/TerminalRegistry"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
export async function updateDefaultTerminalProfile(
|
||||
controller: Controller,
|
||||
@@ -26,25 +25,16 @@ export async function updateDefaultTerminalProfile(
|
||||
|
||||
// Show information message if terminals were closed
|
||||
if (closedCount > 0) {
|
||||
const message = `Closed ${closedCount} ${closedCount === 1 ? "terminal" : "terminals"} with different profile.`
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
vscode.window.showInformationMessage(
|
||||
`Closed ${closedCount} ${closedCount === 1 ? "terminal" : "terminals"} with different profile.`,
|
||||
)
|
||||
}
|
||||
|
||||
// Show warning if there are busy terminals that couldn't be closed
|
||||
if (busyTerminals.length > 0) {
|
||||
const message =
|
||||
vscode.window.showWarningMessage(
|
||||
`${busyTerminals.length} busy ${busyTerminals.length === 1 ? "terminal has" : "terminals have"} a different profile. ` +
|
||||
`Close ${busyTerminals.length === 1 ? "it" : "them"} to use the new profile for all commands.`
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
}),
|
||||
`Close ${busyTerminals.length === 1 ? "it" : "them"} to use the new profile for all commands.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,16 +58,7 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
// Update chat settings
|
||||
if (request.chatSettings) {
|
||||
const chatSettings = convertProtoChatSettingsToChatSettings(request.chatSettings)
|
||||
|
||||
// Store mode to global state
|
||||
if (chatSettings.mode !== undefined) {
|
||||
await controller.context.globalState.update("mode", chatSettings.mode)
|
||||
}
|
||||
|
||||
// Store chat settings (excluding mode) to global state
|
||||
const { mode, ...globalChatSettings } = chatSettings
|
||||
await controller.context.globalState.update("chatSettings", globalChatSettings)
|
||||
|
||||
await controller.context.workspaceState.update("chatSettings", chatSettings)
|
||||
if (controller.task) {
|
||||
controller.task.chatSettings = chatSettings
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import { Controller } from ".."
|
||||
import { DeleteAllTaskHistoryCount } from "../../../shared/proto/task"
|
||||
import { getGlobalState, updateGlobalState } from "../../storage/state"
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import vscode from "vscode"
|
||||
|
||||
/**
|
||||
* Deletes all task history, with an option to preserve favorites
|
||||
@@ -22,18 +21,12 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
const taskHistory = ((await getGlobalState(controller.context, "taskHistory")) as any[]) || []
|
||||
const totalTasks = taskHistory.length
|
||||
|
||||
const userChoice = (
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message: "What would you like to delete?",
|
||||
options: {
|
||||
modal: true,
|
||||
items: ["Delete All Except Favorites", "Delete Everything"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
const userChoice = await vscode.window.showWarningMessage(
|
||||
"What would you like to delete?",
|
||||
{ modal: true },
|
||||
"Delete All Except Favorites",
|
||||
"Delete Everything",
|
||||
)
|
||||
|
||||
// Default VS Code Cancel button returns `undefined` - don't delete anything
|
||||
if (userChoice === undefined) {
|
||||
@@ -66,18 +59,11 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
})
|
||||
} else {
|
||||
// No favorited tasks found - show warning and ask user what to do
|
||||
const answer = (
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message: "No favorited tasks found. Would you like to delete all tasks anyway?",
|
||||
options: {
|
||||
modal: true,
|
||||
items: ["Delete All Tasks"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
const answer = await vscode.window.showWarningMessage(
|
||||
"No favorited tasks found. Would you like to delete all tasks anyway?",
|
||||
{ modal: true },
|
||||
"Delete All Tasks",
|
||||
)
|
||||
|
||||
// User cancelled - don't delete anything
|
||||
if (answer === undefined) {
|
||||
@@ -105,11 +91,8 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
await fs.rm(checkpointsDirPath, { recursive: true, force: true })
|
||||
}
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
vscode.window.showErrorMessage(
|
||||
`Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import vscode from "vscode"
|
||||
import { Controller } from ".."
|
||||
import { Empty, StringArrayRequest } from "../../../shared/proto/common"
|
||||
import { Empty, StringArrayRequest, BooleanRequest } from "../../../shared/proto/common"
|
||||
import { TaskMethodHandler } from "./index"
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
/**
|
||||
* Deletes tasks with the specified IDs
|
||||
@@ -28,13 +27,7 @@ export const deleteTasksWithIds: TaskMethodHandler = async (
|
||||
? "Are you sure you want to delete this task? This action cannot be undone."
|
||||
: `Are you sure you want to delete these ${taskCount} tasks? This action cannot be undone.`
|
||||
|
||||
const userChoice = await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
options: { modal: true, items: ["Delete"] },
|
||||
}),
|
||||
)
|
||||
const userChoice = await vscode.window.showWarningMessage(message, { modal: true }, "Delete")
|
||||
|
||||
if (userChoice === undefined) {
|
||||
return Empty.create()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Controller } from ".."
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { NewTaskRequest } from "../../../shared/proto/task"
|
||||
import { handleFileServiceRequest } from "../file"
|
||||
|
||||
/**
|
||||
* Creates a new task with the given text and optional images
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Controller } from ".."
|
||||
import { Empty, StringRequest } from "../../../shared/proto/common"
|
||||
import { openExternal } from "@utils/env"
|
||||
|
||||
/**
|
||||
* Opens a URL in the user's default browser
|
||||
@@ -11,7 +11,7 @@ import { openExternal } from "@utils/env"
|
||||
export async function openInBrowser(controller: Controller, request: StringRequest): Promise<Empty> {
|
||||
try {
|
||||
if (request.value) {
|
||||
await openExternal(request.value)
|
||||
await vscode.env.openExternal(vscode.Uri.parse(request.value))
|
||||
}
|
||||
return Empty.create()
|
||||
} catch (error) {
|
||||
|
||||
+10
-19
@@ -12,9 +12,6 @@ import { getCommitInfo } from "@utils/git"
|
||||
import { getWorkingState } from "@utils/git"
|
||||
import { FileContextTracker } from "../context/context-tracking/FileContextTracker"
|
||||
import { getCwd } from "@/utils/path"
|
||||
import { openExternal } from "@utils/env"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
export async function openMention(mention?: string): Promise<void> {
|
||||
if (!mention) {
|
||||
@@ -39,7 +36,7 @@ export async function openMention(mention?: string): Promise<void> {
|
||||
} else if (mention === "terminal") {
|
||||
vscode.commands.executeCommand("workbench.action.terminal.focus")
|
||||
} else if (mention.startsWith("http")) {
|
||||
await openExternal(mention)
|
||||
vscode.env.openExternal(vscode.Uri.parse(mention))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +75,7 @@ export async function parseMentions(
|
||||
await urlContentFetcher.launchBrowser()
|
||||
} catch (error) {
|
||||
launchBrowserError = error
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${urlMention}: ${error.message}`,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(`Error fetching content for ${urlMention}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +92,7 @@ export async function parseMentions(
|
||||
const markdown = await urlContentFetcher.urlToMarkdown(mention)
|
||||
result = markdown
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${mention}: ${error.message}`,
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage(`Error fetching content for ${mention}: ${error.message}`)
|
||||
result = `Error fetching content: ${error.message}`
|
||||
}
|
||||
}
|
||||
@@ -132,7 +119,7 @@ export async function parseMentions(
|
||||
}
|
||||
} else if (mention === "problems") {
|
||||
try {
|
||||
const problems = await getWorkspaceProblems()
|
||||
const problems = getWorkspaceProblems(cwd)
|
||||
parsedText += `\n\n<workspace_diagnostics>\n${problems}\n</workspace_diagnostics>`
|
||||
} catch (error) {
|
||||
parsedText += `\n\n<workspace_diagnostics>\nError fetching diagnostics: ${error.message}\n</workspace_diagnostics>`
|
||||
@@ -228,9 +215,13 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
|
||||
}
|
||||
}
|
||||
|
||||
async function getWorkspaceProblems(): Promise<string> {
|
||||
function getWorkspaceProblems(cwd: string): string {
|
||||
const diagnostics = vscode.languages.getDiagnostics()
|
||||
const result = diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning])
|
||||
const result = diagnosticsToProblemsString(
|
||||
diagnostics,
|
||||
[vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning],
|
||||
cwd,
|
||||
)
|
||||
if (!result) {
|
||||
return "No errors or warnings detected."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type SecretKey =
|
||||
| "apiKey"
|
||||
| "clineAccountId"
|
||||
| "clineApiKey"
|
||||
| "openRouterApiKey"
|
||||
| "awsAccessKey"
|
||||
| "awsSecretKey"
|
||||
@@ -80,7 +80,6 @@ export type GlobalStateKey =
|
||||
| "claudeCodePath"
|
||||
// Settings around plan/act and ephemeral model configuration
|
||||
| "chatSettings"
|
||||
| "mode"
|
||||
// Current active model configuration (per workspace)
|
||||
| "apiProvider"
|
||||
| "apiModelId"
|
||||
|
||||
@@ -202,7 +202,7 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
|
||||
config.doubaoApiKey,
|
||||
config.mistralApiKey,
|
||||
config.vsCodeLmModelSelector,
|
||||
config.clineAccountId,
|
||||
config.clineApiKey,
|
||||
config.asksageApiKey,
|
||||
config.xaiApiKey,
|
||||
config.sambanovaApiKey,
|
||||
|
||||
+85
-155
@@ -18,66 +18,19 @@ import { migrateEnableCheckpointsSetting, migrateMcpMarketplaceEnableSetting } f
|
||||
https://www.eliostruyf.com/devhack-code-extension-storage-options/
|
||||
*/
|
||||
|
||||
const isTemporaryProfile = process.env.TEMP_PROFILE === "true"
|
||||
|
||||
// In-memory storage for temporary profiles
|
||||
const inMemoryGlobalState = new Map<string, any>()
|
||||
const inMemoryWorkspaceState = new Map<string, any>()
|
||||
const inMemorySecrets = new Map<string, string>()
|
||||
|
||||
// global
|
||||
|
||||
export async function updateGlobalState(context: vscode.ExtensionContext, key: GlobalStateKey, value: any) {
|
||||
if (isTemporaryProfile) {
|
||||
inMemoryGlobalState.set(key, value)
|
||||
return
|
||||
}
|
||||
await context.globalState.update(key, value)
|
||||
}
|
||||
|
||||
export async function getGlobalState(context: vscode.ExtensionContext, key: GlobalStateKey) {
|
||||
if (isTemporaryProfile) {
|
||||
return inMemoryGlobalState.get(key)
|
||||
}
|
||||
return await context.globalState.get(key)
|
||||
}
|
||||
|
||||
// Batched operations for performance optimization
|
||||
export async function updateGlobalStateBatch(context: vscode.ExtensionContext, updates: Record<string, any>) {
|
||||
if (isTemporaryProfile) {
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
inMemoryGlobalState.set(key, value)
|
||||
})
|
||||
return
|
||||
}
|
||||
// Use Promise.all to batch the updates
|
||||
await Promise.all(Object.entries(updates).map(([key, value]) => context.globalState.update(key as GlobalStateKey, value)))
|
||||
}
|
||||
|
||||
export async function updateSecretsBatch(context: vscode.ExtensionContext, updates: Record<string, string | undefined>) {
|
||||
if (isTemporaryProfile) {
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
inMemorySecrets.set(key, value)
|
||||
} else {
|
||||
inMemorySecrets.delete(key)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
// Use Promise.all to batch the secret updates
|
||||
await Promise.all(Object.entries(updates).map(([key, value]) => storeSecret(context, key as SecretKey, value)))
|
||||
}
|
||||
|
||||
// secrets
|
||||
|
||||
export async function storeSecret(context: vscode.ExtensionContext, key: SecretKey, value?: string) {
|
||||
if (isTemporaryProfile) {
|
||||
if (value) {
|
||||
inMemorySecrets.set(key, value)
|
||||
} else {
|
||||
inMemorySecrets.delete(key)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (value) {
|
||||
await context.secrets.store(key, value)
|
||||
} else {
|
||||
@@ -86,36 +39,26 @@ export async function storeSecret(context: vscode.ExtensionContext, key: SecretK
|
||||
}
|
||||
|
||||
export async function getSecret(context: vscode.ExtensionContext, key: SecretKey) {
|
||||
if (isTemporaryProfile) {
|
||||
return inMemorySecrets.get(key)
|
||||
}
|
||||
return await context.secrets.get(key)
|
||||
}
|
||||
|
||||
// workspace
|
||||
|
||||
export async function updateWorkspaceState(context: vscode.ExtensionContext, key: LocalStateKey, value: any) {
|
||||
if (isTemporaryProfile) {
|
||||
inMemoryWorkspaceState.set(key, value)
|
||||
return
|
||||
}
|
||||
await context.workspaceState.update(key, value)
|
||||
}
|
||||
|
||||
export async function getWorkspaceState(context: vscode.ExtensionContext, key: LocalStateKey) {
|
||||
if (isTemporaryProfile) {
|
||||
return inMemoryWorkspaceState.get(key)
|
||||
}
|
||||
return await context.workspaceState.get(key)
|
||||
}
|
||||
|
||||
export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
const firstBatchStart = performance.now()
|
||||
const [
|
||||
isNewUser,
|
||||
welcomeViewCompleted,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineAccountId,
|
||||
clineApiKey,
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsSessionToken,
|
||||
@@ -188,7 +131,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "welcomeViewCompleted") as Promise<boolean | undefined>,
|
||||
getSecret(context, "apiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "openRouterApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "clineAccountId") as Promise<string | undefined>,
|
||||
getSecret(context, "clineApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "awsAccessKey") as Promise<string | undefined>,
|
||||
getSecret(context, "awsSecretKey") as Promise<string | undefined>,
|
||||
getSecret(context, "awsSessionToken") as Promise<string | undefined>,
|
||||
@@ -260,10 +203,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
|
||||
|
||||
const secondBatchStart = performance.now()
|
||||
const [
|
||||
chatSettings,
|
||||
currentMode,
|
||||
storedApiProvider,
|
||||
apiModelId,
|
||||
thinkingBudgetTokens,
|
||||
@@ -295,7 +236,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
sapAiCoreModelId,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "chatSettings") as Promise<StoredChatSettings | undefined>,
|
||||
getGlobalState(context, "mode") as Promise<"plan" | "act" | undefined>,
|
||||
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
|
||||
@@ -327,7 +267,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "sapAiCoreModelId") as Promise<string | undefined>,
|
||||
])
|
||||
|
||||
const processingStart = performance.now()
|
||||
let apiProvider: ApiProvider
|
||||
if (storedApiProvider) {
|
||||
apiProvider = storedApiProvider
|
||||
@@ -370,7 +309,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineAccountId,
|
||||
clineApiKey,
|
||||
claudeCodePath,
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
@@ -450,8 +389,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
browserSettings: { ...DEFAULT_BROWSER_SETTINGS, ...browserSettings }, // this will ensure that older versions of browserSettings (e.g. before remoteBrowserEnabled was added) are merged with the default values (false for remoteBrowserEnabled)
|
||||
chatSettings: {
|
||||
...DEFAULT_CHAT_SETTINGS, // Apply defaults first
|
||||
...(chatSettings || {}), // Spread fetched global chatSettings, which includes preferredLanguage, and openAIReasoningEffort
|
||||
mode: currentMode || "act", // Merge mode from global state
|
||||
...(chatSettings || {}), // Spread fetched chatSettings, which includes preferredLanguage, and openAIReasoningEffort
|
||||
},
|
||||
userInfo,
|
||||
previousModeApiProvider,
|
||||
@@ -535,7 +473,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
xaiApiKey,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
clineAccountId,
|
||||
clineApiKey,
|
||||
sambanovaApiKey,
|
||||
cerebrasApiKey,
|
||||
nebiusApiKey,
|
||||
@@ -553,92 +491,84 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
claudeCodePath,
|
||||
} = apiConfiguration
|
||||
|
||||
// OPTIMIZED: Batch all global state updates into 2 operations instead of 47
|
||||
const batchedGlobalUpdates = {
|
||||
// Ephemeral model config updates (20 keys)
|
||||
apiProvider,
|
||||
apiModelId,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
vsCodeLmModelSelector,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
ollamaModelId,
|
||||
lmStudioModelId,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherModelId,
|
||||
fireworksModelId,
|
||||
sapAiCoreModelId,
|
||||
// Ephemeral model config updates
|
||||
await updateGlobalState(context, "apiProvider", apiProvider)
|
||||
await updateGlobalState(context, "apiModelId", apiModelId)
|
||||
await updateGlobalState(context, "thinkingBudgetTokens", thinkingBudgetTokens)
|
||||
await updateGlobalState(context, "reasoningEffort", reasoningEffort)
|
||||
await updateGlobalState(context, "vsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
await updateGlobalState(context, "awsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await updateGlobalState(context, "awsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await updateGlobalState(context, "openRouterModelId", openRouterModelId)
|
||||
await updateGlobalState(context, "openRouterModelInfo", openRouterModelInfo)
|
||||
await updateGlobalState(context, "openAiModelId", openAiModelId)
|
||||
await updateGlobalState(context, "openAiModelInfo", openAiModelInfo)
|
||||
await updateGlobalState(context, "ollamaModelId", ollamaModelId)
|
||||
await updateGlobalState(context, "lmStudioModelId", lmStudioModelId)
|
||||
await updateGlobalState(context, "liteLlmModelId", liteLlmModelId)
|
||||
await updateGlobalState(context, "liteLlmModelInfo", liteLlmModelInfo)
|
||||
await updateGlobalState(context, "requestyModelId", requestyModelId)
|
||||
await updateGlobalState(context, "requestyModelInfo", requestyModelInfo)
|
||||
await updateGlobalState(context, "togetherModelId", togetherModelId)
|
||||
await updateGlobalState(context, "fireworksModelId", fireworksModelId)
|
||||
await updateGlobalState(context, "sapAiCoreModelId", sapAiCoreModelId)
|
||||
|
||||
// Global state updates (27 keys)
|
||||
awsRegion,
|
||||
awsUseCrossRegionInference,
|
||||
awsBedrockUsePromptCache,
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
openAiHeaders: openAiHeaders || {},
|
||||
ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum,
|
||||
lmStudioBaseUrl,
|
||||
anthropicBaseUrl,
|
||||
geminiBaseUrl,
|
||||
azureApiVersion,
|
||||
openRouterProviderSorting,
|
||||
liteLlmBaseUrl,
|
||||
liteLlmUsePromptCache,
|
||||
qwenApiLine,
|
||||
asksageApiUrl,
|
||||
favoritedModelIds,
|
||||
requestTimeoutMs: apiConfiguration.requestTimeoutMs,
|
||||
fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens,
|
||||
sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
claudeCodePath,
|
||||
}
|
||||
// Global state updates
|
||||
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, "vertexProjectId", vertexProjectId)
|
||||
await updateGlobalState(context, "vertexRegion", vertexRegion)
|
||||
await updateGlobalState(context, "openAiBaseUrl", openAiBaseUrl)
|
||||
await updateGlobalState(context, "openAiHeaders", openAiHeaders || {})
|
||||
await updateGlobalState(context, "ollamaBaseUrl", ollamaBaseUrl)
|
||||
await updateGlobalState(context, "ollamaApiOptionsCtxNum", ollamaApiOptionsCtxNum)
|
||||
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)
|
||||
await updateGlobalState(context, "sapAiCoreBaseUrl", sapAiCoreBaseUrl)
|
||||
await updateGlobalState(context, "sapAiCoreTokenUrl", sapAiCoreTokenUrl)
|
||||
await updateGlobalState(context, "sapAiResourceGroup", sapAiResourceGroup)
|
||||
await updateGlobalState(context, "claudeCodePath", claudeCodePath)
|
||||
|
||||
// OPTIMIZED: Batch all secret updates into 1 operation instead of 23
|
||||
const batchedSecretUpdates = {
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineAccountId,
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsSessionToken,
|
||||
openAiApiKey,
|
||||
geminiApiKey,
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
togetherApiKey,
|
||||
qwenApiKey,
|
||||
doubaoApiKey,
|
||||
mistralApiKey,
|
||||
liteLlmApiKey,
|
||||
fireworksApiKey,
|
||||
asksageApiKey,
|
||||
xaiApiKey,
|
||||
sambanovaApiKey,
|
||||
cerebrasApiKey,
|
||||
nebiusApiKey,
|
||||
sapAiCoreClientId,
|
||||
sapAiCoreClientSecret,
|
||||
}
|
||||
|
||||
// Execute batched operations in parallel for maximum performance
|
||||
await Promise.all([updateGlobalStateBatch(context, batchedGlobalUpdates), updateSecretsBatch(context, batchedSecretUpdates)])
|
||||
// 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 storeSecret(context, "openAiNativeApiKey", openAiNativeApiKey)
|
||||
await storeSecret(context, "deepSeekApiKey", deepSeekApiKey)
|
||||
await storeSecret(context, "requestyApiKey", requestyApiKey)
|
||||
await storeSecret(context, "togetherApiKey", togetherApiKey)
|
||||
await storeSecret(context, "qwenApiKey", qwenApiKey)
|
||||
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 storeSecret(context, "sambanovaApiKey", sambanovaApiKey)
|
||||
await storeSecret(context, "cerebrasApiKey", cerebrasApiKey)
|
||||
await storeSecret(context, "nebiusApiKey", nebiusApiKey)
|
||||
await storeSecret(context, "sapAiCoreClientId", sapAiCoreClientId)
|
||||
await storeSecret(context, "sapAiCoreClientSecret", sapAiCoreClientSecret)
|
||||
}
|
||||
|
||||
export async function resetWorkspaceState(context: vscode.ExtensionContext) {
|
||||
@@ -667,7 +597,7 @@ export async function resetGlobalState(context: vscode.ExtensionContext) {
|
||||
"qwenApiKey",
|
||||
"doubaoApiKey",
|
||||
"mistralApiKey",
|
||||
"clineAccountId",
|
||||
"clineApiKey",
|
||||
"liteLlmApiKey",
|
||||
"fireworksApiKey",
|
||||
"asksageApiKey",
|
||||
|
||||
+2
-16
@@ -174,7 +174,7 @@ export class Task {
|
||||
this.urlContentFetcher = new UrlContentFetcher(context)
|
||||
this.browserSession = new BrowserSession(context, browserSettings)
|
||||
this.contextManager = new ContextManager()
|
||||
this.diffViewProvider = new DiffViewProvider()
|
||||
this.diffViewProvider = new DiffViewProvider(cwd)
|
||||
this.autoApprovalSettings = autoApprovalSettings
|
||||
this.browserSettings = browserSettings
|
||||
this.chatSettings = chatSettings
|
||||
@@ -1723,7 +1723,7 @@ export class Task {
|
||||
await this.messageStateHandler.updateClineMessage(lastApiReqStartedIndex, {
|
||||
text: JSON.stringify({
|
||||
...currentApiReqInfo, // Spread the modified info (with retryStatus removed)
|
||||
// cancelReason: "retries_exhausted", // Indicate that automatic retries failed
|
||||
cancelReason: "retries_exhausted", // Indicate that automatic retries failed
|
||||
streamingFailedMessage: errorMessage,
|
||||
} satisfies ClineApiReqInfo),
|
||||
})
|
||||
@@ -2124,13 +2124,6 @@ export class Task {
|
||||
this.api.getModel().id,
|
||||
"assistant",
|
||||
true,
|
||||
{
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
// signals to provider that it can retrieve the saved messages from disk, as abortTask can not be awaited on in nature
|
||||
@@ -2304,13 +2297,6 @@ export class Task {
|
||||
this.api.getModel().id,
|
||||
"assistant",
|
||||
true,
|
||||
{
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
await this.messageStateHandler.addToApiConversationHistory({
|
||||
|
||||
@@ -10,8 +10,6 @@ import path from "node:path"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { Uri } from "vscode"
|
||||
import { ExtensionMessage } from "@/shared/ExtensionMessage"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
export abstract class WebviewProvider {
|
||||
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.
|
||||
@@ -262,12 +260,8 @@ export abstract class WebviewProvider {
|
||||
try {
|
||||
await axios.get(`http://${localServerUrl}`)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message:
|
||||
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
|
||||
}),
|
||||
vscode.window.showErrorMessage(
|
||||
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
|
||||
)
|
||||
|
||||
return this.getHtmlContent()
|
||||
|
||||
@@ -4,8 +4,6 @@ import * as path from "path"
|
||||
import { Controller } from "@core/controller"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
|
||||
/**
|
||||
* Registers development-only commands for task manipulation.
|
||||
@@ -98,13 +96,7 @@ export function registerTaskCommands(context: vscode.ExtensionContext, controlle
|
||||
// Update the UI to show the new tasks
|
||||
await controller.postStateToWebview()
|
||||
|
||||
const message = `Created ${tasksCount} test tasks`
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage(`Created ${tasksCount} test tasks`)
|
||||
},
|
||||
)
|
||||
}),
|
||||
|
||||
+16
-64
@@ -35,10 +35,8 @@ import * as hostProviders from "@hosts/host-providers"
|
||||
import { vscodeHostBridgeClient } from "@/hosts/vscode/client/host-grpc-client"
|
||||
import { VscodeWebviewProvider } from "./core/webview/VscodeWebviewProvider"
|
||||
import { ExtensionContext } from "vscode"
|
||||
import { AuthService } from "./services/auth/AuthService"
|
||||
import { writeTextToClipboard, readTextFromClipboard } from "@/utils/env"
|
||||
import { getHostBridgeProvider } from "@hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "./shared/proto/host/window"
|
||||
|
||||
/*
|
||||
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
|
||||
|
||||
@@ -105,12 +103,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const message = `Cline has been updated to v${currentVersion}`
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage(message)
|
||||
// Record that we've shown the popup for this version.
|
||||
await context.globalState.update("clineLastPopupNotificationVersion", currentVersion)
|
||||
}
|
||||
@@ -300,39 +293,24 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
break
|
||||
}
|
||||
case "/auth": {
|
||||
const authService = AuthService.getInstance()
|
||||
console.log("Auth callback received:", uri.toString())
|
||||
|
||||
const token = query.get("idToken")
|
||||
const token = query.get("token")
|
||||
const state = query.get("state")
|
||||
const provider = query.get("provider")
|
||||
const apiKey = query.get("apiKey")
|
||||
|
||||
console.log("Auth callback received:", {
|
||||
token: token,
|
||||
state: state,
|
||||
provider: provider,
|
||||
apiKey: apiKey,
|
||||
})
|
||||
|
||||
// Ask user to confirm on state mismatch. This enables signins initiated from
|
||||
// outside the extension (e.g. Cline web) to be handled correctly.
|
||||
if (authService.authNonce !== state) {
|
||||
const userConfirmation = (
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid auth state",
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
if (userConfirmation === "Cancel") {
|
||||
console.log("User declined to continue with auth callback due to state mismatch")
|
||||
return
|
||||
}
|
||||
// Validate state parameter
|
||||
if (!(await visibleWebview?.controller.validateAuthState(state))) {
|
||||
vscode.window.showErrorMessage("Invalid auth state")
|
||||
return
|
||||
}
|
||||
|
||||
if (token) {
|
||||
await visibleWebview?.controller.handleAuthCallback(token, provider)
|
||||
// await authService.handleAuthCallback(token)
|
||||
if (token && apiKey) {
|
||||
await visibleWebview?.controller.handleAuthCallback(token, apiKey)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -435,12 +413,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Ensure clipboard is restored even if an error occurs
|
||||
await writeTextToClipboard(tempCopyBuffer)
|
||||
console.error("Error getting terminal contents:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to get terminal contents",
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("Failed to get terminal contents")
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -576,12 +549,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Please select some code to explain.")
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
@@ -603,12 +571,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Please select some code to improve.")
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
@@ -673,11 +636,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
sendFocusChatInputEvent(clientId)
|
||||
} else {
|
||||
console.error("FocusChatInput: Could not find or activate a Cline webview to focus.")
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Could not activate Cline view. Please try opening it manually from the Activity Bar.",
|
||||
}),
|
||||
vscode.window.showErrorMessage(
|
||||
"Could not activate Cline view. Please try opening it manually from the Activity Bar.",
|
||||
)
|
||||
}
|
||||
telemetryService.captureButtonClick("command_focusChatInput", activeWebviewProvider?.controller.task?.taskId, true)
|
||||
@@ -712,14 +672,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}),
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
context.secrets.onDidChange((event) => {
|
||||
if (event.key === "clineAccountId") {
|
||||
AuthService.getInstance(context)?.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
return createClineAPI(outputChannel, sidebarWebview.controller)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
UriServiceClientInterface,
|
||||
WatchServiceClientInterface,
|
||||
WorkspaceServiceClientInterface,
|
||||
EnvServiceClientInterface,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
* Interface for host bridge client providers
|
||||
*/
|
||||
export interface HostBridgeClientProvider {
|
||||
uriServiceClient: UriServiceClientInterface
|
||||
watchServiceClient: WatchServiceClientInterface
|
||||
workspaceClient: WorkspaceServiceClientInterface
|
||||
envClient: EnvServiceClientInterface
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WebviewProvider } from "@core/webview"
|
||||
import { HostBridgeClientProvider } from "./host-provider-types"
|
||||
import { WebviewProviderType } from "@/shared/webview/types"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
/**
|
||||
* A function that creates WebviewProvider instances
|
||||
|
||||
@@ -3,6 +3,7 @@ import { HostBridgeClientProvider } from "@/hosts/host-provider-types"
|
||||
import * as host from "@shared/proto/index.host"
|
||||
|
||||
export const vscodeHostBridgeClient: HostBridgeClientProvider = {
|
||||
uriServiceClient: createGrpcClient(host.UriServiceDefinition),
|
||||
watchServiceClient: createGrpcClient(host.WatchServiceDefinition),
|
||||
workspaceClient: createGrpcClient(host.WorkspaceServiceDefinition),
|
||||
envClient: createGrpcClient(host.EnvServiceDefinition),
|
||||
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
import { Empty, StringRequest } from "@/shared/proto/common"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
export async function openExternal(request: StringRequest): Promise<Empty> {
|
||||
console.log("openExternal called with request:", request)
|
||||
await vscode.env.openExternal(vscode.Uri.parse(request.value))
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Uri } from "@shared/proto/host/uri"
|
||||
import { StringRequest } from "@shared/proto/common"
|
||||
|
||||
/**
|
||||
* Creates a file URI from a file path
|
||||
* @param request The request containing the file path
|
||||
* @returns A URI object representing the file
|
||||
*/
|
||||
export async function file(request: StringRequest): Promise<Uri> {
|
||||
const uri = vscode.Uri.file(request.value)
|
||||
return Uri.create({
|
||||
scheme: uri.scheme,
|
||||
authority: uri.authority,
|
||||
path: uri.path,
|
||||
query: uri.query,
|
||||
fragment: uri.fragment,
|
||||
fsPath: uri.fsPath,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as vscode from "vscode"
|
||||
import { JoinPathRequest, Uri } from "@shared/proto/host/uri"
|
||||
|
||||
/**
|
||||
* Joins a URI with additional path segments
|
||||
* @param request The request containing the base URI and path segments
|
||||
* @returns A new URI with the path segments joined
|
||||
*/
|
||||
export async function joinPath(request: JoinPathRequest): Promise<Uri> {
|
||||
// Convert proto Uri to vscode.Uri
|
||||
if (!request.base) {
|
||||
throw new Error("Base URI is required")
|
||||
}
|
||||
const baseUri = vscode.Uri.parse(`${request.base.scheme}://${request.base.authority}${request.base.path}`)
|
||||
|
||||
// Join paths
|
||||
const result = vscode.Uri.joinPath(baseUri, ...request.pathSegments)
|
||||
|
||||
// Convert back to proto Uri
|
||||
return Uri.create({
|
||||
scheme: result.scheme,
|
||||
authority: result.authority,
|
||||
path: result.path,
|
||||
query: result.query,
|
||||
fragment: result.fragment,
|
||||
fsPath: result.fsPath,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as vscode from "vscode"
|
||||
import { Uri } from "@shared/proto/host/uri"
|
||||
import { StringRequest } from "@shared/proto/common"
|
||||
|
||||
/**
|
||||
* Parses a string URI into a Uri object
|
||||
* @param request The request containing the URI string
|
||||
* @returns A URI object representing the parsed URI
|
||||
*/
|
||||
export async function parse(request: StringRequest): Promise<Uri> {
|
||||
const uri = vscode.Uri.parse(request.value)
|
||||
return Uri.create({
|
||||
scheme: uri.scheme,
|
||||
authority: uri.authority,
|
||||
path: uri.path,
|
||||
query: uri.query,
|
||||
fragment: uri.fragment,
|
||||
fsPath: uri.fsPath,
|
||||
})
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { window } from "vscode"
|
||||
import { SelectedResponse, ShowMessageRequest, ShowMessageType } from "@/shared/proto/index.host"
|
||||
|
||||
export async function showMessage(request: ShowMessageRequest): Promise<SelectedResponse | undefined> {
|
||||
const { message, type, options } = request
|
||||
const { modal, detail, items } = options || {}
|
||||
const option = items ? { modal, items } : { modal, detail }
|
||||
|
||||
let selectedOption: string | undefined = undefined
|
||||
|
||||
switch (type) {
|
||||
case ShowMessageType.ERROR:
|
||||
selectedOption = await window.showErrorMessage(message, option)
|
||||
break
|
||||
case ShowMessageType.WARNING:
|
||||
selectedOption = await window.showWarningMessage(message, option)
|
||||
break
|
||||
default:
|
||||
selectedOption = await window.showInformationMessage(message, option)
|
||||
break
|
||||
}
|
||||
|
||||
return SelectedResponse.create({ selectedOption })
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
import { getCwd } from "@/utils/path"
|
||||
|
||||
export function getNewDiagnostics(
|
||||
oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
|
||||
@@ -71,11 +70,11 @@ export function getNewDiagnostics(
|
||||
// // - New error in file3 (1:1)
|
||||
|
||||
// will return empty string if no problems with the given severity are found
|
||||
export async function diagnosticsToProblemsString(
|
||||
export function diagnosticsToProblemsString(
|
||||
diagnostics: [vscode.Uri, vscode.Diagnostic[]][],
|
||||
severities: vscode.DiagnosticSeverity[],
|
||||
): Promise<string> {
|
||||
const cwd = await getCwd()
|
||||
cwd: string,
|
||||
): string {
|
||||
let result = ""
|
||||
for (const [uri, fileDiagnostics] of diagnostics) {
|
||||
const problems = fileDiagnostics.filter((d) => severities.includes(d.severity))
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import * as fs from "fs/promises"
|
||||
import { createDirectoriesForFile } from "@utils/fs"
|
||||
import { arePathsEqual, getCwd } from "@utils/path"
|
||||
import { arePathsEqual } from "@utils/path"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { DecorationController } from "./DecorationController"
|
||||
import * as diff from "diff"
|
||||
@@ -21,7 +21,6 @@ export class DiffViewProvider {
|
||||
private createdDirs: string[] = []
|
||||
private documentWasOpen = false
|
||||
private relPath?: string
|
||||
private absolutePath?: string
|
||||
private newContent?: string
|
||||
private activeDiffEditor?: vscode.TextEditor
|
||||
private fadedOverlayController?: DecorationController
|
||||
@@ -29,25 +28,32 @@ export class DiffViewProvider {
|
||||
private streamedLines: string[] = []
|
||||
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
private fileEncoding: string = "utf8"
|
||||
private lastFirstVisibleLine: number = 0
|
||||
private shouldAutoScroll: boolean = true
|
||||
private scrollListener?: vscode.Disposable
|
||||
|
||||
constructor() {}
|
||||
constructor(private cwd: string) {}
|
||||
|
||||
async open(relPath: string): Promise<void> {
|
||||
this.isEditing = true
|
||||
this.relPath = relPath
|
||||
this.absolutePath = path.resolve(await getCwd(), relPath)
|
||||
const fileExists = this.editType === "modify"
|
||||
|
||||
const absolutePath = path.resolve(this.cwd, relPath)
|
||||
this.isEditing = true
|
||||
this.shouldAutoScroll = true
|
||||
this.lastFirstVisibleLine = 0
|
||||
// if the file is already open, ensure it's not dirty before getting its contents
|
||||
if (fileExists) {
|
||||
const existingDocument = vscode.workspace.textDocuments.find((doc) =>
|
||||
arePathsEqual(doc.uri.fsPath, this.absolutePath),
|
||||
)
|
||||
const existingDocument = vscode.workspace.textDocuments.find((doc) => arePathsEqual(doc.uri.fsPath, absolutePath))
|
||||
if (existingDocument && existingDocument.isDirty) {
|
||||
await existingDocument.save()
|
||||
}
|
||||
}
|
||||
|
||||
const fileBuffer = await fs.readFile(this.absolutePath)
|
||||
// get diagnostics before editing the file, we'll compare to diagnostics after editing to see if cline needs to fix anything
|
||||
this.preDiagnostics = vscode.languages.getDiagnostics()
|
||||
|
||||
if (fileExists) {
|
||||
const fileBuffer = await fs.readFile(absolutePath)
|
||||
this.fileEncoding = await detectEncoding(fileBuffer)
|
||||
this.originalContent = iconv.decode(fileBuffer, this.fileEncoding)
|
||||
} else {
|
||||
@@ -55,14 +61,46 @@ export class DiffViewProvider {
|
||||
this.fileEncoding = "utf8"
|
||||
}
|
||||
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
||||
this.createdDirs = await createDirectoriesForFile(this.absolutePath)
|
||||
this.createdDirs = await createDirectoriesForFile(absolutePath)
|
||||
// make sure the file exists before we open it
|
||||
if (!fileExists) {
|
||||
await fs.writeFile(this.absolutePath, "")
|
||||
await fs.writeFile(absolutePath, "")
|
||||
}
|
||||
await this.openDiffEditor()
|
||||
// if the file was already open, close it (must happen after showing the diff view since if it's the only tab the column will close)
|
||||
this.documentWasOpen = false
|
||||
// close the tab if it's open (it's already saved above)
|
||||
const tabs = vscode.window.tabGroups.all
|
||||
.map((tg) => tg.tabs)
|
||||
.flat()
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath))
|
||||
for (const tab of tabs) {
|
||||
if (!tab.isDirty) {
|
||||
await vscode.window.tabGroups.close(tab)
|
||||
}
|
||||
this.documentWasOpen = true
|
||||
}
|
||||
this.activeDiffEditor = await this.openDiffEditor()
|
||||
this.fadedOverlayController = new DecorationController("fadedOverlay", this.activeDiffEditor)
|
||||
this.activeLineController = new DecorationController("activeLine", this.activeDiffEditor)
|
||||
// Apply faded overlay to all lines initially
|
||||
this.fadedOverlayController.addLines(0, this.activeDiffEditor.document.lineCount)
|
||||
this.scrollEditorToLine(0) // will this crash for new files?
|
||||
this.streamedLines = []
|
||||
|
||||
// Add scroll detection to disable auto-scrolling when user scrolls up
|
||||
this.scrollListener = vscode.window.onDidChangeTextEditorVisibleRanges((e: vscode.TextEditorVisibleRangesChangeEvent) => {
|
||||
if (e.textEditor === this.activeDiffEditor) {
|
||||
const currentFirstVisibleLine = e.visibleRanges[0]?.start.line || 0
|
||||
|
||||
// If the first visible line moved upward, user scrolled up
|
||||
// if (currentFirstVisibleLine < this.lastFirstVisibleLine) {
|
||||
// this.shouldAutoScroll = false
|
||||
// }
|
||||
|
||||
// Always update our tracking variable
|
||||
this.lastFirstVisibleLine = currentFirstVisibleLine
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -116,34 +154,36 @@ export class DiffViewProvider {
|
||||
this.activeLineController.setActiveLine(currentLine)
|
||||
this.fadedOverlayController.updateOverlayAfterLine(currentLine, document.lineCount)
|
||||
|
||||
// Scroll to the actual change location if provided.
|
||||
if (changeLocation) {
|
||||
// We have the actual location of the change, scroll to it
|
||||
const targetLine = changeLocation.startLine
|
||||
this.scrollEditorToLine(targetLine)
|
||||
} else {
|
||||
// Fallback to the old logic for non-replacement updates
|
||||
if (diffLines.length <= 5) {
|
||||
// For small changes, just jump directly to the line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
// Scroll to the actual change location if provided, otherwise use the old logic
|
||||
if (this.shouldAutoScroll) {
|
||||
if (changeLocation) {
|
||||
// We have the actual location of the change, scroll to it
|
||||
const targetLine = changeLocation.startLine
|
||||
this.scrollEditorToLine(targetLine)
|
||||
} else {
|
||||
// For larger changes, create a quick scrolling animation
|
||||
const startLine = this.streamedLines.length
|
||||
const endLine = currentLine
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
// Fallback to the old logic for non-replacement updates
|
||||
if (diffLines.length <= 5) {
|
||||
// For small changes, just jump directly to the line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
} else {
|
||||
// For larger changes, create a quick scrolling animation
|
||||
const startLine = this.streamedLines.length
|
||||
const endLine = currentLine
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor?.revealRange(
|
||||
new vscode.Range(line, 0, line, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor?.revealRange(
|
||||
new vscode.Range(line, 0, line, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
}
|
||||
// Ensure we end at the final line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
}
|
||||
// Ensure we end at the final line
|
||||
this.scrollEditorToLine(currentLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +225,7 @@ export class DiffViewProvider {
|
||||
finalContent: undefined,
|
||||
}
|
||||
}
|
||||
const absolutePath = path.resolve(this.cwd, this.relPath)
|
||||
const updatedDocument = this.activeDiffEditor.document
|
||||
|
||||
// get the contents before save operation which may do auto-formatting
|
||||
@@ -199,7 +240,7 @@ export class DiffViewProvider {
|
||||
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: this.absolutePath,
|
||||
path: absolutePath,
|
||||
options: ShowTextDocumentOptions.create({
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
@@ -226,9 +267,13 @@ export class DiffViewProvider {
|
||||
initial fix is usually correct and it may just take time for linters to catch up.
|
||||
*/
|
||||
const postDiagnostics = vscode.languages.getDiagnostics()
|
||||
const newProblems = await diagnosticsToProblemsString(getNewDiagnostics(this.preDiagnostics, postDiagnostics), [
|
||||
vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
|
||||
]) // will be empty string if no errors
|
||||
const newProblems = diagnosticsToProblemsString(
|
||||
getNewDiagnostics(this.preDiagnostics, postDiagnostics),
|
||||
[
|
||||
vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
|
||||
],
|
||||
this.cwd,
|
||||
) // will be empty string if no errors
|
||||
const newProblemsMessage =
|
||||
newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
|
||||
|
||||
@@ -268,23 +313,24 @@ export class DiffViewProvider {
|
||||
}
|
||||
|
||||
async revertChanges(): Promise<void> {
|
||||
if (!this.absolutePath || !this.activeDiffEditor) {
|
||||
if (!this.relPath || !this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const fileExists = this.editType === "modify"
|
||||
const updatedDocument = this.activeDiffEditor.document
|
||||
const absolutePath = path.resolve(this.cwd, this.relPath)
|
||||
if (!fileExists) {
|
||||
if (updatedDocument.isDirty) {
|
||||
await updatedDocument.save()
|
||||
}
|
||||
await this.closeAllDiffViews()
|
||||
await fs.unlink(this.absolutePath)
|
||||
await fs.unlink(absolutePath)
|
||||
// Remove only the directories we created, in reverse order
|
||||
for (let i = this.createdDirs.length - 1; i >= 0; i--) {
|
||||
await fs.rmdir(this.createdDirs[i])
|
||||
console.log(`Directory ${this.createdDirs[i]} has been deleted.`)
|
||||
}
|
||||
console.log(`File ${this.absolutePath} has been deleted.`)
|
||||
console.log(`File ${absolutePath} has been deleted.`)
|
||||
} else {
|
||||
// revert document
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
@@ -296,11 +342,11 @@ export class DiffViewProvider {
|
||||
// Apply the edit and save, since contents shouldn't have changed this won't show in local history unless of course the user made changes and saved during the edit
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
await updatedDocument.save()
|
||||
console.log(`File ${this.absolutePath} has been reverted to its original content.`)
|
||||
console.log(`File ${absolutePath} has been reverted to its original content.`)
|
||||
if (this.documentWasOpen) {
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: this.absolutePath,
|
||||
path: absolutePath,
|
||||
options: ShowTextDocumentOptions.create({
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
@@ -327,28 +373,11 @@ export class DiffViewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
private async openDiffEditor(): Promise<vscode.TextEditor> {
|
||||
if (!this.relPath) {
|
||||
throw new Error("No file path set")
|
||||
}
|
||||
// get diagnostics before editing the file, we'll compare to diagnostics after editing to see if cline needs to fix anything
|
||||
this.preDiagnostics = vscode.languages.getDiagnostics()
|
||||
|
||||
// if the file was already open, close it (must happen after showing the diff view since if it's the only tab the column will close)
|
||||
this.documentWasOpen = false
|
||||
// close the tab if it's open (it's already been saved)
|
||||
const tabs = vscode.window.tabGroups.all
|
||||
.map((tg) => tg.tabs)
|
||||
.flat()
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, this.absolutePath))
|
||||
for (const tab of tabs) {
|
||||
if (!tab.isDirty) {
|
||||
await vscode.window.tabGroups.close(tab)
|
||||
}
|
||||
this.documentWasOpen = true
|
||||
}
|
||||
|
||||
const uri = vscode.Uri.file(this.absolutePath)
|
||||
const uri = vscode.Uri.file(path.resolve(this.cwd, this.relPath))
|
||||
// If this diff editor is already open (ie if a previous write file was interrupted) then we should activate that instead of opening a new diff
|
||||
const diffTab = vscode.window.tabGroups.all
|
||||
.flatMap((group) => group.tabs)
|
||||
@@ -372,41 +401,35 @@ export class DiffViewProvider {
|
||||
if (!editor) {
|
||||
throw new Error("Failed to find opened text editor")
|
||||
}
|
||||
this.activeDiffEditor = editor
|
||||
} else {
|
||||
// Open new diff editor
|
||||
this.activeDiffEditor = await new Promise<vscode.TextEditor>((resolve, reject) => {
|
||||
const fileName = path.basename(uri.fsPath)
|
||||
const fileExists = this.editType === "modify"
|
||||
const disposable = vscode.window.onDidChangeActiveTextEditor((editor) => {
|
||||
if (editor && arePathsEqual(editor.document.uri.fsPath, uri.fsPath)) {
|
||||
disposable.dispose()
|
||||
resolve(editor)
|
||||
}
|
||||
})
|
||||
vscode.commands.executeCommand(
|
||||
"vscode.diff",
|
||||
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
|
||||
query: Buffer.from(this.originalContent ?? "").toString("base64"),
|
||||
}),
|
||||
uri,
|
||||
`${fileName}: ${fileExists ? "Original ↔ Cline's Changes" : "New File"} (Editable)`,
|
||||
{
|
||||
preserveFocus: true,
|
||||
},
|
||||
)
|
||||
// This may happen on very slow machines ie project idx
|
||||
setTimeout(() => {
|
||||
disposable.dispose()
|
||||
reject(new Error("Failed to open diff editor, please try again..."))
|
||||
}, 10_000)
|
||||
})
|
||||
return editor
|
||||
}
|
||||
|
||||
this.fadedOverlayController = new DecorationController("fadedOverlay", this.activeDiffEditor)
|
||||
this.activeLineController = new DecorationController("activeLine", this.activeDiffEditor)
|
||||
// Apply faded overlay to all lines initially
|
||||
this.fadedOverlayController.addLines(0, this.activeDiffEditor.document.lineCount)
|
||||
// Open new diff editor
|
||||
return new Promise<vscode.TextEditor>((resolve, reject) => {
|
||||
const fileName = path.basename(uri.fsPath)
|
||||
const fileExists = this.editType === "modify"
|
||||
const disposable = vscode.window.onDidChangeActiveTextEditor((editor) => {
|
||||
if (editor && arePathsEqual(editor.document.uri.fsPath, uri.fsPath)) {
|
||||
disposable.dispose()
|
||||
resolve(editor)
|
||||
}
|
||||
})
|
||||
vscode.commands.executeCommand(
|
||||
"vscode.diff",
|
||||
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
|
||||
query: Buffer.from(this.originalContent ?? "").toString("base64"),
|
||||
}),
|
||||
uri,
|
||||
`${fileName}: ${fileExists ? "Original ↔ Cline's Changes" : "New File"} (Editable)`,
|
||||
{
|
||||
preserveFocus: true,
|
||||
},
|
||||
)
|
||||
// This may happen on very slow machines ie project idx
|
||||
setTimeout(() => {
|
||||
disposable.dispose()
|
||||
reject(new Error("Failed to open diff editor, please try again..."))
|
||||
}, 10_000)
|
||||
})
|
||||
}
|
||||
|
||||
private scrollEditorToLine(line: number) {
|
||||
@@ -453,5 +476,15 @@ export class DiffViewProvider {
|
||||
this.activeLineController = undefined
|
||||
this.streamedLines = []
|
||||
this.preDiagnostics = []
|
||||
|
||||
// Clean up the scroll listener
|
||||
if (this.scrollListener) {
|
||||
this.scrollListener.dispose()
|
||||
this.scrollListener = undefined
|
||||
}
|
||||
|
||||
// Reset auto-scroll state
|
||||
this.shouldAutoScroll = true
|
||||
this.lastFirstVisibleLine = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as vscode from "vscode"
|
||||
import { openExternal } from "@utils/env"
|
||||
|
||||
/**
|
||||
* Detects potential AI-generated code omissions in the given file content.
|
||||
@@ -48,8 +47,10 @@ export function showOmissionWarning(originalFileContent: string, newFileContent:
|
||||
)
|
||||
.then((selection) => {
|
||||
if (selection === "Follow this guide to fix the issue") {
|
||||
openExternal(
|
||||
"https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments",
|
||||
vscode.env.openExternal(
|
||||
vscode.Uri.parse(
|
||||
"https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments",
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as vscode from "vscode"
|
||||
import { getWorkingState } from "@utils/git"
|
||||
import { writeTextToClipboard } from "@utils/env"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageType, ShowTextDocumentRequest, ShowMessageRequest } from "@/shared/proto/host/window"
|
||||
import { ShowTextDocumentRequest } from "@/shared/proto/host/window"
|
||||
|
||||
/**
|
||||
* Formats the git diff into a prompt for the AI
|
||||
* @param gitDiff The git diff to format
|
||||
@@ -59,12 +61,7 @@ export function extractCommitMessage(aiResponse: string): string {
|
||||
*/
|
||||
export async function copyCommitMessageToClipboard(message: string): Promise<void> {
|
||||
await writeTextToClipboard(message)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message copied to clipboard",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Commit message copied to clipboard")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,19 +73,13 @@ export async function showCommitMessageOptions(message: string): Promise<void> {
|
||||
const applyAction = "Apply to Git Input"
|
||||
const editAction = "Edit Message"
|
||||
|
||||
const selectedAction = (
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message generated",
|
||||
options: {
|
||||
modal: false,
|
||||
detail: message,
|
||||
items: [copyAction, applyAction, editAction],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
const selectedAction = await vscode.window.showInformationMessage(
|
||||
"Commit message generated",
|
||||
{ modal: false, detail: message },
|
||||
copyAction,
|
||||
applyAction,
|
||||
editAction,
|
||||
)
|
||||
|
||||
// Handle user dismissing the dialog (selectedAction is undefined)
|
||||
if (!selectedAction) {
|
||||
@@ -120,28 +111,13 @@ async function applyCommitMessageToGitInput(message: string): Promise<void> {
|
||||
if (api && api.repositories.length > 0) {
|
||||
const repo = api.repositories[0]
|
||||
repo.inputBox.value = message
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message applied to Git input",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Commit message applied to Git input")
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No Git repositories found",
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("No Git repositories found")
|
||||
await copyCommitMessageToClipboard(message)
|
||||
}
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Git extension not found",
|
||||
}),
|
||||
)
|
||||
vscode.window.showErrorMessage("Git extension not found")
|
||||
await copyCommitMessageToClipboard(message)
|
||||
}
|
||||
}
|
||||
@@ -161,10 +137,5 @@ async function editCommitMessage(message: string): Promise<void> {
|
||||
path: document.uri.fsPath,
|
||||
}),
|
||||
)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Edit the commit message and copy when ready",
|
||||
}),
|
||||
)
|
||||
vscode.window.showInformationMessage("Edit the commit message and copy when ready")
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import os from "os"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowTextDocumentRequest, ShowTextDocumentOptions, ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { writeFile } from "@utils/fs"
|
||||
import { ShowTextDocumentRequest, ShowTextDocumentOptions } from "@/shared/proto/host/window"
|
||||
|
||||
export async function downloadTask(dateTs: number, conversationHistory: Anthropic.MessageParam[]) {
|
||||
// File name
|
||||
@@ -40,7 +39,7 @@ export async function downloadTask(dateTs: number, conversationHistory: Anthropi
|
||||
if (saveUri) {
|
||||
try {
|
||||
// Write content to the selected location
|
||||
await writeFile(saveUri.fsPath, markdownContent)
|
||||
await vscode.workspace.fs.writeFile(saveUri, new TextEncoder().encode(markdownContent))
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: saveUri.fsPath,
|
||||
@@ -48,11 +47,8 @@ export async function downloadTask(dateTs: number, conversationHistory: Anthropi
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to save markdown file: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to save markdown file: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user