Compare commits

..
Author SHA1 Message Date
Jose R. Perez 9d45b4d073 Merge branch 'main' into jose/ui-refresh-01b 2025-05-06 11:33:15 -04:00
root 4bc23c152f Fix: Hide Approve/Reject buttons in Plan mode when not streaming 2025-05-05 22:39:59 -04:00
root 8a399580cf Fix: Hide Approve/Reject buttons in Plan mode when not streaming 2025-05-05 22:37:53 -04:00
root 1c555e768e feat: enhance SendButton and remove Cancel button
- Create dedicated SendButton component with VSCode Codicons
- Add custom SVG for Resume Task state with mode-specific colors
- Remove Cancel button during streaming per request
- Add tooltip and hover effects for improved UX
2025-05-05 12:45:43 -04:00
root 3286f831ef fix: reset didClickCancel when streaming starts to fix Cancel button disappearing bug
- Add useEffect hook to reset didClickCancel when streaming starts
- This ensures the Cancel button reappears when a new task is started after canceling a previous task
2025-05-05 12:12:53 -04:00
root 9ea5ee4335 feat: enhance SendButton with custom SVG for Resume Task state
- Replace HeroUI icons with VSCode Codicons for play/pause button
- Add custom SVG for Resume Task state with mode-specific colors
- Make button 3px bigger in Resume Task state
- Add hover effect with brightness filter
- Add tooltip for Resume Task state
- Hide original Resume Task and Cancel buttons
- Match colors with Plan/Act mode toggle
2025-05-05 12:05:00 -04:00
root a1ea9799ab UI Refresh: Modernize Chat Interface with Personalized Welcome Screen 2025-05-02 10:48:33 -04:00
root 7d5fce3e37 UI Refresh: Modernize Chat Interface with Personalized Welcome Screen 2025-05-02 10:42:50 -04:00
217 changed files with 2877 additions and 12139 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix minor visual issues with auto-approve menu
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
convert condense command to use grpc
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add the o4-mini model in the isOminiModel
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Allow option to collect events to send them in a bundle to avoid sending too many events
@@ -2,4 +2,4 @@
"claude-dev": minor
---
Add Fireworks API Provider
add open ai cache to ui
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Moved rule file conversions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
getRelativePaths protobus migration
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
downloadMcp protobus migration
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add confirmation dialog to Delete All History button
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
This UI refresh transforms Cline's interface from a technical, feature-focused experience to a personal, welcoming environment that greets users by name and invites them to start coding with the friendly tagline "Let the vibe coding begin." Visual improvements including rounded corners and distinct color-coding for Plan/Act modes create a more modern aesthetic while improving usability through better visual distinction between different states. By shifting from a capabilities-focused introduction to a personalized greeting with the Cline logo, the experience feels more human and approachable, potentially increasing user engagement and reducing the intimidation factor for those new to AI coding assistants.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Add npm script for issue creation
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Extend ReasoningEffort to non-o3-mini reasoning models for all providers
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
toggleFavoriteModel protobus migration
+31
View File
@@ -0,0 +1,31 @@
---
"claude-dev": minor
---
# Summary of Changes
1. __Created a New SendButton Component__
- Created a dedicated SendButton component in `webview-ui/src/components/chat/SendButton.tsx`
- Implemented VSCode Codicons instead of HeroUI icons
- Added dynamic behavior to switch between play/send and pause/cancel states
2. __Added Mode-Specific Styling__
- Implemented custom SVG for the Resume Task state
- Added color matching with Plan/Act mode toggle (purple for Plan mode, green for Act mode)
- Created hover effects with brightness filter
3. __Fixed Cancel Button Issues__
- Initially attempted to fix the Cancel button's visibility during streaming
- Ultimately removed the Cancel button completely during streaming per request
- Modified the condition to only show secondary buttons when not streaming
4. __Improved User Experience__
- Added tooltip for the Resume Task state
- Made the button 3px bigger in Resume Task state
- Ensured proper cursor behavior during streaming
These changes have enhanced the UI with native VSCode styling while addressing the issues with the Cancel button's behavior during streaming.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
updated gemini caching for OR and cline provider
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
adding activation events so cline is activated when vs code opens
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
adding quote reply support
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Make Previous Updates in the Announcement a dropdown
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Migrate prompting section to new docs
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
prevent IME composition Enter from autosending edited message
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
add ui for windsurf and cursor rules
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Batch selection and deletion of tasks in history
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Migrate getting-started section to new docs
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Task Timeline
+1 -1
View File
@@ -209,7 +209,7 @@ class Task {
switch (chunk.type) {
case "text":
// Parse into content blocks
this.assistantMessageContent = parseAssistantMessageV2(chunk.text)
this.assistantMessageContent = parseAssistantMessage(chunk.text)
// Present blocks to user
await this.presentAssistantMessage()
break
@@ -33,7 +33,6 @@ jobs:
uses: morfien101/actions-authorized-user@4a3cfbf0bcb3cafe4a71710a278920c5d94bb38b
with:
username: ${{ github.actor }}
org: ${{ github.repository_owner }}
team: "deployer"
github_token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
uses: ./.github/workflows/test.yml
publish:
needs: test
# needs: test
name: Publish Extension
runs-on: ubuntu-latest
environment: publish
-22
View File
@@ -16,28 +16,6 @@
"IS_DEV": "true",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
}
},
{
"name": "Run Extension (Fresh Install Mode)",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--profile-temp",
"--sync",
"off",
"--disable-extensions",
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}"
],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "clean-sandbox",
"internalConsoleOptions": "openOnSessionStart",
"postDebugTask": "stop",
"env": {
"IS_DEV": "true",
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
}
}
]
}
-6
View File
@@ -185,12 +185,6 @@
"label": "stop",
"command": "echo ${input:terminate}",
"type": "shell"
},
{
"label": "clean-sandbox",
"type": "shell",
"dependsOn": ["watch"],
"command": "rm -rf .vscode-dev"
}
],
"inputs": [
-55
View File
@@ -1,60 +1,5 @@
# Changelog
## [3.15.2]
- Added details to auto approve menu and more sensible default controls
- Add detailed configuration options for LiteLLM provider
- Add webview telemetry for users who have opted in to telemetry
- Update Gemini in OpenRouter/Cline providers to use implicit caching
- Fix freezing issues during rendering of large streaming text
- Fix grey screen webview crashes by releasing memory after every diff edit
- Fix breaking out of diff auto-scroll
- Fix IME composition Enter autosending edited message
## [3.15.1]
- Fix bug where PowerShell commands weren't given enough time before giving up and showing an error
## [3.15.0]
- Add Task Timeline visualization to tasks (Thanks eomcaleb!)
- Add cache to ui for OpenAi provider
- Add FeatureFlagProvider service for the Node.js extension side
- Add copy buttons to task header and assistant messages
- Add a more simplified home header was added
- Add ability to favorite a task, allowing it to be kept when clearing all tasks
- Add npm script for issue creation (Thanks DaveFres!)
- Add confirmation dialog to Delete All History button
- Add ability to allow the user to type their next message into the chat while Cline is taking action
- Add ability to generate commit message via cline (Thanks zapp88!)
- Add improvements to caching for gemini models on OpenRouter and Cline providers
- Add improvements to allow scrolling the file being edited.
- Add ui for windsurf and cursor rules
- Add mistral medium-3 model
- Add option to collect events to send them in a bundle to avoid sending too many events
- Add support to quote a previous message in chat
- Add support for Gemini Implicit Caching
- Add support for batch selection and deletion of tasks in history (Thanks danix800!)
- Update change suggested models
- Update fetch cache details from generation endpoint
- Update converted docs to Mintlify
- Update the isOminiModel to include o4-mini model (Thanks PeterDaveHello!)
- Update file size that can be read by Cline, allowing larger files
- Update defaults for bedrock API models (Thanks Watany!)
- Update to extend ReasoningEffort to non-o3-mini reasoning models for all providers (Thanks PeterDaveHello!)
- Update to give error when a user tries to upload an image larger than 7500x7500 pixels
- Update announcement so that previous updates are in a dropdown
- Update UI for auto approve with favorited settings
- Fix bug where certain terminal commands would lock you out of a task
- Fix only initialize posthog in the webview if the user has opted into telemetry
- Fix bug where autocapture was on for front-end telemetry
- Fix for markdown copy excessively escaping characters (Thanks weshoke!)
- Fix an issue where loading never finished when using an application inference profile for the model ID (Thanks WinterYukky!)
## [3.14.1]
- Disables autocaptures when initializing feature flags
## [3.14.0]
- Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile (Thanks @clicube!)
+3
View File
@@ -0,0 +1,3 @@
<svg width="115" height="121" viewBox="0 0 115 121" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M114.317 68.0068L107.099 53.5136V45.1668C107.099 31.3312 95.9942 20.1262 82.2992 20.1262H69.9623C70.8552 18.2924 71.3456 16.231 71.3456 14.0557C71.3456 6.36649 65.1583 0.144287 57.5123 0.144287C49.8662 0.144287 43.6789 6.36649 43.6789 14.0557C43.6789 16.231 44.1693 18.2924 45.0622 20.1262H32.7254C19.0303 20.1262 7.9259 31.3312 7.9259 45.1668V53.5136L0.556479 67.9689C-0.185493 69.4232 -0.185493 71.1558 0.556479 72.6102L7.9259 86.9011V95.2479C7.9259 109.083 19.0303 120.289 32.7254 120.289H82.2992C95.9942 120.289 107.099 109.083 107.099 95.2479V86.9011L114.305 72.5596C115.021 71.1306 115.021 69.4485 114.317 68.0068ZM49.9668 79.8189C49.9668 86.1043 44.8987 91.201 38.6486 91.201C32.3984 91.201 27.3303 86.1043 27.3303 79.8189V59.5841C27.3303 53.2986 32.3984 48.202 38.6486 48.202C44.8987 48.202 49.9668 53.2986 49.9668 59.5841V79.8189ZM86.4366 79.8189C86.4366 86.1043 81.3685 91.201 75.1184 91.201C68.8682 91.201 63.8002 86.1043 63.8002 79.8189V59.5841C63.8002 53.2986 68.8682 48.202 75.1184 48.202C81.3685 48.202 86.4366 53.2986 86.4366 59.5841V79.8189Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,121 +0,0 @@
---
title: "AWS Bedrock"
description: "Learn how to set up AWS Bedrock with Cline using credentials authentication. This guide covers AWS environment setup, regional access verification, and secure integration with the Cline VS Code extension."
---
### Overview
- **AWS Bedrock:** A fully managed service that offers access to leading generative AI models (e.g., Anthropic Claude, Amazon Titan) through AWS.\
[Learn more about AWS Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html).
- **Cline:** A VS Code extension that acts as a coding assistant by integrating with AI models—empowering developers to generate code, debug, and analyze data.
- **Enterprise Focus:** This guide is tailored for organizations with established AWS environments (using IAM roles, AWS SSO, AWS Organizations, etc.) to ensure secure and compliant usage.
---
### Step 1: Prepare Your AWS Environment
#### 1.1 Create or Use an IAM Role/User
1. **Sign in to the AWS Management Console:**\
[AWS Console](https://aws.amazon.com/console/)
2. **Access IAM:**
- Search for **IAM (Identity and Access Management)** in the AWS Console.
- Either create a new IAM user or use your enterprise's AWS SSO to assume a dedicated role for Bedrock access.
- [AWS IAM User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
#### 1.2 Attach the Required Policies
1. **Attach the Managed Policy:**
- Attach the **`AmazonBedrockFullAccess`** managed policy to your user/role.\
[View AmazonBedrockFullAccess Policy Details](https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html)
2. **Confirm Additional Permissions:**
- Ensure your policy includes permissions for model invocation (e.g., `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream`), model listing, and AWS Marketplace actions (like `aws-marketplace:Subscribe`).
- _Enterprise Tip:_ Apply least-privilege practices by scoping resource ARNs and using [Service Control Policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) to restrict access where necessary.
---
### Step 2: Verify Regional and Model Access
#### 2.1 Choose and Confirm a Region
1. **Select a Region:**\
AWS Bedrock is available in multiple regions (e.g., US East, Europe, Asia Pacific). Choose the region that meets your latency and compliance needs.\
[AWS Global Infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/)
2. **Verify Model Access:**
- In the AWS Bedrock console, confirm that the models your team requires (e.g., Anthropic Claude, Amazon Titan) are marked as "Access granted."
- **Note:** Some advanced models might require an [Inference Profile](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-prereq.html) if not available on-demand.
#### 2.2 Set Up AWS Marketplace Subscriptions (if needed)
1. **Subscribe to Third-Party Models:**
- Navigate to the AWS Bedrock console and locate the model subscription section.
- For models from third-party providers (e.g., Anthropic), accept the terms to subscribe.
- [AWS Marketplace](https://aws.amazon.com/marketplace/)
2. **Enterprise Tip:**
- Model subscriptions are often managed centrally. Confirm with your cloud team if a standard subscription process is in place.
---
### Step 3: Configure the Cline VS Code Extension
#### 3.1 Install and Open Cline
1. **Install VS Code:**\
Download from the [VS Code website](https://code.visualstudio.com/).
2. **Install the Cline Extension:**
- Open VS Code.
- Go to the Extensions Marketplace (`Ctrl+Shift+X` or `Cmd+Shift+X`).
- Search for **Cline** and install it.
#### 3.2 Configure Cline Settings
1. **Open Cline Settings:**
- Click on the settings ⚙️ to select your API Provider.
2. **Select AWS Bedrock as the API Provider:**
- From the API Provider dropdown, choose **AWS Bedrock**.
3. **Enter Your AWS Credentials:**
- Input your **Access Key** and **Secret Key** (or use temporary credentials if using AWS SSO).
- Specify the correct **AWS Region** (e.g., `us-east-1` or your enterprise-approved region).
4. **Select a Model:**
- Choose an on-demand model (e.g., **anthropic.claude-3-5-sonnet-20241022-v2:0**).
5. **Save and Test:**
- Click **Done/Save** to apply your settings.
- Test the integration by sending a simple prompt (e.g., "Generate a Python function to check if a number is prime.").
---
### Step 4: Security, Monitoring, and Best Practices
1. **Secure Access:**
- Prefer AWS SSO/federated roles over long-lived IAM credentials.
- [AWS IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
2. **Enhance Network Security:**
- Consider setting up [AWS PrivateLink](https://docs.aws.amazon.com/vpc/latest/userguide/endpoint-services-overview.html) to securely connect to Bedrock.
3. **Monitor and Log Activity:**
- Enable AWS CloudTrail to log Bedrock API calls.
- Use CloudWatch to monitor metrics like invocation count, latency, and token usage.
- Set up alerts for abnormal activity.
4. **Handle Errors and Manage Costs:**
- Implement exponential backoff for throttling errors.
- Use AWS Cost Explorer and set billing alerts to track usage.\
[AWS Cost Management](https://docs.aws.amazon.com/cost-management/latest/userguide/what-is-aws-cost-management.html)
5. **Regular Audits and Compliance:**
- Periodically review IAM roles and CloudTrail logs.
- Follow internal data privacy and governance policies.
---
### Conclusion
By following these steps, your enterprise team can securely integrate AWS Bedrock with the Cline VS Code extension to accelerate development:
1. **Prepare Your AWS Environment:** Create or use a secure IAM role/user, attach the `AmazonBedrockFullAccess` policy, and ensure necessary permissions.
2. **Verify Region and Model Access:** Confirm that your selected region supports your required models and subscribe via AWS Marketplace if needed.
3. **Configure Cline in VS Code:** Install and set up Cline with your AWS credentials and choose an appropriate model.
4. **Implement Security and Monitoring:** Use best practices for IAM, network security, monitoring, and cost management.
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your internal cloud team. Happy coding!
---
_This guide will be updated as AWS Bedrock and Cline evolve. Always refer to the latest documentation and internal policies for up-to-date practices._
@@ -1,42 +0,0 @@
---
title: "AWS Bedrock w/ Profile Authentication"
description: "Learn how to configure AWS Bedrock to use AWS Profiles for authentication with Cline, focusing on SSO/Federated roles for secure access."
---
### Overview
Cline offers the option of utilizing AWS credentials or AWS profiles to access AWS Bedrock services. SSO/Federated roles are suggested over Legacy IAM configuration; this guide describes how to configure your environment so that Cline uses SSO roles for authentication.
---
### Configuration Steps
1. Install the [latest version](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) of AWS CLI
- Follow the AWS docs to install your OS-specific version of AWS CLI
2. [Configure IAM authentication](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html) with the AWS CLI
- If you do not already have AWS access through the IAM Identity Center, follow the [IAM User Guide](https://docs.aws.amazon.com/singlesignon/latest/userguide/getting-started.html) to set up IAM users and roles. Ensure you have a `PowerUserAccess` role.
- If you have access to AWS through your employer, open your AWS access portal and find the appropriate account. Ensure you have `PowerUserAccess` permissions.
- Open the `Access keys` link and note the `SSO start URL` and `SSO region`, which are needed in the next step
3. Continue configuring your profile using [the `aws configure sso` CLI wizard](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html#cli-configure-sso-configure)
- Once configured, use the following command to authenticate the AWS CLI: `aws sso login --profile <AWS-profile-name>`
- Note which profile name you attach to your AWS account, this is needed to configure Cline in the following steps
4. If you haven't already done so, install VSCode and the Cline extension. Consult the [Getting Started](/getting-started) page for guidance.
5. Open the Cline extension, then click on the settings button ⚙️ to select your API Provider.
- From the API Provider dropdown, select AWS Bedrock
- Select the AWS Profile radio button, then enter the AWS Profile Name from step 3
- Select your AWS Region from the dropdown menu
- Selecting the cross-region inference checkbox is required for some models
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-aws-setup-markup%20(1).png"
alt="AWS Bedrock configuration in Cline settings showing profile authentication setup"
/>
</Frame>
-209
View File
@@ -1,209 +0,0 @@
---
title: "GCP Vertex AI"
description: "Configure GCP Vertex AI with Cline to access leading generative AI models like Claude 3.5 Sonnet v2. This guide covers GCP environment setup, authentication, and secure integration for enterprise teams."
---
### Overview
**GCP Vertex AI:**\
A fully managed service that provides access to leading generative AI models—such as Anthropic's Claude 3.5 Sonnet v2—through Google Cloud.\
[Learn more about GCP Vertex AI](https://cloud.google.com/vertex-ai).
This guide is tailored for organizations with established GCP environments (leveraging IAM roles, service accounts, and best practices in resource management) to ensure secure and compliant usage.
---
### Step 1: Prepare Your GCP Environment
#### 1.1 Create or Use a GCP Project
- **Sign in to the GCP Console:**\
[Google Cloud Console](https://console.cloud.google.com/)
- **Select or Create a Project:**\
Use an existing project or create a new one dedicated to Vertex AI.
#### 1.2 Set Up IAM Permissions and Service Accounts
- **Assign Required Roles:**
- Grant your user (or service account) the **Vertex AI User** role (`roles/aiplatform.user`)
- For service accounts, also attach the **Vertex AI Service Agent** role (`roles/aiplatform.serviceAgent`) to enable certain operations
- Consider additional predefined roles as needed:
- Vertex AI Platform Express Admin
- Vertex AI Platform Express User
- Vertex AI Migration Service User
- **Cross-Project Resource Access:**
- For BigQuery tables in different projects, assign the **BigQuery Data Viewer** role
- For Cloud Storage buckets in different projects, assign the **Storage Object Viewer** role
- For external data sources, refer to the [GCP Vertex AI Access Control documentation](https://cloud.google.com/vertex-ai/docs/general/access-control)
---
### Step 2: Verify Regional and Model Access
#### 2.1 Choose and Confirm a Region
Vertex AI supports eight regions. Select a region that meets your latency, compliance, and capacity needs. Examples include:
- **us-east5 (Columbus, Ohio)**
- **us-east1 (South Carolina)**
- **us-east4 (Northern Virginia)**
- **us-central1 (Iowa)**
- **us-west1 (The Dalles, Oregon)**
- **us-west4 (Las Vegas, Nevada)**
- **europe-west1 (Belgium)**
- **asia-southeast1 (Singapore)**
#### 2.2 Enable the Claude 3.5 Sonnet v2 Model
- **Open Vertex AI Model Garden:**\
In the Cloud Console, navigate to **Vertex AI → Model Garden**
- **Enable Claude 3.5 Sonnet v2:**\
Locate the model card for Claude 3.5 Sonnet v2 and click **Enable**
---
### Step 3: Configure the Cline VS Code Extension
#### 3.1 Install and Open Cline
- **Download VS Code:**\
[Download Visual Studio Code](https://code.visualstudio.com/)
- **Install the Cline Extension:**
- Open VS Code
- Navigate to the Extensions Marketplace (Ctrl+Shift+X or Cmd+Shift+X)
- Search for **Cline** and install the extension
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-extension-arrow.png"
alt="Cline extension in VS Code"
/>
</Frame>
#### 3.2 Configure Cline Settings
- **Open Cline Settings:**\
Click the settings ⚙️ icon within the Cline extension
- **Set API Provider:**\
Choose **GCP Vertex AI** from the API Provider dropdown
- **Enter Your Google Cloud Project ID:**\
Provide the project ID you set up earlier
- **Select the Region:**\
Choose one of the supported regions (e.g., `us-east5`)
- **Select the Model:**\
From the available list, choose **Claude 3.5 Sonnet v2**
- **Save and Test:**\
Save your settings and test by sending a simple prompt (e.g., "Generate a Python function to check if a number is prime.")
---
### Step 4: Authentication and Credentials Setup
#### Option A: Using Your Google Account (User Credentials)
1. **Install the Google Cloud CLI:**\
Follow the [installation guide](https://cloud.google.com/sdk/docs/install)
2. **Initialize and Authenticate:**
```bash
gcloud init
gcloud auth application-default login
```
- This sets up Application Default Credentials (ADC) using your Google account
3. **Restart VS Code:**\
Ensure VS Code is restarted so that the Cline extension picks up the new credentials
#### Option B: Using a Service Account (JSON Key)
1. **Create a Service Account:**
- In the GCP Console, navigate to **IAM & Admin > Service Accounts**
- Create a new service account (e.g., "vertex-ai-client")
2. **Assign Roles:**
- Attach **Vertex AI User** (`roles/aiplatform.user`)
- Attach **Vertex AI Service Agent** (`roles/aiplatform.serviceAgent`)
- Optionally, add other roles as required
3. **Generate a JSON Key:**
- In the Service Accounts section, manage keys for your service account and download the JSON key
4. **Set the Environment Variable:**
```bash
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-account-key.json"
```
- This instructs Google Cloud client libraries (and Cline) to use this key
5. **Restart VS Code:**\
Launch VS Code from a terminal where the `GOOGLE_APPLICATION_CREDENTIALS` variable is set
---
### Step 5: Security, Monitoring, and Best Practices
#### 5.1 Enforce Least Privilege
- **Principle of Least Privilege:**\
Only grant the minimum necessary permissions. Custom roles can offer finer control compared to broad predefined roles
- **Best Practices:**\
Refer to [GCP IAM Best Practices](https://cloud.google.com/iam/docs/best-practices)
#### 5.2 Manage Resource Access
- **Project vs. Resource-Level Access:**\
Access can be managed at both levels. Note that resource-level permissions (e.g., for BigQuery or Cloud Storage) add to, but do not override, project-level policies
#### 5.3 Monitor Usage and Quotas
- **Model Observability Dashboard:**
- In the Vertex AI Console, navigate to the **Model Observability** dashboard
- Monitor metrics such as request throughput, latency, and error rates (including 429 quota errors)
- **Quota Management:**
- If you encounter 429 errors, check the **IAM & Admin > Quotas** page
- Request a quota increase if necessary\
[Learn more about GCP Vertex AI Quotas](https://cloud.google.com/vertex-ai/docs/quotas)
#### 5.4 Service Agents and Cross-Project Considerations
- **Service Agents:**\
Be aware of the different service agents:
- Vertex AI Service Agent
- Vertex AI RAG Data Service Agent
- Vertex AI Custom Code Service Agent
- Vertex AI Extension Service Agent
- **Cross-Project Access:**\
For resources in other projects (e.g., BigQuery, Cloud Storage), ensure that the appropriate roles (BigQuery Data Viewer, Storage Object Viewer) are assigned
---
### Conclusion
By following these steps, your enterprise team can securely integrate GCP Vertex AI with the Cline VS Code extension to harness the power of **Claude 3.5 Sonnet v2**:
- **Prepare Your GCP Environment:**\
Create or use a project, configure IAM with least privilege, and ensure necessary roles (including the Vertex AI Service Agent role) are attached
- **Verify Regional and Model Access:**\
Confirm that your chosen region supports Claude 3.5 Sonnet v2 and that the model is enabled
- **Configure Cline in VS Code:**\
Install Cline, enter your project ID, select the appropriate region, and choose the model
- **Set Up Authentication:**\
Use either user credentials (via `gcloud auth application-default login`) or a service account with a JSON key
- **Implement Security and Monitoring:**\
Adhere to best practices for IAM, manage resource access carefully, and monitor usage with the Model Observability dashboard
For further details, please consult the [GCP Vertex AI Documentation](https://cloud.google.com/vertex-ai/docs) and your internal security policies.\
Happy coding!
_This guide will be updated as GCP Vertex AI and Cline evolve. Always refer to the latest documentation for current practices._
@@ -1,65 +0,0 @@
---
title: "LiteLLM & Cline (using Codestral)"
description: "Learn how to set up and run LiteLLM with Cline using the Codestral model. This guide covers Docker setup, configuration, and integration with Cline."
---
### Using LiteLLM with Cline
This guide demonstrates how to run a demo for LiteLLM starting with the Codestral model for use with Cline.
#### Prerequisites
- [Docker CLI or Docker Desktop](https://www.docker.com/get-started/) installed to run the LiteLLM image locally
- For this example config: A Codestral API Key (different from the Mistral API Keys)
#### Setup
1. **Create a `.env` file and fill in the appropriate field**
```bash
# Tip: Use the following command to generate a random alphanumeric key:
# openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32
LITELLM_MASTER_KEY=YOUR_LITELLM_MASTER_KEY
CODESTRAL_API_KEY=YOUR_CODESTRAL_API_KEY
```
_Note: Although this is limited to localhost, it's a good practice set LITELLM_MASTER_KEY to something secure_
2. **Configuration**
We'll need to create a `config.yaml` file to contain our LiteLLM configuration. In this case we'll just have one model, 'codestral-latest' and label it 'codestral'
```yaml
model_list:
- model_name: codestral
litellm_params:
model: codestral/codestral-latest
api_key: os.environ/CODESTRAL_API_KEY
```
#### Running the Demo
1. **Startup the LiteLLM docker container**
```bash
docker run \
--env-file .env \
-v $(pwd)/config.yaml:/app/config.yaml \
-p 127.0.0.1:4000:4000 \
ghcr.io/berriai/litellm:main-latest \
--config /app/config.yaml --detailed_debug
```
2. **Setup Cline**
Once the LiteLLM server is up and running you can set it up in Cline:
- Base URL should be `http://0.0.0.0:4000/v1`
- API Key should be the one you set in `.env` for LITELLM_MASTER_KEY
- Model ID is `codestral` or whatever you named it under `config.yaml`
#### Getting Help
- [LiteLLM Documentation](https://docs.litellm.ai/)
- [Mistral AI Console](https://console.mistral.ai/)
- [Cline Discord Community](https://discord.gg/cline)
+2 -45
View File
@@ -62,15 +62,10 @@
"getting-started/installing-dev-essentials",
"getting-started/model-selection-guide",
"getting-started/our-favorite-tech-stack",
"getting-started/task-management",
"getting-started/understanding-context-management",
"getting-started/what-is-cline"
]
},
{
"group": "Improving Your Prompting Skills",
"pages": ["prompting/prompt-engineering-guide", "prompting/cline-memory-bank"]
},
{
"group": "Exploring Cline's Tools",
"pages": [
@@ -83,46 +78,8 @@
]
},
{
"group": "Enterprise Solutions",
"pages": [
"enterprise-solutions/cloud-provider-integration",
"enterprise-solutions/custom-instructions",
"enterprise-solutions/mcp-servers",
"enterprise-solutions/security-concerns"
]
},
{
"group": "MCP Servers",
"pages": [
"mcp/mcp-overview",
"mcp/adding-mcp-servers-from-github",
"mcp/configuring-mcp-servers",
"mcp/connecting-to-a-remote-server",
"mcp/mcp-marketplace",
"mcp/mcp-server-development-protocol",
"mcp/mcp-transport-mechanisms"
]
},
{
"group": "Custom Model Configurations",
"pages": [
"custom-model-configs/aws-bedrock-with-credentials-authentication",
"custom-model-configs/aws-bedrock-with-profile-authentication",
"custom-model-configs/gcp-vertex-ai",
"custom-model-configs/litellm-and-cline-using-codestral"
]
},
{
"group": "Running Models Locally",
"pages": [
"running-models-locally/read-me-first",
"running-models-locally/lm-studio",
"running-models-locally/ollama"
]
},
{
"group": "More Info",
"pages": ["more-info/telemetry"]
"group": "Improving Your Prompting Skills",
"pages": ["prompting/prompt-engineering-guide", "prompting/cline-memory-bank"]
}
]
},
@@ -1,39 +0,0 @@
---
title: "Cloud Provider Integration"
---
Cline supports major cloud providers like AWS Bedrock and Google's Cloud Vertex; whichever your team currently uses is appropriate, and there's no need to change providers to utilize Cline's features.
For the purpose of this document, we assume your organization will use cloud-based frontier models. Cloud inference providers offer cutting-edge capabilities and the flexibility to select models which best suit your needs.
Certain scenarios may warrant using local models, including handling highly sensitive data, applications requiring consistent low-latency responses, or compliance with strict data sovereignty requirements. If your team needs to utilize local models, see [Running Local Models ](/running-models-locally/read-me-first.mdx)with Cline.
---
## AWS Bedrock Setup Guides
#### [IAM Security Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) (For administrators)
#### [AWS Bedrock setup for Legacy IAM (AWS Credentials)](/custom-model-configs/aws-bedrock-with-credentials-authentication.mdx)
#### [AWS Bedrock setup for SSO token (AWS Profile)](/custom-model-configs/aws-bedrock-with-profile-authentication.mdx)
#### VPC Endpoint Setup
To protect your team's data, Cline supports VPC (Virtual Private Cloud) endpoints, which create private connections between your data and AWS Bedrock. AWS VPCs enhance security by eliminating the need for public IP addresses, network gateways, or complex firewall rules—essentially creating a private highway for data that bypasses the public internet entirely. By keeping traffic within AWS's private network, teams also benefit from lower latency and more predictable performance when accessing services like AWS Bedrock or custom APIs. For those working with confidential information or operating in highly regulated industries like healthcare or finance, VPCs offers the perfect balance between the accessibility of cloud services and the security of private infrastructure.
---
1. Consult the [AWS guide](https://docs.aws.amazon.com/bedrock/latest/userguide/vpc-interface-endpoints.html) to creating VPC endpoints. This document specifies pre-requisites and describes the syntax used for creating VPC endpoints.
2. Follow the directions for [creating a VPC endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html#create-interface-endpoint-aws) in the AWS console. The image below pertains to steps 4 and 5 of the AWS guide linked above.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/vpc-console.png" alt="VPC Console" />
</Frame>
3. Note the IP address of your VPC endpoint, open Cline's settings menu, and select `AWS Bedrock`from the API Provider dropdown.
4. Click the `Use Custom VPC endpoint`checkbox and enter the IP address of your VPC endpoint
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/vpc-settings-menu.png" alt="VPC Settings Menu" />
</Frame>
@@ -1,22 +0,0 @@
---
title: "Custom Instructions"
---
## Building Custom Instructions for Teams
**Creating standardized project instructions ensures that all team members work within consistent guidelines. Start by documenting your project's technical foundation, then identify which information needs to be included in the instructions. The exact scope will vary depending on your team's needs, but generally it's best to provide as much information as possible. By creating comprehensive instructions that all team members follow, you establish a shared understanding of how code should be written, tested, and deployed across your project, resulting in more maintainable and consistent software.**
---
Here are a few topics and examples to consider for your team's custom instructions:
1. **Testing framework and specific commands**
- "All components must include Jest tests with at least 85% coverage. Run tests using `npm run test:coverage` before submitting any pull request."
2. **Explicit library preferences**
- "Use React Query for data fetching and state management. Avoid Redux unless specifically required for complex global state. For styling, use Tailwind CSS with our custom theme configuration found in `src/styles/theme.js.`"
3. **Where to find documentation**
- "All API documentation is available in our internal Notion workspace under 'Engineering > API Reference'. For component usage examples, refer to our Storybook instance at `https://storybook.internal.company.com`"
4. **Which MCP servers to use, and for which purposes**
- "For database operations, use the Postgres MCP server with credentials stored in 1Password under 'Development > Database'. For deployments, use the AWS MCP server which requires the deployment role from IAM. Refer to `docs/mcp-setup.md` for configuration instructions."
5. **Coding conventions specific to your project**
- "Name all React components using PascalCase and all helper functions using camelCase. Place components in the `src/components` directory organized by feature, not by type. Always use TypeScript interfaces for prop definitions."
-25
View File
@@ -1,25 +0,0 @@
---
title: "MCP Servers"
---
**Model Context Protocol (MCP) servers expand Cline's capabilities by providing standardized access to external data sources and executable functions. By implementing MCP servers, LLM tools can dynamically retrieve and incorporate relevant information from both local and remote data sources. This capability ensures that the models operate with the most current and contextually appropriate data, improving the accuracy and relevance of their outputs.**
---
### Secure Architecture Fundamentals
MCP servers follow a client-server architecture where hosts (LLM applications like Cline) initiate connections through a transport layer to MCP servers. This architecture inherently provides security benefits as it maintains clear separation between components. Enterprise deployments should focus on the proper implementation of this architecture to ensure secure operations, particularly regarding the message exchange patterns and connection lifecycle management. For MCP architecture details, see [MCP Architecture](https://modelcontextprotocol.io/docs/concepts/architecture), and for latest specifications, see [MCP Specifications](https://spec.modelcontextprotocol.io/specification/2024-11-05/).
### Transport Layer Security
For enterprise environments, selecting the appropriate transport mechanism is crucial. While stdio transport works efficiently for local processes, HTTP with Server-Sent Events (SSE) transport requires additional security measures. TLS should be used for all remote connections whenever possible. This is especially important when MCP servers are deployed across different network segments within corporate infrastructure.
### Message Validation and Access Control
The MCP architecture defines standard error codes and message types (Requests, Results, Errors, and Notifications), providing a structured framework for secure communication. Security teams should consider message validation, sanitizing inputs, checking message size limits, and verifying JSON-RPC format. Additionally, implementing resource protection through access controls, path validation, and request rate limiting helps prevent potential abuse of MCP server capabilities.
### Monitoring and Compliance
For enterprise compliance requirements, implementing comprehensive logging of protocol events, message flows, and errors is essential. The MCP architecture supports diagnostic capabilities including health checks, connection state monitoring, and resource usage tracking. Organizations should extend these capabilities to meet their specific compliance needs, particularly for audit trails of all MCP server interactions and resource access patterns.
By leveraging the client-server design of the MCP architecture and implementing appropriate security controls at each layer, enterprises can safely integrate MCP servers into their environments while maintaining their security posture and meeting regulatory requirements.
@@ -1,63 +0,0 @@
---
title: "Security Concerns"
---
## Enterprise Security with Cline
#### Cline addresses enterprise security concerns through its unique client-side architecture that prioritizes data privacy, secure cloud integration, and transparent operations. Below is a comprehensive overview of how Cline maintains robust security measures for enterprise environments.
---
### Client-Side Architecture
Cline operates exclusively as a client-side VSCode extension with zero server-side components. This fundamental design choice ensures that your code and data remain within your secure environment at all times. Unlike traditional AI assistants that send data to external servers for processing, Cline connects directly to your chosen cloud provider's AI endpoints, keeping all sensitive information within your infrastructure boundaries.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-arch.png"
alt="Cline's relationship to local and remote assets"
/>
</Frame>
### Data Privacy Commitment
Cline implements a strict zero data retention policy, meaning your intellectual property never leaves your secure environment. The extension does not collect, store, or transmit your code to any central servers. This approach significantly reduces potential attack vectors that might otherwise be introduced through data transmission to third-party systems. Telemetry collection is optional and requires explicit consent.
### Cloud Provider Integration
Enterprise teams can access cutting-edge AI models through their existing cloud deployments. Cline supports seamless integration with:
- AWS Bedrock
- Google Cloud Vertex AI
- Microsoft Azure
These integrations utilize your organization's existing security credentials, including native IAM role assumption for AWS. This ensures that all AI processing occurs within your corporate cloud environment, maintaining compliance with your established security protocols.
### Open-Source Transparency
Cline's codebase is completely open-source, allowing for comprehensive security auditing by your internal teams. This transparency enables security professionals to verify exactly how the extension functions and confirm that it adheres to your organization's security requirements. Organizations can review the code to ensure it aligns with their security policies before deployment.
### Controlled Modifications
The extension implements safeguards against unauthorized changes to your codebase. Cline requires explicit user approval for all file modifications and terminal commands, preventing accidental or unwanted alterations. This approval-based workflow maintains the integrity of your projects while still providing AI assistance.
### Enterprise Deployment Support
For organizations with strict security review processes, Cline provides comprehensive documentation including detailed deployment diagrams, sequence diagrams illustrating all data flows, and complete security posture documentation. These materials facilitate thorough security reviews and help demonstrate compliance with enterprise data handling standards and regulations.
### Access Control
Enterprise editions of Cline (planned for Q2 2025) will include centralized administration features that allow organizations to:
- Manage user access with customizable permission levels
- Provision accounts with corporate credentials
- Immediately revoke access when needed
- Control which AI providers and LLM endpoints can be used
- Deploy standardized settings across the organization
- Prevent unauthorized use of personal API keys
### Compliance and Governance
Cline's architecture supports compliance with data sovereignty requirements and enterprise data handling regulations. The planned Enterprise Complete edition will further enhance governance with detailed audit logging, compliance reporting, and automated policy enforcement mechanisms.
By combining client-side processing, direct cloud provider integration, and transparent operations, Cline offers enterprise teams a secure way to leverage AI assistance while maintaining strict control over their sensitive code and data.
+3 -9
View File
@@ -28,10 +28,7 @@ After each tool use, you can:
2. Click the "Restore" button to open restore options
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(13).png"
alt="Checkpoint comparison and restore options"
/>
<img src="/assets/robot_panel_dark.png" alt="Checkpoint comparison and restore options" />
</Frame>
#### Rolling Back
@@ -63,7 +60,7 @@ Checkpoints let you be more experimental with Cline. While human coding is often
- Ideal for exploring different design patterns or architectural approaches
<Frame caption="In this case, I didn't like the changes Cline made to my robot dog-walking website (still working on the robots) and I wanted to revert both the codebase and the task to before any changes were made so I could start fresh.">
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/checkpointsDemo.gif" alt="Checkpoint restore demo" />
<img src="/assets/robot_panel_dark.png" alt="Checkpoint restore demo" />
</Frame>
### ✨ Best Practices
@@ -107,8 +104,5 @@ Perhaps you didn't get the results you wanted, thought of a better way to phrase
- Shift + Enter: Insert new line / line break
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/message-editing.png"
alt="Message editing interface"
/>
<img src="/assets/robot_panel_dark.png" alt="Message editing interface" />
</Frame>
@@ -7,10 +7,7 @@ title: "Plan & Act Modes: A Guide to Effective AI Development"
Plan & Act modes represent Cline's approach to structured AI development, emphasizing thoughtful planning before implementation. This dual-mode system helps developers create more maintainable, accurate code while reducing iteration time.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/planningThenActing%20(1).gif"
alt="Use Plan to gather context before using Act to implement the plan"
/>
<img src="/assets/robot_panel_dark.png" alt="Use Plan to gather context before using Act to implement the plan" />
</Frame>
### Understanding the Modes
@@ -30,7 +27,7 @@ Plan & Act modes represent Cline's approach to structured AI development, emphas
- Can execute changes to your codebase
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(5).png" alt="Act mode capabilities" />
<img src="/assets/robot_panel_dark.png" alt="Act mode capabilities" />
</Frame>
### Workflow Guide
@@ -42,7 +39,7 @@ Begin every significant development task in Plan mode:
In this mode:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(5)%20(1).png" alt="Plan mode workflow" />
<img src="/assets/robot_panel_dark.png" alt="Plan mode workflow" />
</Frame>
- Share your requirements
@@ -51,10 +48,7 @@ In this mode:
- Develop implementation strategy
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(2)%20(1)%20(1)%20(1).png"
alt="Planning phase"
/>
<img src="/assets/robot_panel_dark.png" alt="Planning phase" />
</Frame>
#### 2. Switch to Act Mode
@@ -62,7 +56,7 @@ In this mode:
Once you have a clear plan, switch to Act mode:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/switching-to-act.gif" alt="Switching to Act mode" />
<img src="/assets/robot_panel_dark.png" alt="Switching to Act mode" />
</Frame>
Act mode allows Cline to:
@@ -96,10 +90,7 @@ Complex projects often require multiple plan-act cycles:
4. Document significant decisions
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(3)%20(1).png"
alt="Implementation best practices"
/>
<img src="/assets/robot_panel_dark.png" alt="Implementation best practices" />
</Frame>
### Power User Tips
@@ -128,7 +119,7 @@ Complex projects often require multiple plan-act cycles:
- Executing test cases
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(6).png" alt="Mode usage patterns" />
<img src="/assets/robot_panel_dark.png" alt="Mode usage patterns" />
</Frame>
### Contributing
+1 -4
View File
@@ -22,10 +22,7 @@ Follow these steps to get Cline up and running:
4. **Search for 'Cline':** In the Extensions search bar, type `Cline`.
<Frame caption="VS Code marketplace with Cline extension ready to install">
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(20).png"
alt="VS Code marketplace showing Cline extension"
/>
<img src="/assets/robot_panel_dark.png" alt="VS Code marketplace showing Cline extension" />
</Frame>
1. **Install the Extension:** Click the "Install" button next to the Cline extension.
@@ -28,10 +28,7 @@ Cline helps you manage this limitation with its Context Window Progress Bar, whi
- The total capacity for your chosen model
<Frame caption="Visual representation of the context window usage in Cline">
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(11).png"
alt="Context window progress bar example"
/>
<img src="/assets/robot_panel_light.png" alt="Context window progress bar example" />
</Frame>
This visibility helps you work more effectively with Cline by letting you know when you might need to start fresh or break tasks into smaller chunks.
-67
View File
@@ -1,67 +0,0 @@
---
title: "Task Management in Cline"
description: "Learn how to effectively manage your task history, use favorites, and organize your work in Cline."
---
# Task Management
As you use Cline, you'll accumulate many tasks over time. The task management system helps you organize, filter, search, and clean up your task history to keep your workspace efficient.
## Accessing Task History
You can access your task history by:
1. Clicking on the "History" button in the Cline sidebar
2. Using the command palette to search for "Cline: Show Task History"
## Task History Features
The task history view provides several powerful features:
### Searching and Filtering
- **Search Bar**: Use the fuzzy search at the top to quickly find tasks by content
- **Sort Options**: Sort tasks by:
- Newest (default)
- Oldest
- Most Expensive (highest API cost)
- Most Tokens (highest token usage)
- Most Relevant (when searching)
- **Favorites Filter**: Toggle to show only favorited tasks
### Task Actions
Each task in the history view has several actions available:
- **Open**: Click on a task to reopen it in the Cline chat
- **Favorite**: Click the star icon to mark a task as a favorite
- **Delete**: Remove individual tasks (favorites are protected from deletion)
- **Export**: Export a task's conversation to markdown
## ⭐ Task Favorites
The favorites feature allows you to mark important tasks that you want to preserve and find quickly.
### How Favorites Work
- **Marking Favorites**: Click the star icon next to any task to toggle its favorite status
- **Protection**: Favorited tasks are protected from individual and bulk deletion operations (can be overridden)
- **Filtering**: Use the favorites filter to quickly access your important tasks
## Batch Operations
The task history view supports several batch operations:
- **Select Multiple**: Use the checkboxes to select multiple tasks
- **Select All/None**: Quickly select or deselect all tasks
- **Delete Selected**: Remove all selected tasks
- **Delete All**: Remove all tasks from history (favorites are preserved unless you choose to include them)
## Best Practices
1. **Favorite Important Tasks**: Mark reference tasks or frequently accessed conversations as favorites
2. **Regular Cleanup**: Periodically remove old or unused tasks to improve performance
3. **Use Search**: Leverage the fuzzy search to quickly find specific conversations
4. **Export Valuable Tasks**: Export important tasks to markdown for external reference
Task management helps you maintain an organized workflow when using Cline, allowing you to quickly find past conversations, preserve important work, and keep your history clean and efficient.
@@ -14,7 +14,7 @@ description: "Context is key to getting the most out of Cline"
<Frame caption="In a world of infinite context, the context window is what Cline currently has available">
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(2).png"
src="/assets/robot_panel_dark.png"
alt="In a world of infinite context, the context window is what Cline currently has available"
/>
</Frame>
@@ -65,10 +65,7 @@ Think of context like a whiteboard you and Cline share:
Cline provides a visual way to monitor your context window usage through a progress bar:
<Frame caption="Visual representation of the context window usage">
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(1)%20(1).png"
alt="Context window progress bar"
/>
<img src="/assets/robot_panel_light.png" alt="Context window progress bar" />
</Frame>
### Reading the Bar
@@ -1,69 +0,0 @@
---
title: "Adding MCP Servers from GitHub"
---
This guide provides a step-by-step walkthrough of how to use Cline to build an existing MCP server from a GitHub repository.
## **Finding an MCP Server**
There are multiple places online to find MCP servers:
- **Cline can automatically add MCP servers to its list, which you can then edit.** Cline can clone repositories directly from GitHub and build the servers for you.
- **GitHub:** Two of the most common places to find MCP servers on GitHub include:
- [Official MCP servers repository](https://github.com/modelcontextprotocol/servers)
- [Awesome-MCP servers repository](https://github.com/punkpeye/awesome-mcp-servers)
- **Online directories:** Several websites list MCP servers including:
- [mcpservers.org](https://mcpservers.org/)
- [mcp.so](https://mcp.so/)
- [glama.ai/mcp/servers](https://glama.ai/mcp/servers)
These directories allow users to sort the servers by various criteria such as downloads, date, stars, and use case. Each entry provides information such as features, tools, and configuration instructions.
- **PulseMCP:** This website has a blog post discussing how AI could use MCP servers to make websites obsolete. PulseMCP also includes an FAQ section about MCP servers: [https://www.pulsemcp.com/](https://www.pulsemcp.com/)
## **Building with Cline**
1. **Initiate the Process:** Provide Cline with the following information:
- **GitHub Repository URL:** The URL of the server's repository.
- **README.md Contents:** This is optional but helpful for Cline to understand the server's purpose and configuration. You can copy the README.md file from the GitHub repository.
2. **Example Interaction with Cline:**
```
User: "Cline, I want to add the MCP server for Brave browser control. Here's the GitHub link: @https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search Can you add it?"
Cline: "OK. Cloning the repository to the MCP directory. It needs to be built because it has a 'package.json' file. Should I run 'npm run build'?"
User: "Yes, build it."
Cline: "Build complete. This server needs a browser path. Where's your Brave browser executable?"
User: "It's in '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'."
Cline: "OK. I've updated the server config with that path and added it to 'cline_mcp_settings.json'. Anything else?"
User: "No, that's all. Let's test it."
Cline: "Great! Starting the MCP Inspector to test the server connection. After that, we can try controlling the browser from Cline."
```
3. **Cline's Actions:** Based on your instructions, Cline will perform the following:
- **Repository Cloning:** Cline will clone the repository to your local machine, usually in the directory specified in your configuration.
- **Tweaking:** You can guide Cline to modify the server's configuration. For instance:
- **User:** "This server requires an API key. Can you find where it should be added?"
- Cline may automatically update the `cline_mcp_settings.json` file or other relevant files based on your instructions.
- **Building the Server:** Cline will run the appropriate build command for the server, which is commonly `npm run build`.
- **Adding Server to Settings:** Cline will add the server's configuration to the `cline_mcp_settings.json` file.
## **Testing and Troubleshooting**
1. **Test the Server:** Once Cline finishes the build process, test the server to make sure it works as expected. Cline can assist you if you encounter any problems.
2. **MCP Inspector:** You can use the MCP Inspector to test the server's connection and functionality.
## **Best Practices**
- **Understand the Basics:** While Cline simplifies the process, it's beneficial to have a basic understanding of the server's code, the MCP protocol ([learn more](/mcp/mcp-overview)), and how to configure the server. This allows for more effective troubleshooting and customization.
- **Clear Instructions:** Provide clear and specific instructions to Cline throughout the process.
- **Testing:** Thoroughly test the server after installation and configuration to ensure it functions correctly.
- **Version Control:** Use a version control system (like Git) to track changes to the server's code.
- **Stay Updated:** Keep your MCP servers updated to benefit from the latest features and security patches.
-166
View File
@@ -1,166 +0,0 @@
---
title: "Configuring MCP Servers"
---
## Global MCP Server Inclusion Mode
Utilizing MCP servers will increase your token usage. Cline offers the ability to restrict or disable MCP server functionality as desired.
1. Click the "MCP Servers" icon in the top navigation bar of the Cline extension.
2. Select the "Installed" tab, and then Click the "Advanced MCP Settings" link at the bottom of that pane.
3. Cline will open a new settings window. find `Cline>Mcp:Mode` and make your selection from the dropdown menu.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/MCP-settings-edit%20(1).png"
alt="MCP settings edit"
/>
</Frame>
## Managing Individual MCP Servers
Each MCP server has its own configuration panel where you can modify settings, manage tools, and control its operation. To access these settings:
1. Click the "MCP Servers" icon in the top navigation bar of the Cline extension.
2. Locate the MCP server you want to manage in the list, and open it by clicking on its name.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/MCP-settings-individual.png"
alt="MCP settings individual"
/>
</Frame>
### Deleting a Server
1. Click the Trash icon next to the MCP server you would like to delete, or the red Delete Server button at the bottom of the MCP server config box.
**NOTE:** There is no delete confirmation dialog box
### Restarting a Server
1. Click the Restart button next to the MCP server you would like to restart, or the gray Restart Server button at the bottom of the MCP server config box.
### Enabling or Disabling a Server
1. Click the toggle switch next to the MCP server to enable/disable servers individually.
### Network Timeout
To set the maximum time to wait for a response after a tool call to the MCP server:
1. Click the `Network Timeout` dropdown at the bottom of the individual MCP server's config box and change the time. Default is 1 minute but it can be set between 30 seconds and 1 hour.
## Editing MCP Settings Files
Settings for all installed MCP servers are located in the `cline_mcp_settings.json` file:
1. Click the MCP Servers icon at the top navigation bar of the Cline pane.
2. Select the "Installed" tab.
3. Click the "Configure MCP Servers" button at the bottom of the pane.
The file uses a JSON format with a `mcpServers` object containing named server configurations:
```json
{
"mcpServers": {
"server1": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"API_KEY": "your_api_key"
},
"alwaysAllow": ["tool1", "tool2"],
"disabled": false
}
}
}
```
_Example of MCP Server config in Cline (STDIO Transport)_
---
## Understanding Transport Types
MCP supports two transport types for server communication:
### STDIO Transport
Used for local servers running on your machine:
- Communicates via standard input/output streams
- Lower latency (no network overhead)
- Better security (no network exposure)
- Simpler setup (no HTTP server needed)
- Runs as a child process on your machine
For more in-depth information about how STDIO transport works, see [MCP Transport Mechanisms](/mcp/mcp-transport-mechanisms).
STDIO configuration example:
```json
{
"mcpServers": {
"local-server": {
"command": "node",
"args": ["/path/to/server.js"],
"env": {
"API_KEY": "your_api_key"
},
"alwaysAllow": ["tool1", "tool2"],
"disabled": false
}
}
}
```
### SSE Transport
Used for remote servers accessed over HTTP/HTTPS:
- Communicates via Server-Sent Events protocol
- Can be hosted on a different machine
- Supports multiple client connections
- Requires network access
- Allows centralized deployment and management
For more in-depth information about how SSE transport works, see [MCP Transport Mechanisms](/mcp/mcp-transport-mechanisms).
SSE configuration example:
```json
{
"mcpServers": {
"remote-server": {
"url": "https://your-server-url.com/mcp",
"headers": {
"Authorization": "Bearer your-token"
},
"alwaysAllow": ["tool3"],
"disabled": false
}
}
}
```
---
## Using MCP Tools in Your Workflow
After configuring an MCP server, Cline will automatically detect available tools and resources. To use them:
1. Type your request in Cline's conversation window
2. Cline will identify when an MCP tool can help with your task
3. Approve the tool use when prompted (or use auto-approval)
Example: "Analyze the performance of my API" might use an MCP tool that tests API endpoints.
## Troubleshooting MCP Servers
Common issues and solutions:
- **Server Not Responding:** Check if the server process is running and verify network connectivity
- **Permission Errors:** Ensure proper API keys and credentials are configured in your `mcp_settings.json` file
- **Tool Not Available:** Confirm the server is properly implementing the tool and it's not disabled in settings
- **Slow Performance:** Try adjusting the network timeout value for the specific MCP server
-132
View File
@@ -1,132 +0,0 @@
---
title: "Connecting to a Remote Server"
description: "The Model Context Protocol (MCP) allows Cline to communicate with external servers that provide additional tools and resources to extend its capabilities. This guide explains how to add and connect to remote MCP servers through the MCP Servers interface."
---
## Adding and Managing Remote MCP Servers
### Accessing the MCP Servers Interface
To access the MCP Servers interface in Cline:
1. Click on the Cline icon in the VSCode sidebar
2. Open the menu (⋮) in the top right corner of the Cline panel
3. Select "MCP Servers" from the dropdown menu
### Understanding the MCP Servers Interface
The MCP Servers interface is divided into three main tabs:
- **Marketplace**: Discover and install pre-configured MCP servers (if enabled)
- **Remote Servers**: Connect to existing MCP servers via URL endpoints
- **Installed**: Manage your connected MCP servers
### Adding a Remote MCP Server
The "Remote Servers" tab allows you to connect to any MCP server that's accessible via a URL endpoint:
1. Click on the "Remote Servers" tab in the MCP Servers interface
2. Fill in the required information:
- **Server Name**: Provide a unique, descriptive name for the server
- **Server URL**: Enter the complete URL endpoint of the MCP server (e.g., `https://example.com/mcp-sse`)
3. Click "Add Server" to initiate the connection
4. Cline will attempt to connect to the server and display the connection status
> **Note**: When connecting to a remote server, ensure you trust the source, as MCP servers can execute code in your environment.
### Remote Server Discovery
If you're looking for MCP servers to connect to, several third-party marketplaces provide directories of available servers with various capabilities.
> **Warning**: The following third-party marketplaces are listed for informational purposes only. Cline does not endorse, verify, or take responsibility for any servers listed on these marketplaces. These servers are cloud-hosted services that process your requests and may have access to data you share with them. Always review privacy policies and terms of use before connecting to third-party services.
#### Composio MCP Integration
[Composio's MCP Marketplace](https://mcp.composio.dev/) provides access to a wide range of third-party servers that support the Model Context Protocol (MCP). These servers expose APIs for services like GitHub, Notion, Slack, and others. Each server includes configuration instructions and built-in authentication support (e.g. OAuth or API keys). To connect, locate the desired service in the marketplace and follow the integration steps provided there.
#### Connecting via Smithery
Smithery is a third-party MCP server marketplace that allows users to discover and connect to a variety of Model Context Protocol (MCP) servers. If you're using an MCP-compatible client (such as Cursor, Claude Desktop, or Cline), you can browse available servers and integrate them directly into your workflow.
To explore available options, visit the Smithery marketplace: [https://smithery.ai](https://smithery.ai)
Please note: Smithery is maintained independently and is not affiliated with our project. Use at your own discretion.
### Managing Installed MCP Servers
Once added, your MCP servers appear in the "Installed" tab where you can:
#### View Server Status
Each server displays its current status:
- **Green dot**: Connected and ready to use
- **Yellow dot**: In the process of connecting
- **Red dot**: Disconnected or experiencing errors
#### Configure Server Settings
Click on a server to expand its settings panel:
1. **Tools & Resources**:
- View all available tools and resources from the server
- Configure auto-approval settings for tools (if enabled)
2. **Request Timeout**:
- Set how long Cline should wait for server responses
- Options range from 30 seconds to 1 hour
3. **Server Management**:
- **Restart Server**: Reconnect if the server becomes unresponsive
- **Delete Server**: Remove the server from your configuration
#### Enable/Disable Servers
Toggle the switch next to each server to enable or disable it:
- **Enabled**: Cline can use the server's tools and resources
- **Disabled**: The server remains in your configuration but is not active
### Troubleshooting Connection Issues
If a server fails to connect:
1. An error message will be displayed with details about the failure
2. Check that the server URL is correct and the server is running
3. Use the "Restart Server" button to attempt reconnection
4. If problems persist, you can delete the server and try adding it again
### Advanced Configuration
For advanced users, Cline stores MCP server configurations in a JSON file that can be modified:
1. In the "Installed" tab, click "Configure MCP Servers" to access the settings file
2. The configuration for each server follows this format:
```json
{
"mcpServers": {
"exampleServer": {
"url": "https://example.com/mcp-sse",
"disabled": false,
"autoApprove": ["tool1", "tool2"],
"timeout": 30
}
}
}
```
Key configuration options:
- **url**: The endpoint URL (for remote servers)
- **disabled**: Whether the server is currently enabled (true/false)
- **autoApprove**: List of tool names that don't require confirmation
- **timeout**: Maximum time in seconds to wait for server responses
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
### Using MCP Server Tools
Once connected, Cline can use the tools and resources provided by the MCP server. When Cline suggests using an MCP tool:
1. A tool approval prompt will appear (unless auto-approved)
2. Review the tool details and parameters before approving
3. The tool will execute and return results to Cline
-199
View File
@@ -1,199 +0,0 @@
---
title: "MCP Made Easy"
description: "Learn how to use the MCP Marketplace to discover, install, and configure MCP servers that enhance Cline's capabilities with additional tools and resources."
---
## What's an MCP Server?
MCP servers are specialized extensions that enhance Cline's capabilities. They enable Cline to perform additional tasks like fetching web pages, processing images, accessing APIs, and much more.
## MCP Marketplace Walkthrough
The MCP Marketplace provides a one-click installation experience for hundreds of MCP servers across various categories.
### 1. Access the Marketplace
- In Cline, click the "Extensions" button (square icon) in the top toolbar
- The MCP marketplace will open, showing available servers by category
### 2. Browse and Select a Server
- Browse servers by category (Search, File-systems, Browser-automation, Research-data, etc.)
- Click on a server to see details about its capabilities and requirements
### 3. Install and Configure
- Click the install button for your chosen server
- If the server requires an API key (most do), Cline will guide you through:
- Where to get the API key
- How to enter it securely
- The server will be added to your MCP settings automatically
### 4. Verify Installation
- Cline will show confirmation when installation is complete
- Check the server status in Cline's MCP settings UI
### 5. Using Your New Server
- After successful installation, Cline will automatically integrate the server's capabilities
- You'll see new tools and resources available in Cline's system prompt
- Simply ask Cline to use the capabilities of your new server
- Example: "Search the web for recent React updates using Perplexity"
**Corporate Users:** If you're using Cline in a corporate environment, ensure you have permission to install third-party MCP servers according to your organization's security policies.
## What Happens Behind the Scenes
When you install an MCP server, several things happen automatically:
### 1. Installation Process
- The server code is cloned/installed to `/Users/<username>/Documents/Cline/MCP/`
- Dependencies are installed
- The server is built (TypeScript/JavaScript compilation or Python package installation)
### 2. Configuration
- The MCP settings file is updated with your server configuration
- This file is located at: `/Users/<username>/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
- Environment variables (like API keys) are securely stored
- The server path is registered
### 3. Server Launch
- Cline detects the configuration change
- Cline launches your server as a separate process
- Communication is established via stdio or HTTP
### 4. Integration with Cline
- Your server's capabilities are added to Cline's system prompt
- Tools become available via `use_mcp_tool` commands
- Resources become available via `access_mcp_resource` commands
- Cline can now use these capabilities when prompted by the user
## Troubleshooting
### System Requirements
Make sure your system meets these requirements:
- **Node.js 18.x or newer**
- Check by running: `node --version`
- Install from: https://nodejs.org/
- Required for JavaScript/TypeScript implementations
- **Python 3.10 or newer**
- Check by running: `python --version`
- Install from: https://python.org/
- Note: Some specialized implementations may require Python 3.11+
- **UV Package Manager**
- Modern Python package manager for dependency isolation
- Install using:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Or: `pip install uv`
- Verify with: `uv --version`
If any of these commands fail or show older versions, please install/update before continuing!
### Common Installation Issues
- Ensure your internet connection is stable
- Check that you have the necessary permissions to install new software
- Verify that the API key was entered correctly (if required)
- Check the server status in the MCP settings UI for any error messages
### How to Remove an MCP Server
To completely remove a faulty MCP server:
1. Open the MCP settings file: `/Users/<username>/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
2. Delete the entire entry for your server from the `mcpServers` object
3. Save the file
4. Restart Cline
### I'm Still Getting an Error
If you're getting an error when using an MCP server, you can try the following:
- Check the MCP settings file for errors
- Use a Claude Sonnet model for installation
- Verify that paths to your server's files are correct
- Ensure all required environment variables are set
- Check if another process is using the same port (for HTTP-based servers)
- Try removing and reinstalling the server (remove from both the `cline_mcp_settings.json` file and the `/Users/<username>/Documents/Cline/MCP/` directory)
- Use a terminal and run the command with its arguments directly. This will allow you to see the same errors that Cline is seeing
## MCP Server Rules
Cline is already aware of your active MCP servers and what they are for, but when you have a lot of MCP servers enabled, it can be useful to define when to use each server.
Utilize a `.clinerules` file or custom instructions to support intelligent MCP server activation through keyword-based triggers, making Cline's tool selection more intuitive and context-aware.
### How MCP Rules Work
MCP Rules group your connected MCP servers into functional categories and define trigger keywords that activate them automatically when detected in your conversations with Cline.
```json
{
"mcpRules": {
"webInteraction": {
"servers": ["firecrawl-mcp-server", "fetch-mcp"],
"triggers": ["web", "scrape", "browse", "website"],
"description": "Tools for web browsing and scraping"
}
}
}
```
### Configuration Structure
1. **Categories**: Group related servers (e.g., "webInteraction", "mediaAndDesign")
2. **Servers**: List server names in each category
3. **Triggers**: Keywords that activate these servers
4. **Description**: Human-readable category explanation
### Benefits of MCP Rules
- **Contextual Tool Selection**: Cline selects appropriate tools based on conversation context
- **Reduced Friction**: No need to manually specify which tool to use
- **Organized Capabilities**: Logically group related tools and servers
- **Prioritization**: Handle ambiguous cases with explicit priority ordering
### Example Usage
When you write "Can you scrape this website?", Cline detects "scrape" and "website" as triggers, automatically selecting web-related MCP servers.
For finance tasks like "What's Apple's stock price?", keywords like "stock" and "price" trigger finance-related servers.
### Quick Start Template
```json
{
"mcpRules": {
"category1": {
"servers": ["server-name-1", "server-name-2"],
"triggers": ["keyword1", "keyword2", "phrase1", "phrase2"],
"description": "Description of what these tools do"
},
"category2": {
"servers": ["server-name-3"],
"triggers": ["keyword3", "keyword4", "phrase3"],
"description": "Description of what these tools do"
},
"category3": {
"servers": ["server-name-4", "server-name-5"],
"triggers": ["keyword5", "keyword6", "phrase4"],
"description": "Description of what these tools do"
}
},
"defaultBehavior": {
"priorityOrder": ["category1", "category2", "category3"],
"fallbackBehavior": "Ask user which tool would be most appropriate"
}
}
```
Add this to your `.clinerules` file or to your custom instructions to make Cline's MCP server selection more intuitive and context-aware.
-107
View File
@@ -1,107 +0,0 @@
---
title: "MCP Overview"
description: "Learn about Model Context Protocol (MCP) servers, their capabilities, and how Cline can help build and use them. MCP standardizes how applications provide context to LLMs, acting like a USB-C port for AI applications."
---
## Quick Links
- [Building MCP Servers from GitHub](/mcp/adding-mcp-servers-from-github)
- [Building Custom MCP Servers from Scratch](/mcp/mcp-server-development-protocol)
## Overview
Model Context Protocol is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications; it provides a standardized way to connect AI models to different data sources and tools. MCP servers act as intermediaries between large language models (LLMs), such as Claude, and external tools or data sources. They are small programs that expose functionalities to LLMs, enabling them to interact with the outside world through the MCP. An MCP server is essentially like an API that an LLM can use.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/mcp-diagram.png"
alt="MCP diagram showing how MCP servers connect LLMs to external tools and data sources"
/>
</Frame>
## Key Concepts
MCP servers define a set of "**tools,**" which are functions the LLM can execute. These tools offer a wide range of capabilities.
**Here's how MCP works:**
- **MCP hosts** discover the capabilities of connected servers and load their tools, prompts, and resources.
- **Resources** provide consistent access to read-only data, akin to file paths or database queries.
- **Security** is ensured as servers isolate credentials and sensitive data. Interactions require explicit user approval.
## Use Cases
The potential of MCP servers is vast. They can be used for a variety of purposes.
**Here are some concrete examples of how MCP servers can be used:**
- **Web Services and API Integration:**
- Monitor GitHub repositories for new issues
- Post updates to Twitter based on specific triggers
- Retrieve real-time weather data for location-based services
- **Browser Automation:**
- Automate web application testing
- Scrape e-commerce sites for price comparisons
- Generate screenshots for website monitoring
- **Database Queries:**
- Generate weekly sales reports
- Analyze customer behavior patterns
- Create real-time dashboards for business metrics
- **Project and Task Management:**
- Automate Jira ticket creation based on code commits
- Generate weekly progress reports
- Create task dependencies based on project requirements
- **Codebase Documentation:**
- Generate API documentation from code comments
- Create architecture diagrams from code structure
- Maintain up-to-date README files
## Getting Started
Cline does not come with any pre-installed MCP servers. You'll need to find and install them separately.
**Choose the right approach for your needs:**
- **Community Repositories:** Check for community-maintained lists of MCP servers on GitHub. See [Adding MCP Servers from Github](/mcp/adding-mcp-servers-from-github)
- **Cline Marketplace:** Install one from Cline's [MCP Marketplace](/mcp/mcp-marketplace)
- **Ask Cline:** You can ask Cline to help you find or create MCP servers
- **Build Your Own:** Create custom MCP servers using the [MCP SDK](https://github.com/modelcontextprotocol/)
- **Customize Existing Servers:** Modify existing servers to fit your specific requirements
## Integration with Cline
Cline simplifies the building and use of MCP servers through its AI capabilities.
### Building MCP Servers
- **Natural language understanding:** Instruct Cline in natural language to build an MCP server by describing its functionalities. Cline will interpret your instructions and generate the necessary code.
- **Cloning and building servers:** Cline can clone existing MCP server repositories from GitHub and build them automatically.
- **Configuration and dependency management:** Cline handles configuration files, environment variables, and dependencies.
- **Troubleshooting and debugging:** Cline helps identify and resolve errors during development.
### Using MCP Servers
- **Tool execution:** Cline seamlessly integrates with MCP servers, allowing you to execute their defined tools.
- **Context-aware interactions:** Cline can intelligently suggest using relevant tools based on conversation context.
- **Dynamic integrations:** Combine multiple MCP server capabilities for complex tasks. For example, Cline could use a GitHub server to get data and a Notion server to create a formatted report.
## Security Considerations
When working with MCP servers, it's important to follow security best practices:
- **Authentication:** Always use secure authentication methods for API access
- **Environment Variables:** Store sensitive information in environment variables
- **Access Control:** Limit server access to authorized users only
- **Data Validation:** Validate all inputs to prevent injection attacks
- **Logging:** Implement secure logging practices without exposing sensitive data
## Resources
There are various resources available for finding and learning about MCP servers.
**Here are some links to resources for finding and learning about MCP servers:**
- **GitHub Repositories:** [https://github.com/modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers) and [https://github.com/punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers)
- **Online Directories:** [https://mcpservers.org/](https://mcpservers.org/), [https://mcp.so/](https://mcp.so/), and [https://glama.ai/mcp/servers](https://glama.ai/mcp/servers)
- **PulseMCP:** [https://www.pulsemcp.com/](https://www.pulsemcp.com/)
- **YouTube Tutorial (AI-Driven Coder):** A video guide for building and using MCP servers: [https://www.youtube.com/watch?v=b5pqTNiuuJg](https://www.youtube.com/watch?v=b5pqTNiuuJg)
@@ -1,705 +0,0 @@
---
title: "MCP Server Development Protocol"
description: "This protocol is designed to streamline the development process of building MCP servers with Cline."
---
> 🚀 **Build and share your MCP servers with the world.** Once you've created a great MCP server, submit it to the [Cline MCP Marketplace](https://github.com/cline/mcp-marketplace) to make it discoverable and one-click installable by thousands of developers.
## What Are MCP Servers?
Model Context Protocol (MCP) servers extend AI assistants like Cline by giving them the ability to:
- Access external APIs and services
- Retrieve real-time data
- Control applications and local systems
- Perform actions beyond what text prompts alone can achieve
Without MCP, AI assistants are powerful but isolated. With MCP, they gain the ability to interact with virtually any digital system.
## The Development Protocol
The heart of effective MCP server development is following a structured protocol. This protocol is implemented through a `.clinerules` file that lives at the **root** of your MCP working directory (/Users/your-name/Documents/Cline/MCP).
### Using `.clinerules` Files
A `.clinerules` file is a special configuration that Cline reads automatically when working in the directory where it's placed. These files:
- Configure Cline's behavior and enforce best practices
- Switch Cline into a specialized MCP development mode
- Provide a step-by-step protocol for building servers
- Implement safety measures like preventing premature completion
- Guide you through planning, implementation, and testing phases
Here's the complete MCP Server Development Protocol that should be placed in your `.clinerules` file:
````markdown
# MCP Server Development Protocol
⚠️ CRITICAL: DO NOT USE attempt_completion BEFORE TESTING ⚠️
## Step 1: Planning (PLAN MODE)
- What problem does this tool solve?
- What API/service will it use?
- What are the authentication requirements?
□ Standard API key
□ OAuth (requires separate setup script)
□ Other credentials
## Step 2: Implementation (ACT MODE)
1. Bootstrap
- For web services, JavaScript integration, or Node.js environments:
```bash
npx @modelcontextprotocol/create-server my-server
cd my-server
npm install
```
- For data science, ML workflows, or Python environments:
```bash
pip install mcp
# Or with uv (recommended)
uv add "mcp[cli]"
```
2. Core Implementation
- Use MCP SDK
- Implement comprehensive logging
- TypeScript (for web/JS projects):
```typescript
console.error("[Setup] Initializing server...")
console.error("[API] Request to endpoint:", endpoint)
console.error("[Error] Failed with:", error)
```
- Python (for data science/ML projects):
```python
import logging
logging.error('[Setup] Initializing server...')
logging.error(f'[API] Request to endpoint: {endpoint}')
logging.error(f'[Error] Failed with: {str(error)}')
```
- Add type definitions
- Handle errors with context
- Implement rate limiting if needed
3. Configuration
- Get credentials from user if needed
- Add to MCP settings:
- For TypeScript projects:
```json
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["path/to/build/index.js"],
"env": {
"API_KEY": "key"
},
"disabled": false,
"autoApprove": []
}
}
}
```
- For Python projects:
```bash
# Directly with command line
mcp install server.py -v API_KEY=key
# Or in settings.json
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["server.py"],
"env": {
"API_KEY": "key"
},
"disabled": false,
"autoApprove": []
}
}
}
```
## Step 3: Testing (BLOCKER ⛔️)
<thinking>
BEFORE using attempt_completion, I MUST verify:
□ Have I tested EVERY tool?
□ Have I confirmed success from the user for each test?
□ Have I documented the test results?
If ANY answer is "no", I MUST NOT use attempt_completion.
</thinking>
1. Test Each Tool (REQUIRED)
□ Test each tool with valid inputs
□ Verify output format is correct
⚠️ DO NOT PROCEED UNTIL ALL TOOLS TESTED
## Step 4: Completion
❗ STOP AND VERIFY:
□ Every tool has been tested with valid inputs
□ Output format is correct for each tool
Only after ALL tools have been tested can attempt_completion be used.
## Key Requirements
- ✓ Must use MCP SDK
- ✓ Must have comprehensive logging
- ✓ Must test each tool individually
- ✓ Must handle errors gracefully
- ⛔️ NEVER skip testing before completion
````
When this `.clinerules` file is present in your working directory, Cline will:
1. Start in **PLAN MODE** to design your server before implementation
2. Enforce proper implementation patterns in **ACT MODE**
3. Require testing of all tools before allowing completion
4. Guide you through the entire development lifecycle
## Getting Started
Creating an MCP server requires just a few simple steps to get started:
### 1. Create a `.clinerules` file (🚨 IMPORTANT)
First, add a `.clinerules` file to the root of your MCP working directory using the protocol above. This file configures Cline to use the MCP development protocol when working in this folder.
### 2. Start a Chat with a Clear Description
Begin your Cline chat by clearly describing what you want to build. Be specific about:
- The purpose of your MCP server
- Which API or service you want to integrate with
- Any specific tools or features you need
For example:
```plaintext
I want to build an MCP server for the AlphaAdvantage financial API.
It should allow me to get real-time stock data, perform technical
analysis, and retrieve company financial information.
```
### 3. Work Through the Protocol
Cline will automatically start in PLAN MODE, guiding you through the planning process:
- Discussing the problem scope
- Reviewing API documentation
- Planning authentication methods
- Designing tool interfaces
When ready, switch to ACT MODE using the toggle at the bottom of the chat to begin implementation.
### 4. Provide API Documentation Early
One of the most effective ways to help Cline build your MCP server is to share official API documentation right at the start:
```plaintext
Here's the API documentation for the service:
[Paste API documentation here]
```
Providing comprehensive API details (endpoints, authentication, data structures) significantly improves Cline's ability to implement an effective MCP server.
## Understanding the Two Modes
### PLAN MODE
In this collaborative phase, you work with Cline to design your MCP server:
- Define the problem scope
- Choose appropriate APIs
- Plan authentication methods
- Design the tool interfaces
- Determine data formats
### ACT MODE
Once planning is complete, Cline helps implement the server:
- Set up the project structure
- Write the implementation code
- Configure settings
- Test each component thoroughly
- Finalize documentation
## Case Study: AlphaAdvantage Stock Analysis Server
Let's walk through the development process of our AlphaAdvantage MCP server, which provides stock data analysis and reporting capabilities.
### Planning Phase
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/planning-phase.gif"
alt="Planning phase demonstration"
/>
</Frame>
During the planning phase, we:
1. **Defined the problem**: Users need access to financial data, stock analysis, and market insights directly through their AI assistant
2. **Selected the API**: AlphaAdvantage API for financial market data
- Standard API key authentication
- Rate limits of 5 requests per minute (free tier)
- Various endpoints for different financial data types
3. **Designed the tools needed**:
- Stock overview information (current price, company details)
- Technical analysis with indicators (RSI, MACD, etc.)
- Fundamental analysis (financial statements, ratios)
- Earnings report data
- News and sentiment analysis
4. **Planned data formatting**:
- Clean, well-formatted markdown output
- Tables for structured data
- Visual indicators (↑/↓) for trends
- Proper formatting of financial numbers
### Implementation
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/building-mcp-plugin.gif"
alt="Building MCP plugin demonstration"
/>
</Frame>
We began by bootstrapping the project:
```bash
npx @modelcontextprotocol/create-server alphaadvantage-mcp
cd alphaadvantage-mcp
npm install axios node-cache
```
Next, we structured our project with:
```plaintext
src/
├── api/
│ └── alphaAdvantageClient.ts # API client with rate limiting & caching
├── formatters/
│ └── markdownFormatter.ts # Output formatters for clean markdown
└── index.ts # Main MCP server implementation
```
#### API Client Implementation
The API client implementation included:
- **Rate limiting**: Enforcing the 5 requests per minute limit
- **Caching**: Reducing API calls with strategic caching
- **Error handling**: Robust error detection and reporting
- **Typed interfaces**: Clear TypeScript types for all data
Key implementation details:
```typescript
/**
* Manage rate limiting based on free tier (5 calls per minute)
*/
private async enforceRateLimit() {
if (this.requestsThisMinute >= 5) {
console.error("[Rate Limit] Rate limit reached. Waiting for next minute...");
return new Promise<void>((resolve) => {
const remainingMs = 60 * 1000 - (Date.now() % (60 * 1000));
setTimeout(resolve, remainingMs + 100); // Add 100ms buffer
});
}
this.requestsThisMinute++;
return Promise.resolve();
}
```
#### Markdown Formatting
We implemented formatters to display financial data beautifully:
```typescript
/**
* Format company overview into markdown
*/
export function formatStockOverview(overviewData: any, quoteData: any): string {
// Extract data
const overview = overviewData
const quote = quoteData["Global Quote"]
// Calculate price change
const currentPrice = parseFloat(quote["05. price"] || "0")
const priceChange = parseFloat(quote["09. change"] || "0")
const changePercent = parseFloat(quote["10. change percent"]?.replace("%", "") || "0")
// Format markdown
let markdown = `# ${overview.Symbol} (${overview.Name}) - ${formatCurrency(currentPrice)} ${addTrendIndicator(priceChange)}${changePercent > 0 ? "+" : ""}${changePercent.toFixed(2)}%\n\n`
// Add more details...
return markdown
}
```
#### Tool Implementation
We defined five tools with clear interfaces:
```typescript
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("[Setup] Listing available tools")
return {
tools: [
{
name: "get_stock_overview",
description: "Get basic company info and current quote for a stock symbol",
inputSchema: {
type: "object",
properties: {
symbol: {
type: "string",
description: "Stock symbol (e.g., 'AAPL')",
},
market: {
type: "string",
description: "Optional market (e.g., 'US')",
default: "US",
},
},
required: ["symbol"],
},
},
// Additional tools defined here...
],
}
})
```
Each tool's handler included:
- Input validation
- API client calls with error handling
- Markdown formatting of responses
- Comprehensive logging
### Testing Phase
This critical phase involved systematically testing each tool:
1. First, we configured the MCP server in the settings:
```json
{
"mcpServers": {
"alphaadvantage-mcp": {
"command": "node",
"args": ["/path/to/alphaadvantage-mcp/build/index.js"],
"env": {
"ALPHAVANTAGE_API_KEY": "YOUR_API_KEY"
},
"disabled": false,
"autoApprove": []
}
}
}
```
2. Then we tested each tool individually:
- **get_stock_overview**: Retrieved AAPL stock overview information
```markdown
# AAPL (Apple Inc) - $241.84 ↑+1.91%
**Sector:** TECHNOLOGY
**Industry:** ELECTRONIC COMPUTERS
**Market Cap:** 3.63T
**P/E Ratio:** 38.26
...
```
- **get_technical_analysis**: Obtained price action and RSI data
```markdown
# Technical Analysis: AAPL
## Daily Price Action
Current Price: $241.84 (↑$4.54, +1.91%)
### Recent Daily Prices
| Date | Open | High | Low | Close | Volume |
| ---------- | ------- | ------- | ------- | ------- | ------ |
| 2025-02-28 | $236.95 | $242.09 | $230.20 | $241.84 | 56.83M |
...
```
- **get_earnings_report**: Retrieved MSFT earnings history and formatted report
```markdown
# Earnings Report: MSFT (Microsoft Corporation)
**Sector:** TECHNOLOGY
**Industry:** SERVICES-PREPACKAGED SOFTWARE
**Current EPS:** $12.43
## Recent Quarterly Earnings
| Quarter | Date | EPS Estimate | EPS Actual | Surprise % |
| ---------- | ---------- | ------------ | ---------- | ---------- |
| 2024-12-31 | 2025-01-29 | $3.11 | $3.23 | ↑4.01% |
...
```
### Challenges and Solutions
During development, we encountered several challenges:
1. **API Rate Limiting**:
- **Challenge**: Free tier limited to 5 calls per minute
- **Solution**: Implemented queuing, enforced rate limits, and added comprehensive caching
2. **Data Formatting**:
- **Challenge**: Raw API data not user-friendly
- **Solution**: Created formatting utilities for consistent display of financial data
3. **Timeout Issues**:
- **Challenge**: Complex tools making multiple API calls could timeout
- **Solution**: Suggested breaking complex tools into smaller pieces, optimizing caching
### Lessons Learned
Our AlphaAdvantage implementation taught us several key lessons:
1. **Plan for API Limits**: Understand and design around API rate limits from the beginning
2. **Cache Strategically**: Identify high-value caching opportunities to improve performance
3. **Format for Readability**: Invest in good data formatting for improved user experience
4. **Test Every Path**: Test all tools individually before completion
5. **Handle API Complexity**: For APIs requiring multiple calls, design tools with simpler scopes
## Core Implementation Best Practices
### Comprehensive Logging
Effective logging is essential for debugging MCP servers:
```typescript
// Start-up logging
console.error("[Setup] Initializing AlphaAdvantage MCP server...")
// API request logging
console.error(`[API] Getting stock overview for ${symbol}`)
// Error handling with context
console.error(`[Error] Tool execution failed: ${error.message}`)
// Cache operations
console.error(`[Cache] Using cached data for: ${cacheKey}`)
```
### Strong Typing
Type definitions prevent errors and improve maintainability:
```typescript
export interface AlphaAdvantageConfig {
apiKey: string
cacheTTL?: Partial<typeof DEFAULT_CACHE_TTL>
baseURL?: string
}
/**
* Validate that a stock symbol is provided and looks valid
*/
function validateSymbol(symbol: unknown): asserts symbol is string {
if (typeof symbol !== "string" || symbol.trim() === "") {
throw new McpError(ErrorCode.InvalidParams, "A valid stock symbol is required")
}
// Basic symbol validation (letters, numbers, dots)
const symbolRegex = /^[A-Za-z0-9.]+$/
if (!symbolRegex.test(symbol)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid stock symbol: ${symbol}`)
}
}
```
### Intelligent Caching
Reduce API calls and improve performance:
```typescript
// Default cache TTL in seconds
const DEFAULT_CACHE_TTL = {
STOCK_OVERVIEW: 60 * 60, // 1 hour
TECHNICAL_ANALYSIS: 60 * 30, // 30 minutes
FUNDAMENTAL_ANALYSIS: 60 * 60 * 24, // 24 hours
EARNINGS_REPORT: 60 * 60 * 24, // 24 hours
NEWS: 60 * 15, // 15 minutes
}
// Check cache first
const cachedData = this.cache.get<T>(cacheKey)
if (cachedData) {
console.error(`[Cache] Using cached data for: ${cacheKey}`)
return cachedData
}
// Cache successful responses
this.cache.set(cacheKey, response.data, cacheTTL)
```
### Graceful Error Handling
Implement robust error handling that maintains a good user experience:
```typescript
try {
switch (request.params.name) {
case "get_stock_overview": {
// Implementation...
}
// Other cases...
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`)
}
} catch (error) {
console.error(`[Error] Tool execution failed: ${error instanceof Error ? error.message : String(error)}`)
if (error instanceof McpError) {
throw error
}
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
}
}
```
## MCP Resources
Resources let your MCP servers expose data to Cline without executing code. They're perfect for providing context like files, API responses, or database records that Cline can reference during conversations.
### Adding Resources to Your MCP Server
1. **Define the resources** your server will expose:
```typescript
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "file:///project/readme.md",
name: "Project README",
mimeType: "text/markdown",
},
],
}
})
```
2. **Implement read handlers** to deliver the content:
```typescript
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (request.params.uri === "file:///project/readme.md") {
const content = await fs.promises.readFile("/path/to/readme.md", "utf-8")
return {
contents: [
{
uri: request.params.uri,
mimeType: "text/markdown",
text: content,
},
],
}
}
throw new Error("Resource not found")
})
```
Resources make your MCP servers more context-aware, allowing Cline to access specific information without requiring you to copy/paste. For more information, refer to the [official documentation](https://modelcontextprotocol.io/docs/concepts/resources).
## Common Challenges and Solutions
### API Authentication Complexities
**Challenge**: APIs often have different authentication methods.
**Solution**:
- For API keys, use environment variables in the MCP configuration
- For OAuth, create a separate script to obtain refresh tokens
- Store sensitive tokens securely
```typescript
// Authenticate using API key from environment
const API_KEY = process.env.ALPHAVANTAGE_API_KEY
if (!API_KEY) {
console.error("[Error] Missing ALPHAVANTAGE_API_KEY environment variable")
process.exit(1)
}
// Initialize API client
const apiClient = new AlphaAdvantageClient({
apiKey: API_KEY,
})
```
### Missing or Limited API Features
**Challenge**: APIs may not provide all the functionality you need.
**Solution**:
- Implement fallbacks using available endpoints
- Create simulated functionality where necessary
- Transform API data to match your needs
### API Rate Limiting
**Challenge**: Most APIs have rate limits that can cause failures.
**Solution**:
- Implement proper rate limiting
- Add intelligent caching
- Provide graceful degradation
- Add transparent errors about rate limits
```typescript
if (this.requestsThisMinute >= 5) {
console.error("[Rate Limit] Rate limit reached. Waiting for next minute...")
return new Promise<void>((resolve) => {
const remainingMs = 60 * 1000 - (Date.now() % (60 * 1000))
setTimeout(resolve, remainingMs + 100) // Add 100ms buffer
})
}
```
## Additional Resources
- [MCP Protocol Documentation](https://github.com/modelcontextprotocol/mcp)
- [MCP SDK Documentation](https://github.com/modelcontextprotocol/sdk-js)
- [MCP Server Examples](https://github.com/modelcontextprotocol/servers)
-197
View File
@@ -1,197 +0,0 @@
---
title: "MCP Transport Mechanisms"
description: "Learn about the two primary transport mechanisms for communication between Cline and MCP servers: Standard Input/Output (STDIO) and Server-Sent Events (SSE). Each has distinct characteristics, advantages, and use cases."
---
Model Context Protocol (MCP) supports two primary transport mechanisms for communication between Cline and MCP servers: Standard Input/Output (STDIO) and Server-Sent Events (SSE). Each has distinct characteristics, advantages, and use cases.
## STDIO Transport
STDIO transport runs locally on your machine and communicates via standard input/output streams.
### How STDIO Transport Works
1. The client (Cline) spawns an MCP server as a child process
2. Communication happens through process streams: client writes to server's STDIN, server responds to STDOUT
3. Each message is delimited by a newline character
4. Messages are formatted as JSON-RPC 2.0
```plaintext
Client Server
| |
|<---- JSON message ----->| (via STDIN)
| | (processes request)
|<---- JSON message ------| (via STDOUT)
| |
```
### STDIO Characteristics
- **Locality**: Runs on the same machine as Cline
- **Performance**: Very low latency and overhead (no network stack involved)
- **Simplicity**: Direct process communication without network configuration
- **Relationship**: One-to-one relationship between client and server
- **Security**: Inherently more secure as no network exposure
### When to Use STDIO
STDIO transport is ideal for:
- Local integrations and tools running on the same machine
- Security-sensitive operations
- Low-latency requirements
- Single-client scenarios (one Cline instance per server)
- Command-line tools or IDE extensions
### STDIO Implementation Example
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
const server = new Server({ name: "local-server", version: "1.0.0" })
// Register tools...
// Use STDIO transport
const transport = new StdioServerTransport(server)
transport.listen()
```
## SSE Transport
Server-Sent Events (SSE) transport runs on a remote server and communicates over HTTP/HTTPS.
### How SSE Transport Works
1. The client (Cline) connects to the server's SSE endpoint via HTTP GET request
2. This establishes a persistent connection where the server can push events to the client
3. For client-to-server communication, the client makes HTTP POST requests to a separate endpoint
4. Communication happens over two channels:
- Event Stream (GET): Server-to-client updates
- Message Endpoint (POST): Client-to-server requests
```plaintext
Client Server
| |
|---- HTTP GET /events ----------->| (establish SSE connection)
|<---- SSE event stream -----------| (persistent connection)
| |
|---- HTTP POST /message --------->| (client request)
|<---- SSE event with response ----| (server response)
| |
```
### SSE Characteristics
- **Remote Access**: Can be hosted on a different machine from your Cline instance
- **Scalability**: Can handle multiple client connections concurrently
- **Protocol**: Works over standard HTTP (no special protocols needed)
- **Persistence**: Maintains a persistent connection for server-to-client messages
- **Authentication**: Can use standard HTTP authentication mechanisms
### When to Use SSE
SSE transport is better for:
- Remote access across networks
- Multi-client scenarios
- Public services
- Centralized tools that many users need to access
- Integration with web services
### SSE Implementation Example
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"
import express from "express"
const app = express()
const server = new Server({ name: "remote-server", version: "1.0.0" })
// Register tools...
// Use SSE transport
const transport = new SSEServerTransport(server)
app.use("/mcp", transport.requestHandler())
app.listen(3000, () => {
console.log("MCP server listening on port 3000")
})
```
## Local vs. Hosted: Deployment Aspects
The choice between STDIO and SSE transports directly impacts how you'll deploy and manage your MCP servers.
### STDIO: Local Deployment Model
STDIO servers run locally on the same machine as Cline, which has several important implications:
- **Installation**: The server executable must be installed on each user's machine
- **Distribution**: You need to provide installation packages for different operating systems
- **Updates**: Each instance must be updated separately
- **Resources**: Uses the local machine's CPU, memory, and disk
- **Access Control**: Relies on the local machine's filesystem permissions
- **Integration**: Easy integration with local system resources (files, processes)
- **Execution**: Starts and stops with Cline (child process lifecycle)
- **Dependencies**: Any dependencies must be installed on the user's machine
#### Practical Example
A local file search tool using STDIO would:
- Run on the user's machine
- Have direct access to the local filesystem
- Start when needed by Cline
- Not require network configuration
- Need to be installed alongside Cline or via a package manager
### SSE: Hosted Deployment Model
SSE servers can be deployed to remote servers and accessed over the network:
- **Installation**: Installed once on a server, accessed by many users
- **Distribution**: Single deployment serves multiple clients
- **Updates**: Centralized updates affect all users immediately
- **Resources**: Uses server resources, not local machine resources
- **Access Control**: Managed through authentication and authorization systems
- **Integration**: More complex integration with user-specific resources
- **Execution**: Runs as an independent service (often continuously)
- **Dependencies**: Managed on the server, not on user machines
#### Practical Example
A database query tool using SSE would:
- Run on a central server
- Connect to databases with server-side credentials
- Be continuously available for multiple users
- Require proper network security configuration
- Be deployed using container or cloud technologies
### Hybrid Approaches
Some scenarios benefit from a hybrid approach:
1. **STDIO with Network Access**: A local STDIO server that acts as a proxy to remote services
2. **SSE with Local Commands**: A remote SSE server that can trigger operations on the client machine through callbacks
3. **Gateway Pattern**: STDIO servers for local operations that connect to SSE servers for specialized functions
## Choosing Between STDIO and SSE
| Consideration | STDIO | SSE |
| -------------------- | ------------------------ | ----------------------------------- |
| **Location** | Local machine only | Local or remote |
| **Clients** | Single client | Multiple clients |
| **Performance** | Lower latency | Higher latency (network overhead) |
| **Setup Complexity** | Simpler | More complex (requires HTTP server) |
| **Security** | Inherently secure | Requires explicit security measures |
| **Network Access** | Not needed | Required |
| **Scalability** | Limited to local machine | Can distribute across network |
| **Deployment** | Per-user installation | Centralized installation |
| **Updates** | Distributed updates | Centralized updates |
| **Resource Usage** | Uses client resources | Uses server resources |
| **Dependencies** | Client-side dependencies | Server-side dependencies |
## Configuring Transports in Cline
For detailed information on configuring STDIO and SSE transports in Cline, including examples, see [Configuring MCP Servers](/mcp/configuring-mcp-servers).
-34
View File
@@ -1,34 +0,0 @@
---
title: "Telemetry"
---
### Overview
To help make Cline better for everyone, we collect anonymous usage data that helps us understand how developers are using our open-source AI coding agent. This feedback loop is crucial for improving Cline's capabilities and user experience.
We use PostHog, an open-source analytics platform, for data collection and analysis. Our telemetry implementation is fully transparent - you can review the [source code](https://github.com/cline/cline/blob/main/src/services/telemetry/TelemetryService.ts) to see exactly what we track.
### Tracking Policy
Privacy is our priority. All collected data is anonymized before being sent to PostHog, with no personally identifiable information (PII) included. Your code, prompts, and conversation content always remain private and are never collected.
### What We Track
We collect basic anonymous usage data including:
**Task Interactions:** When tasks start and finish, conversation flow (without content)\
**Mode and Tool Usage:** Switches between plan/act modes, which tools are being used\
**Token Usage:** Basic metrics about conversation length to estimate cost (not the actual content of the tokens)\
**System Context:** OS type and VS Code environment details\
**UI Activity:** Navigation patterns and feature usage
For complete transparency, you can inspect our [telemetry implementation](https://github.com/cline/cline/blob/main/src/services/telemetry/TelemetryService.ts) to see the exact events we track.
### How to Opt Out
Telemetry in Cline is entirely optional:
- When you update or install our VS Code extension, you'll see a message about our anonymous telemetry
- You can change your preference anytime in settings
Cline also respects VS Code's global telemetry settings. If you've disabled telemetry at the VS Code level, Cline's telemetry will automatically be disabled as well.
+5 -5
View File
@@ -13,7 +13,7 @@ To get started with Cline Memory Bank:
3. **Paste into Cline** - Add as custom instructions or in a .clinerules file
4. **Initialize** - Ask Cline to "initialize memory bank"
[See detailed setup instructions](#getting-started-with-memory-bank)
[See detailed setup instructions](cline-memory-bank.md#getting-started-with-memory-bank)
### Cline Memory Bank Custom Instructions \[COPY THIS]
@@ -152,7 +152,7 @@ The Memory Bank is a structured documentation system that allows Cline to mainta
The Memory Bank isn't a Cline-specific feature - it's a methodology for managing AI context through structured documentation. When you instruct Cline to "follow custom instructions," it reads the Memory Bank files to rebuild its understanding of your project.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(15).png" alt="Memory Bank Workflow" />
<img src="/assets/robot_panel_dark.png" alt="Memory Bank Workflow" />
</Frame>
#### Understanding the Files
@@ -162,7 +162,7 @@ Memory Bank files are simply markdown files you create in your project. They're
Files are organized in a hierarchical structure that builds up a complete picture of your project:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(16).png" alt="Memory Bank File Structure" />
<img src="/assets/robot_panel_dark.png" alt="Memory Bank File Structure" />
</Frame>
### Memory Bank Files Explained
@@ -223,7 +223,7 @@ Create additional files when needed to organize:
3. Ask Cline to "initialize memory bank"
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(17).png" alt="Memory Bank Setup" />
<img src="/assets/robot_panel_dark.png" alt="Memory Bank Setup" />
</Frame>
#### Project Brief Tips
@@ -287,7 +287,7 @@ As you work with Cline, your context window will eventually fill up (note the pr
This workflow ensures that important context is preserved in your Memory Bank files before the context window is cleared, allowing you to continue seamlessly in a fresh conversation.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(18).png" alt="Memory Bank Context Window" />
<img src="/assets/robot_panel_dark.png" alt="Memory Bank Context Window" />
</Frame>
#### How often should I update the memory bank?
+2 -2
View File
@@ -22,7 +22,7 @@ To add custom instructions:
4. Paste your instructions
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(1).png" alt="Cline Logo" />
<img src="/assets/robot_panel_dark.png" alt="Cline Logo" />
</Frame>
Custom instructions are powerful for:
@@ -207,7 +207,7 @@ Located conveniently under the chat input field, this popover allows you to:
This UI significantly simplifies switching contexts and managing different sets of instructions without needing to manually edit files or configurations during a conversation.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(1).png" alt="Cline Logo" />
<img src="/assets/robot_panel_dark.png" alt="Cline Logo" />
</Frame>
## .clineignore File Guide
-90
View File
@@ -1,90 +0,0 @@
---
title: "LM Studio"
description: "A quick guide to setting up LM Studio for local AI model execution with Cline."
---
## 🤖 Setting Up LM Studio with Cline
Run AI models locally using LM Studio with Cline.
### 📋 Prerequisites
- Windows, macOS, or Linux computer with AVX2 support
- Cline installed in VS Code
### 🚀 Setup Steps
#### 1. Install LM Studio
- Visit [lmstudio.ai](https://lmstudio.ai)
- Download and install for your operating system
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(7).png" alt="LM Studio download page" />
</Frame>
#### 2. Launch LM Studio
- Open the installed application
- You'll see four tabs on the left: **Chat**, **Developer** (where you will start the server), **My Models** (where your downloaded models are stored), **Discover** (add new models)
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(10).png"
alt="LM Studio interface overview"
/>
</Frame>
#### 3. Download a Model
- Browse the "Discover" page
- Select and download your preferred model
- Wait for download to complete
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/lm-studio-download-model.gif"
alt="Downloading a model in LM Studio"
/>
</Frame>
#### 4. Start the Server
- Navigate to the "Developer" tab
- Toggle the server switch to "Running"
- Note: The server will run at `http://localhost:1234`
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/lm-studio-starting-server.gif"
alt="Starting the LM Studio server"
/>
</Frame>
#### 5. Configure Cline
1. Open VS Code
2. Click Cline settings icon
3. Select "LM Studio" as API provider
4. Select your model from the available options
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/lm-studio-select-model-cline.gif"
alt="Configuring Cline with LM Studio"
/>
</Frame>
### ⚠️ Important Notes
- Start LM Studio before using with Cline
- Keep LM Studio running in background
- First model download may take several minutes depending on size
- Models are stored locally after download
### 🔧 Troubleshooting
1. If Cline can't connect to LM Studio:
2. Verify LM Studio server is running (check Developer tab)
3. Ensure a model is loaded
4. Check your system meets hardware requirements
-88
View File
@@ -1,88 +0,0 @@
---
title: "Ollama"
description: "A quick guide to setting up Ollama for local AI model execution with Cline."
---
### 📋 Prerequisites
- Windows, macOS, or Linux computer
- Cline installed in VS Code
### 🚀 Setup Steps
#### 1. Install Ollama
- Visit [ollama.com](https://ollama.com)
- Download and install for your operating system
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(2)%20(1)%20(1).png"
alt="Ollama download page"
/>
</Frame>
#### 2. Choose and Download a Model
- Browse models at [ollama.com/search](https://ollama.com/search)
- Select model and copy command:
```bash
ollama run [model-name]
```
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/ollama-model-grab%20(2).gif"
alt="Selecting a model in Ollama"
/>
</Frame>
- Open your Terminal and run the command:
- Example:
```bash
ollama run llama2
```
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/starting-ollama-terminal%20(2).gif"
alt="Running Ollama in terminal"
/>
</Frame>
**✨ Your model is now ready to use within Cline!**
#### 3. Configure Cline
1. Open VS Code
2. Click Cline settings icon
3. Select "Ollama" as API provider
4. Enter configuration:
- Base URL: `http://localhost:11434/` (default value, can be left as is)
- Select the model from your available options
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/selecting-ollama-model-cline%20(3).gif"
alt="Configuring Cline with Ollama"
/>
</Frame>
### ⚠️ Important Notes
- Start Ollama before using with Cline
- Keep Ollama running in background
- First model download may take several minutes
### 🔧 Troubleshooting
If Cline can't connect to Ollama:
1. Verify Ollama is running
2. Check base URL is correct
3. Ensure model is downloaded
Need more info? Read the [Ollama Docs](https://github.com/ollama/ollama/blob/main/docs/api.md).
@@ -1,109 +0,0 @@
---
title: "Read Me First"
---
## Running Local Models with Cline: What You Need to Know 🤖
Cline is a powerful AI coding assistant that uses tool-calling to help you write, analyze, and modify code. While running models locally can save on API costs, there's an important trade-off: local models are significantly less reliable at using these essential tools.
## Why Local Models Are Different 🔬
When you run a "local version" of a model, you're actually running a drastically simplified copy of the original. This process, called distillation, is like trying to compress a professional chef's knowledge into a basic cookbook you keep the simple recipes but lose the complex techniques and intuition.
Local models are created by training a smaller model to imitate a larger one, but they typically only retain 1-26% of the original model's capacity. This massive reduction means:
- Less ability to understand complex contexts
- Reduced capability for multi-step reasoning
- Limited tool-use abilities
- Simplified decision-making process
Think of it like running your development environment on a calculator instead of a computer it might handle basic tasks, but complex operations become unreliable or impossible.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/image%20(4).png"
alt="Local model comparison diagram"
/>
</Frame>
### What Actually Happens
When you run a local model with Cline:
#### Performance Impact 📉
- Responses are 5-10x slower than cloud services
- System resources (CPU, GPU, RAM) get heavily utilized
- Your computer may become less responsive for other tasks
#### Tool Reliability Issues 🛠️
- Code analysis becomes less accurate
- File operations may be unreliable
- Browser automation capabilities are reduced
- Terminal commands might fail more often
- Complex multi-step tasks often break down
### Hardware Requirements 💻
You'll need at minimum:
- Modern GPU with 8GB+ VRAM (RTX 3070 or better)
- 32GB+ system RAM
- Fast SSD storage
- Good cooling solution
Even with this hardware, you'll be running smaller, less capable versions of models:
| Model Size | What You Get |
| ---------- | ------------------------------------------------------- |
| 7B models | Basic coding, limited tool use |
| 14B models | Better coding, unstable tool use |
| 32B models | Good coding, inconsistent tool use |
| 70B models | Best local performance, but requires expensive hardware |
Put simply, the cloud (API) versions of these models are the full-bore version of the model. The full version of DeepSeek-R1 is 671B. These distilled models are essentially "watered-down" versions of the cloud model.
### Practical Recommendations 💡
#### Consider This Approach
1. Use cloud models for:
- Complex development tasks
- When tool reliability is crucial
- Multi-step operations
- Critical code changes
2. Use local models for:
- Simple code completion
- Basic documentation
- When privacy is paramount
- Learning and experimentation
#### If You Must Go Local
- Start with smaller models
- Keep tasks simple and focused
- Save work frequently
- Be prepared to switch to cloud models for complex operations
- Monitor system resources
### Common Issues 🚨
- **"Tool execution failed":** Local models often struggle with complex tool chains. Simplify your prompt.
- **"No connection could be made because the target machine actively refused it":** This usually means that the Ollama or LM Studio server isn't running, or is running on a different port/address than Cline is configured to use. Double-check the Base URL address in your API Provider settings.
- **"Cline is having trouble...":** Increase your model's context length to its maximum size.
- **Slow or incomplete responses:** Local models can be slower than cloud-based models, especially on less powerful hardware. If performance is an issue, try using a smaller model. Expect significantly longer processing times.
- **System stability:** Watch for high GPU/CPU usage and temperature
- **Context limitations:** Local models often have smaller context windows than cloud models. Break tasks down into smaller pieces.
### Looking Ahead 🔮
Local model capabilities are improving, but they're not yet a complete replacement for cloud services, especially for Cline's tool-based functionality. Consider your specific needs and hardware capabilities carefully before committing to a local-only approach.
### Need Help? 🤝
- Join our [Discord](https://discord.gg/cline) community and [r/cline](https://www.reddit.com/r/CLine/)
- Check the latest compatibility guides
- Share your experiences with other developers
Remember: When in doubt, prioritize reliability over cost savings for important development work.
+42 -26
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.15.2",
"version": "3.14.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.15.2",
"version": "3.14.0",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.12.4",
@@ -42,11 +42,11 @@
"globby": "^14.0.2",
"iconv-lite": "^0.6.3",
"ignore": "^7.0.3",
"image-size": "^2.0.2",
"isbinaryfile": "^5.0.2",
"jschardet": "^3.1.4",
"mammoth": "^1.8.0",
"monaco-vscode-textmate-theme-converter": "^0.1.7",
"node-cache": "^5.1.2",
"ollama": "^0.5.13",
"open-graph-scraper": "^6.9.0",
"openai": "^4.83.0",
@@ -12866,6 +12866,14 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -16571,18 +16579,6 @@
"node": ">= 4"
}
},
"node_modules/image-size": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
"integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==",
"license": "MIT",
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -19764,6 +19760,17 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
"dependencies": {
"clone": "2.x"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -20245,9 +20252,10 @@
}
},
"node_modules/ollama": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.15.tgz",
"integrity": "sha512-TSaZSJyP7MQJFjSmmNsoJiriwa3U+/UJRw6+M8aucs5dTsaWNZsBIGpDb5rXnW6nXxJBB/z79gZY8IaiIQgelQ==",
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.13.tgz",
"integrity": "sha512-qK3eE2GjMYjCiTknEJfAHjbUzUqgVtf9qtzjxWrkwBZgBG7kOB6Z4+Ov4fbvDjmKKHv+rpuTsWFg4jZvVjNBtQ==",
"license": "MIT",
"dependencies": {
"whatwg-fetch": "^3.6.20"
}
@@ -34879,6 +34887,11 @@
}
}
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
},
"clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -37417,11 +37430,6 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz",
"integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA=="
},
"image-size": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
"integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -39558,6 +39566,14 @@
"@types/nlcst": "^2.0.0"
}
},
"node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
"requires": {
"clone": "2.x"
}
},
"node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -39880,9 +39896,9 @@
}
},
"ollama": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.15.tgz",
"integrity": "sha512-TSaZSJyP7MQJFjSmmNsoJiriwa3U+/UJRw6+M8aucs5dTsaWNZsBIGpDb5rXnW6nXxJBB/z79gZY8IaiIQgelQ==",
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.13.tgz",
"integrity": "sha512-qK3eE2GjMYjCiTknEJfAHjbUzUqgVtf9qtzjxWrkwBZgBG7kOB6Z4+Ov4fbvDjmKKHv+rpuTsWFg4jZvVjNBtQ==",
"requires": {
"whatwg-fetch": "^3.6.20"
}
+38 -31
View File
@@ -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.15.2",
"version": "3.14.0",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -40,8 +40,6 @@
"llama"
],
"activationEvents": [
"onLanguage",
"onStartupFinished",
"workspaceContains:evals.env"
],
"main": "./dist/extension.js",
@@ -120,12 +118,6 @@
"command": "cline.focusChatInput",
"title": "Jump to Chat Input",
"category": "Cline"
},
{
"command": "cline.generateGitCommitMessage",
"title": "Generate Commit Message with Cline",
"category": "Cline",
"icon": "$(robot)"
}
],
"keybindings": [
@@ -136,10 +128,6 @@
"win": "ctrl+'",
"linux": "ctrl+'",
"when": "editorHasSelection"
},
{
"command": "cline.generateGitCommitMessage",
"when": "scmProvider == git"
}
],
"menus": {
@@ -219,29 +207,50 @@
"command": "cline.addTerminalOutputToChat",
"group": "navigation"
}
],
"scm/title": [
{
"command": "cline.generateGitCommitMessage",
"group": "navigation",
"when": "scmProvider == git"
}
],
"commandPalette": [
{
"command": "cline.generateGitCommitMessage",
"when": "scmProvider == git"
}
]
},
"configuration": {
"title": "Cline",
"properties": {
"cline.vsCodeLmModelSelector": {
"type": "object",
"properties": {
"vendor": {
"type": "string",
"description": "The vendor of the language model (e.g. copilot)"
},
"family": {
"type": "string",
"description": "The family of the language model (e.g. gpt-4)"
}
},
"description": "Settings for VSCode Language Model API"
},
"cline.enableCheckpoints": {
"type": "boolean",
"default": true,
"description": "Enables extension to save checkpoints of workspace throughout the task. Uses git under the hood which may not work well with large workspaces."
},
"cline.disableBrowserTool": {
"type": "boolean",
"default": false,
"description": "Disables extension from spawning browser session."
},
"cline.modelSettings.o3Mini.reasoningEffort": {
"type": "string",
"enum": [
"low",
"medium",
"high"
],
"default": "medium",
"description": "Controls the reasoning effort when using an OpenAI reasoning model. Higher values may result in more thorough but slower responses."
},
"cline.chromeExecutablePath": {
"type": "string",
"default": null,
"description": "Path to Chrome executable for browser use functionality. If not set, the extension will attempt to find or download it automatically."
},
"cline.preferredLanguage": {
"type": "string",
"enum": [
@@ -282,7 +291,7 @@
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
"protos": "node proto/build-proto.js && prettier src/shared/proto src/core/controller webview-ui/src/services --write",
"protos": "node proto/build-proto.js && prettier src/shared/proto --write && prettier src/core/controller --write",
"compile-tests": "node ./scripts/build-tests.js",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "npm run compile-tests && npm run compile && npm run lint",
@@ -304,9 +313,7 @@
"prepare": "husky",
"changeset": "changeset",
"version-packages": "changeset version",
"docs": "cd docs && mintlify dev",
"docs:check-links": "cd docs && mintlify broken-links",
"docs:rename-file": "cd docs && mintlify rename",
"docs:preview": "cd docs && mintlify dev",
"report-issue": "node scripts/report-issue.js"
},
"devDependencies": {
@@ -379,11 +386,11 @@
"globby": "^14.0.2",
"iconv-lite": "^0.6.3",
"ignore": "^7.0.3",
"image-size": "^2.0.2",
"isbinaryfile": "^5.0.2",
"jschardet": "^3.1.4",
"mammoth": "^1.8.0",
"monaco-vscode-textmate-theme-converter": "^0.1.7",
"node-cache": "^5.1.2",
"ollama": "^0.5.13",
"open-graph-scraper": "^6.9.0",
"openai": "^4.83.0",
-4
View File
@@ -40,8 +40,6 @@ message BrowserSettings {
Viewport viewport = 1;
optional string remote_browser_host = 2;
optional bool remote_browser_enabled = 3;
optional string chrome_executable_path = 4;
optional bool disable_tool_use = 5;
}
message UpdateBrowserSettingsRequest {
@@ -49,6 +47,4 @@ message UpdateBrowserSettingsRequest {
Viewport viewport = 2;
optional string remote_browser_host = 3;
optional bool remote_browser_enabled = 4;
optional string chrome_executable_path = 5;
optional bool disable_tool_use = 6;
}
+27 -287
View File
@@ -12,28 +12,11 @@ const require = createRequire(import.meta.url)
const protoc = path.join(require.resolve("grpc-tools"), "../bin/protoc")
const tsProtoPlugin = require.resolve("ts-proto/protoc-gen-ts_proto")
// Get script directory and root directory
const __filename = fileURLToPath(import.meta.url)
const SCRIPT_DIR = path.dirname(__filename)
const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
// List of gRPC services
// To add a new service, simply add it to this map and run this script
// The service handler will be automatically discovered and used by grpc-handler.ts
const serviceNameMap = {
account: "cline.AccountService",
browser: "cline.BrowserService",
checkpoints: "cline.CheckpointsService",
file: "cline.FileService",
mcp: "cline.McpService",
state: "cline.StateService",
task: "cline.TaskService",
web: "cline.WebService",
models: "cline.ModelsService",
slash: "cline.SlashService",
// Add new services here - no other code changes needed!
}
const serviceDirs = Object.keys(serviceNameMap).map((serviceKey) => path.join(ROOT_DIR, "src", "core", "controller", serviceKey))
async function main() {
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
@@ -50,9 +33,6 @@ async function main() {
await fs.unlink(path.join(TS_OUT_DIR, file))
}
// Check for missing proto files for services in serviceNameMap
await ensureProtoFilesExist()
// Process all proto files
console.log(chalk.cyan("Processing proto files from"), SCRIPT_DIR)
const protoFiles = await globby("*.proto", { cwd: SCRIPT_DIR })
@@ -84,135 +64,41 @@ async function main() {
console.log(chalk.green("Protocol Buffer code generation completed successfully."))
console.log(chalk.green(`TypeScript files generated in: ${TS_OUT_DIR}`))
// Generate method registration files
await generateMethodRegistrations()
await generateServiceConfig()
await generateGrpcClientConfig()
}
/**
* Generate a gRPC client configuration file for the webview
* This eliminates the need for manual imports and client creation in grpc-client.ts
*/
async function generateGrpcClientConfig() {
console.log(chalk.cyan("Generating gRPC client configuration..."))
const serviceImports = []
const serviceClientCreations = []
const serviceExports = []
// Process each service in the serviceNameMap
for (const [dirName, fullServiceName] of Object.entries(serviceNameMap)) {
const capitalizedName = dirName.charAt(0).toUpperCase() + dirName.slice(1)
// Add import statement
serviceImports.push(`import { ${capitalizedName}ServiceDefinition } from "@shared/proto/${dirName}"`)
// Add client creation
serviceClientCreations.push(
`const ${capitalizedName}ServiceClient = createGrpcClient(${capitalizedName}ServiceDefinition)`,
)
// Add to exports
serviceExports.push(`${capitalizedName}ServiceClient`)
// Make the script executable
try {
await fs.chmod(path.join(SCRIPT_DIR, "build-proto.js"), 0o755)
} catch (error) {
console.warn(chalk.yellow("Warning: Could not make script executable:"), error)
}
// Generate the file content
const content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createGrpcClient } from "./grpc-client-base"
${serviceImports.join("\n")}
${serviceClientCreations.join("\n")}
export {
${serviceExports.join(",\n\t")}
}`
const configPath = path.join(ROOT_DIR, "webview-ui", "src", "services", "grpc-client.ts")
await fs.writeFile(configPath, content)
console.log(chalk.green(`Generated gRPC client at ${configPath}`))
}
/**
* Parse proto files to extract streaming method information
* @param protoFiles Array of proto file names
* @param scriptDir Directory containing proto files
* @returns Map of service names to their streaming methods
*/
async function parseProtoForStreamingMethods(protoFiles, scriptDir) {
console.log(chalk.cyan("Parsing proto files for streaming methods..."))
// Map of service name to array of streaming method names
const streamingMethodsMap = new Map()
for (const protoFile of protoFiles) {
const content = await fs.readFile(path.join(scriptDir, protoFile), "utf8")
// Extract package name
const packageMatch = content.match(/package\s+([^;]+);/)
const packageName = packageMatch ? packageMatch[1].trim() : "unknown"
// Extract service definitions
const serviceMatches = Array.from(content.matchAll(/service\s+(\w+)\s*\{([^}]+)\}/g))
for (const serviceMatch of serviceMatches) {
const serviceName = serviceMatch[1]
const serviceBody = serviceMatch[2]
const fullServiceName = `${packageName}.${serviceName}`
// Extract method definitions with streaming
const methodMatches = Array.from(
serviceBody.matchAll(/rpc\s+(\w+)\s*\(\s*(stream\s+)?(\w+)\s*\)\s*returns\s*\(\s*(stream\s+)?(\w+)\s*\)/g),
)
const streamingMethods = []
for (const methodMatch of methodMatches) {
const methodName = methodMatch[1]
const isRequestStreaming = !!methodMatch[2]
const requestType = methodMatch[3]
const isResponseStreaming = !!methodMatch[4]
const responseType = methodMatch[5]
if (isResponseStreaming) {
streamingMethods.push({
name: methodName,
requestType,
responseType,
isRequestStreaming,
})
}
}
if (streamingMethods.length > 0) {
streamingMethodsMap.set(fullServiceName, streamingMethods)
}
}
}
return streamingMethodsMap
}
async function generateMethodRegistrations() {
console.log(chalk.cyan("Generating method registration files..."))
// Parse proto files for streaming methods
const protoFiles = await globby("*.proto", { cwd: SCRIPT_DIR })
const streamingMethodsMap = await parseProtoForStreamingMethods(protoFiles, SCRIPT_DIR)
const serviceDirs = [
path.join(ROOT_DIR, "src", "core", "controller", "account"),
path.join(ROOT_DIR, "src", "core", "controller", "browser"),
path.join(ROOT_DIR, "src", "core", "controller", "checkpoints"),
path.join(ROOT_DIR, "src", "core", "controller", "file"),
path.join(ROOT_DIR, "src", "core", "controller", "mcp"),
path.join(ROOT_DIR, "src", "core", "controller", "task"),
path.join(ROOT_DIR, "src", "core", "controller", "web-content"),
// Add more service directories here as needed
]
for (const serviceDir of serviceDirs) {
try {
await fs.access(serviceDir)
} catch (error) {
console.log(chalk.cyan(`Creating directory ${serviceDir} for new service`))
await fs.mkdir(serviceDir, { recursive: true })
console.log(chalk.gray(`Skipping ${serviceDir} - directory does not exist`))
continue
}
const serviceName = path.basename(serviceDir)
const registryFile = path.join(serviceDir, "methods.ts")
const indexFile = path.join(serviceDir, "index.ts")
const fullServiceName = serviceNameMap[serviceName]
const streamingMethods = streamingMethodsMap.get(fullServiceName) || []
console.log(chalk.cyan(`Generating method registrations for ${serviceName}...`))
@@ -222,8 +108,8 @@ async function generateMethodRegistrations() {
// Filter out index.ts and methods.ts
const implementationFiles = files.filter((file) => file !== "index.ts" && file !== "methods.ts")
// Create the methods.ts file with header
let methodsContent = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Create the output file with header
let content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
// Import all method implementations
@@ -232,177 +118,31 @@ import { registerMethod } from "./index"\n`
// Add imports for all implementation files
for (const file of implementationFiles) {
const baseName = path.basename(file, ".ts")
methodsContent += `import { ${baseName} } from "./${baseName}"\n`
}
// Add streaming methods information
if (streamingMethods.length > 0) {
methodsContent += `\n// Streaming methods for this service
export const streamingMethods = ${JSON.stringify(
streamingMethods.map((m) => m.name),
null,
2,
)}\n`
content += `import { ${baseName} } from "./${baseName}"\n`
}
// Add registration function
methodsContent += `\n// Register all ${serviceName} service methods
content += `\n// Register all ${serviceName} service methods
export function registerAllMethods(): void {
\t// Register each method with the registry\n`
// Add registration statements
for (const file of implementationFiles) {
const baseName = path.basename(file, ".ts")
const isStreaming = streamingMethods.some((m) => m.name === baseName)
if (isStreaming) {
methodsContent += `\tregisterMethod("${baseName}", ${baseName}, { isStreaming: true })\n`
} else {
methodsContent += `\tregisterMethod("${baseName}", ${baseName})\n`
}
content += `\tregisterMethod("${baseName}", ${baseName})\n`
}
// Close the function
methodsContent += `}`
content += `}`
// Write the methods.ts file
await fs.writeFile(registryFile, methodsContent)
// Write the file
await fs.writeFile(registryFile, content)
console.log(chalk.green(`Generated ${registryFile}`))
// Generate index.ts file
const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1)
const indexContent = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../grpc-service"
import { StreamingResponseHandler } from "../grpc-handler"
import { registerAllMethods } from "./methods"
// Create ${serviceName} service registry
const ${serviceName}Service = createServiceRegistry("${serviceName}")
// Export the method handler types and registration function
export type ${capitalizedServiceName}MethodHandler = ServiceMethodHandler
export type ${capitalizedServiceName}StreamingMethodHandler = StreamingMethodHandler
export const registerMethod = ${serviceName}Service.registerMethod
// Export the request handlers
export const handle${capitalizedServiceName}ServiceRequest = ${serviceName}Service.handleRequest
export const handle${capitalizedServiceName}ServiceStreamingRequest = ${serviceName}Service.handleStreamingRequest
export const isStreamingMethod = ${serviceName}Service.isStreamingMethod
// Register all ${serviceName} methods
registerAllMethods()`
// Write the index.ts file
await fs.writeFile(indexFile, indexContent)
console.log(chalk.green(`Generated ${indexFile}`))
}
console.log(chalk.green("Method registration files generated successfully."))
}
/**
* Generate a service configuration file that maps service names to their handlers
* This eliminates the need for manual switch/case statements in grpc-handler.ts
*/
async function generateServiceConfig() {
console.log(chalk.cyan("Generating service configuration file..."))
const serviceImports = []
const serviceConfigs = []
// Add all services from the serviceNameMap
for (const [dirName, fullServiceName] of Object.entries(serviceNameMap)) {
const capitalizedName = dirName.charAt(0).toUpperCase() + dirName.slice(1)
serviceImports.push(
`import { handle${capitalizedName}ServiceRequest, handle${capitalizedName}ServiceStreamingRequest } from "./${dirName}/index"`,
)
serviceConfigs.push(`
"${fullServiceName}": {
requestHandler: handle${capitalizedName}ServiceRequest,
streamingHandler: handle${capitalizedName}ServiceStreamingRequest
}`)
}
const content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { Controller } from "./index"
import { StreamingResponseHandler } from "./grpc-handler"
${serviceImports.join("\n")}
/**
* Configuration for a service handler
*/
export interface ServiceHandlerConfig {
requestHandler: (controller: Controller, method: string, message: any) => Promise<any>;
streamingHandler: (controller: Controller, method: string, message: any, responseStream: StreamingResponseHandler, requestId?: string) => Promise<void>;
}
/**
* Map of service names to their handler configurations
*/
export const serviceHandlers: Record<string, ServiceHandlerConfig> = {${serviceConfigs.join(",")}
};`
const configPath = path.join(ROOT_DIR, "src", "core", "controller", "grpc-service-config.ts")
await fs.writeFile(configPath, content)
console.log(chalk.green(`Generated service configuration at ${configPath}`))
}
/**
* Ensure that a .proto file exists for each service in the serviceNameMap
* If a .proto file doesn't exist, create a template file
*/
async function ensureProtoFilesExist() {
console.log(chalk.cyan("Checking for missing proto files..."))
// Get existing proto files
const existingProtoFiles = await globby("*.proto", { cwd: SCRIPT_DIR })
const existingProtoServices = existingProtoFiles.map((file) => path.basename(file, ".proto"))
// Check each service in serviceNameMap
for (const [serviceName, fullServiceName] of Object.entries(serviceNameMap)) {
if (!existingProtoServices.includes(serviceName)) {
console.log(chalk.yellow(`Creating template proto file for ${serviceName}...`))
// Extract service class name from full name (e.g., "cline.ModelsService" -> "ModelsService")
const serviceClassName = fullServiceName.split(".").pop()
// Create template proto file
const protoContent = `syntax = "proto3";
package cline;
option java_package = "bot.cline.proto";
option java_multiple_files = true;
import "common.proto";
// ${serviceClassName} provides methods for managing ${serviceName}
service ${serviceClassName} {
// Add your RPC methods here
// Example (String is from common.proto, responses should be generic types):
// rpc YourMethod(YourRequest) returns (String);
}
// Add your message definitions here
// Example (Requests must always start with Metadata):
// message YourRequest {
// Metadata metadata = 1;
// string stringField = 2;
// int32 int32Field = 3;
// }
`
// Write the template proto file
const protoFilePath = path.join(SCRIPT_DIR, `${serviceName}.proto`)
await fs.writeFile(protoFilePath, protoContent)
console.log(chalk.green(`Created template proto file at ${protoFilePath}`))
}
}
}
// Run the main function
main().catch((error) => {
console.error(chalk.red("Error:"), error)
-4
View File
@@ -54,7 +54,3 @@ message BooleanRequest {
message Boolean {
bool value = 1;
}
message StringArray {
repeated string values = 1;
}
+1 -24
View File
@@ -25,9 +25,6 @@ service FileService {
// Convert URIs to workspace-relative paths
rpc getRelativePaths(RelativePathsRequest) returns (RelativePaths);
// Search for files in the workspace with fuzzy matching
rpc searchFiles(FileSearchRequest) returns (FileSearchResults);
}
// Request to convert a list of URIs to relative paths
@@ -41,27 +38,6 @@ message RelativePaths {
repeated string paths = 1;
}
// Request for file search operations
message FileSearchRequest {
Metadata metadata = 1;
string query = 2; // Search query string
optional string mentions_request_id = 3; // Optional request ID for tracking requests
optional int32 limit = 4; // Optional limit for results (default: 20)
}
// Result for file search operations
message FileSearchResults {
repeated FileInfo results = 1; // Array of file/folder results
optional string mentions_request_id = 2; // Echo of the request ID for tracking
}
// File information structure for search results
message FileInfo {
string path = 1; // Relative path from workspace root
string type = 2; // "file" or "folder"
optional string label = 3; // Display name (usually basename)
}
// Response for searchCommits
message GitCommits {
repeated GitCommit commits = 1;
@@ -90,3 +66,4 @@ message RuleFile {
string display_name = 2; // Filename for display purposes
bool already_exists = 3; // For createRuleFile, indicates if file already existed
}
-1
View File
@@ -10,7 +10,6 @@ service McpService {
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
rpc addRemoteMcpServer(AddRemoteMcpServerRequest) returns (McpServers);
rpc downloadMcp(StringRequest) returns (Empty);
}
message ToggleMcpServerRequest {
-61
View File
@@ -1,61 +0,0 @@
syntax = "proto3";
package cline;
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
rpc getOllamaModels(StringRequest) returns (StringArray);
// Fetches available models from LM Studio
rpc getLmStudioModels(StringRequest) returns (StringArray);
// Fetches available models from VS Code LM API
rpc getVsCodeLmModels(EmptyRequest) returns (VsCodeLmModelsArray);
// Refreshes and returns OpenRouter models
rpc refreshOpenRouterModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Refreshes and returns OpenAI models
rpc refreshOpenAiModels(OpenAiModelsRequest) returns (StringArray);
// Refreshes and returns Requesty models
rpc refreshRequestyModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
}
// List of VS Code LM models
message VsCodeLmModelsArray {
repeated VsCodeLmModel models = 1;
}
// Structure representing a VS Code LM model
message VsCodeLmModel {
string vendor = 1;
string family = 2;
string version = 3;
string id = 4;
}
// For OpenRouterCompatibleModelInfo structure in OpenRouterModels
message OpenRouterModelInfo {
int32 max_tokens = 1;
int32 context_window = 2;
bool supports_images = 3;
bool supports_prompt_cache = 4;
double input_price = 5;
double output_price = 6;
double cache_writes_price = 7;
double cache_reads_price = 8;
string description = 9;
}
// Shared response message for model information
message OpenRouterCompatibleModelInfo {
map<string, OpenRouterModelInfo> models = 1;
}
// Request for fetching OpenAI models
message OpenAiModelsRequest {
Metadata metadata = 1;
string baseUrl = 2;
string apiKey = 3;
}
-14
View File
@@ -1,14 +0,0 @@
syntax = "proto3";
package cline;
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
rpc reportBug(StringRequest) returns (Empty);
rpc condense(StringRequest) returns (Empty);
}
-14
View File
@@ -1,14 +0,0 @@
syntax = "proto3";
package cline;
import "common.proto";
service StateService {
rpc getLatestState(EmptyRequest) returns (State);
rpc subscribeToState(EmptyRequest) returns (stream State);
rpc toggleFavoriteModel(StringRequest) returns (Empty);
}
message State {
string state_json = 1;
}
-65
View File
@@ -15,16 +15,6 @@ service TaskService {
rpc deleteTasksWithIds(StringArrayRequest) returns (Empty);
// Creates a new task with the given text and optional images
rpc newTask(NewTaskRequest) returns (Empty);
// Shows a task with the specified ID
rpc showTaskWithId(StringRequest) returns (TaskResponse);
// Exports a task with the given ID to markdown
rpc exportTaskWithId(StringRequest) returns (Empty);
// Toggles the favorite status of a task
rpc toggleTaskFavorite(TaskFavoriteRequest) returns (Empty);
// Deletes all non-favorited tasks
rpc deleteNonFavoritedTasks(EmptyRequest) returns (DeleteNonFavoritedTasksResults);
// Gets filtered task history
rpc getTaskHistory(GetTaskHistoryRequest) returns (TaskHistoryArray);
}
// Request message for creating a new task
@@ -33,58 +23,3 @@ message NewTaskRequest {
string text = 2;
repeated string images = 3;
}
// Request message for toggling task favorite status
message TaskFavoriteRequest {
Metadata metadata = 1;
string task_id = 2;
bool is_favorited = 3;
}
// Response for task details
message TaskResponse {
string id = 1;
string task = 2;
int64 ts = 3;
bool is_favorited = 4;
int64 size = 5;
double total_cost = 6;
int32 tokens_in = 7;
int32 tokens_out = 8;
int32 cache_writes = 9;
int32 cache_reads = 10;
}
// Results returned when deleting non-favorited tasks
message DeleteNonFavoritedTasksResults {
int32 tasks_preserved = 1;
int32 tasks_deleted = 2;
}
// Request for getting task history with filtering
message GetTaskHistoryRequest {
Metadata metadata = 1;
bool favorites_only = 2;
string search_query = 3;
string sort_by = 4;
}
// Response for task history
message TaskHistoryArray {
repeated TaskItem tasks = 1;
int32 total_count = 2;
}
// Task item details for history list
message TaskItem {
string id = 1;
string task = 2;
int64 ts = 3;
bool is_favorited = 4;
int64 size = 5;
double total_cost = 6;
int32 tokens_in = 7;
int32 tokens_out = 8;
int32 cache_writes = 9;
int32 cache_reads = 10;
}
+1 -1
View File
@@ -6,7 +6,7 @@ option java_multiple_files = true;
import "common.proto";
service WebService {
service WebContentService {
rpc checkIsImageUrl(StringRequest) returns (IsImageUrl);
}
-3
View File
@@ -19,7 +19,6 @@ import { DoubaoHandler } from "./providers/doubao"
import { VsCodeLmHandler } from "./providers/vscode-lm"
import { ClineHandler } from "./providers/cline"
import { LiteLlmHandler } from "./providers/litellm"
import { FireworksHandler } from "./providers/fireworks"
import { AskSageHandler } from "./providers/asksage"
import { XAIHandler } from "./providers/xai"
import { SambanovaHandler } from "./providers/sambanova"
@@ -59,8 +58,6 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
return new DeepSeekHandler(options)
case "requesty":
return new RequestyHandler(options)
case "fireworks":
return new FireworksHandler(options)
case "together":
return new TogetherHandler(options)
case "qwen":
+2 -6
View File
@@ -272,14 +272,10 @@ export class AwsBedrockHandler implements ApiHandler {
}
/**
* Gets the appropriate model ID, accounting for cross-region inference if enabled.
* If the model ID is an ARN that contains a slash, you will get the URL encoded ARN.
* Gets the appropriate model ID, accounting for cross-region inference if enabled
*/
async getModelId(): Promise<string> {
if (this.options.awsBedrockCustomSelected && this.getModel().id.includes("/")) {
return encodeURIComponent(this.getModel().id)
}
if (!this.options.awsBedrockCustomSelected && this.options.awsUseCrossRegionInference) {
if (this.options.awsUseCrossRegionInference) {
const regionPrefix = this.getRegion().slice(0, 3)
switch (regionPrefix) {
case "us-":
+2 -1
View File
@@ -107,8 +107,9 @@ export class ClineHandler implements ApiHandler {
const generation = response.data
return {
type: "usage",
// at this time there's no support for gatting cached_tokens from generation endpoint
cacheWriteTokens: 0,
cacheReadTokens: generation?.native_tokens_cached || 0,
cacheReadTokens: 0,
inputTokens: generation?.native_tokens_prompt || 0,
outputTokens: generation?.native_tokens_completion || 0,
totalCost: generation?.total_cost || 0,
-94
View File
@@ -1,94 +0,0 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { withRetry } from "../retry"
import { ApiHandler } from ".."
import {
ApiHandlerOptions,
DeepSeekModelId,
ModelInfo,
deepSeekDefaultModelId,
deepSeekModels,
openAiModelInfoSaneDefaults,
} from "../../shared/api"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
export class FireworksHandler implements ApiHandler {
private options: ApiHandlerOptions
private client: OpenAI
constructor(options: ApiHandlerOptions) {
this.options = options
this.client = new OpenAI({
baseURL: "https://api.fireworks.ai/inference/v1",
apiKey: this.options.fireworksApiKey,
})
}
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const modelId = this.options.fireworksModelId ?? ""
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
const stream = await this.client.chat.completions.create({
model: modelId,
...(this.options.fireworksModelMaxCompletionTokens
? { max_completion_tokens: this.options.fireworksModelMaxCompletionTokens }
: {}),
...(this.options.fireworksModelMaxTokens ? { max_tokens: this.options.fireworksModelMaxTokens } : {}),
messages: openAiMessages,
stream: true,
stream_options: { include_usage: true },
temperature: 0,
})
let reasoning: string | null = null
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (reasoning || delta?.content?.includes("<think>")) {
reasoning = (reasoning || "") + (delta.content ?? "")
}
if (delta?.content && !reasoning) {
yield {
type: "text",
text: delta.content,
}
}
if (reasoning || ("reasoning_content" in delta && delta.reasoning_content)) {
yield {
type: "reasoning",
reasoning: delta.content || ((delta as any).reasoning_content as string | undefined) || "",
}
if (reasoning?.includes("</think>")) {
// Reset so the next chunk is regular content
reasoning = null
}
}
if (chunk.usage) {
yield {
type: "usage",
inputTokens: chunk.usage.prompt_tokens || 0, // (deepseek reports total input AND cache reads/writes, see context caching: https://api-docs.deepseek.com/guides/kv_cache) where the input tokens is the sum of the cache hits/misses, while anthropic reports them as separate tokens. This is important to know for 1) context management truncation algorithm, and 2) cost calculation (NOTE: we report both input and cache stats but for now set input price to 0 since all the cost calculation will be done using cache hits/misses)
outputTokens: chunk.usage.completion_tokens || 0,
// @ts-ignore-next-line
cacheReadTokens: chunk.usage.prompt_cache_hit_tokens || 0,
// @ts-ignore-next-line
cacheWriteTokens: chunk.usage.prompt_cache_miss_tokens || 0,
}
}
}
}
getModel(): { id: string; info: ModelInfo } {
return {
id: this.options.fireworksModelId ?? "",
info: openAiModelInfoSaneDefaults,
}
}
}
+308 -6
View File
@@ -1,6 +1,7 @@
import type { Anthropic } from "@anthropic-ai/sdk"
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
import { GoogleGenAI, type Content, type GenerateContentConfig, type GenerateContentResponseUsageMetadata } from "@google/genai"
import NodeCache from "node-cache"
import { withRetry } from "../retry"
import { ApiHandler } from "../"
import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "@shared/api"
@@ -38,6 +39,12 @@ export class GeminiHandler implements ApiHandler {
private options: ApiHandlerOptions
private client: GoogleGenAI
// Enhanced caching system
private contentCaches: NodeCache // Stores cache details (key, count, etc.)
private isCacheBusy = false
private taskCacheNames: Map<string, string> = new Map() // Maps taskId to cache name for stable lookup
private taskCacheTokens: Map<string, number> = new Map() // Maps taskId to total tokens in cache
constructor(options: GeminiHandlerOptions) {
// Store the options
this.options = options
@@ -60,13 +67,26 @@ export class GeminiHandler implements ApiHandler {
this.client = new GoogleGenAI({ apiKey: options.geminiApiKey })
}
// Initialize cache with TTL and check period
this.contentCaches = new NodeCache({
stdTTL: DEFAULT_CACHE_TTL_SECONDS,
checkperiod: DEFAULT_CACHE_TTL_SECONDS,
})
}
/**
* Creates a message using the Gemini API with implicit caching.
* Creates a message using the Gemini API with optimized caching and split cost accounting.
*
* This method implements a task-based caching strategy:
* 1. Each task gets its own cache, identified by taskId
* 2. On first call for a task, a new cache is created
* 3. On subsequent calls, the existing cache is reused and only new messages are sent
* 4. Cache operations are tracked for accurate cost accounting
*
* Cost accounting:
* - Immediate costs (returned in the usage object): Input tokens, output tokens, cache read costs
* - Ongoing costs (tracked at task level): Cache storage costs for the TTL period
*
* @param systemPrompt The system prompt to use for the message
* @param messages The conversation history to include in the message
@@ -77,6 +97,54 @@ export class GeminiHandler implements ApiHandler {
const { id: model, info } = this.getModel()
const contents = messages.map(convertAnthropicMessageToGemini)
// Ensure we have a stable cache key (taskId)
if (!this.options.taskId) {
console.warn("[GeminiHandler] No taskId provided, caching will be disabled")
}
const taskId = this.options.taskId
// Calculate total content length for cache eligibility check
const contentsLength = systemPrompt.length + this.getMessagesLength(contents)
// Minimum token threshold for caching (approx 4096 tokens)
const CONTEXT_CACHE_TOKEN_MINIMUM = 4096
let uncachedContent: Content[] | undefined = undefined
let cachedContent: string | undefined = undefined
// Check if caching is available and content is large enough to benefit from caching
// We only enable caching for conversations above a certain size to avoid overhead for small requests
const isCacheAvailable = info.supportsPromptCache && contentsLength > 4 * CONTEXT_CACHE_TOKEN_MINIMUM && taskId
// This flag tracks whether this operation involves a cache write/update
// It's used to track task-level ongoing costs, not immediate costs
let cacheWrite = false
if (isCacheAvailable) {
// Check if we already have a cache for this task
const existingCacheName = this.taskCacheNames.get(taskId)
const cacheEntry = existingCacheName ? this.contentCaches.get<{ key: string; count: number }>(taskId) : undefined
if (cacheEntry) {
// Use existing cache
uncachedContent = contents.slice(cacheEntry.count, contents.length)
cachedContent = cacheEntry.key
console.log(
`[GeminiHandler] using existing cache for task ${taskId}: ${cacheEntry.count} cached messages (${cacheEntry.key}) and ${uncachedContent.length} uncached messages`,
)
}
// Create or update cache only if there's new content to add
const shouldUpdateCache = !existingCacheName || (cacheEntry && uncachedContent && uncachedContent.length > 0)
if (shouldUpdateCache) {
// If we should update the cache, then there will be a cache write
cacheWrite = true
}
}
const isCacheUsed = !!cachedContent
// Configure thinking budget if supported
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
const maxBudget = info.thinkingConfig?.maxBudget ?? 0
@@ -85,7 +153,10 @@ export class GeminiHandler implements ApiHandler {
const requestConfig: GenerateContentConfig = {
// Add base URL if configured
httpOptions: this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined,
...{ systemInstruction: systemPrompt },
// Only include systemInstruction if NOT using the cache
...(isCacheUsed ? {} : { systemInstruction: systemPrompt }),
// Set temperature (default to 0)
temperature: 0,
}
@@ -100,12 +171,19 @@ export class GeminiHandler implements ApiHandler {
// Generate content using the configured parameters
const result = await this.client.models.generateContentStream({
model,
contents: contents,
contents: uncachedContent ?? contents,
config: {
...requestConfig,
...(isCacheUsed ? { cachedContent } : {}),
},
})
// Update the cache after the LLM request is already sent to avoid blocking
// We only update the cache if we have a taskId and the cache write flag is set
// This is a non-blocking operation and will not affect the response time
if (cacheWrite && taskId) {
this.updateCacheContent(taskId, model, contents, systemPrompt)
}
// Track usage metadata
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
@@ -129,7 +207,7 @@ export class GeminiHandler implements ApiHandler {
const outputTokens = lastUsageMetadata.candidatesTokenCount ?? 0
const cacheReadTokens = lastUsageMetadata.cachedContentTokenCount
// Calculate immediate costs
// Calculate immediate costs only (excluding cache write/storage costs)
const totalCost = this.calculateCost({
info,
inputTokens,
@@ -137,17 +215,214 @@ export class GeminiHandler implements ApiHandler {
cacheReadTokens,
})
// Store the token count for task-level ongoing cost tracking
// This is not included in the immediate costs returned to the user
const cacheWriteTokens = cacheWrite ? inputTokens : undefined
// If this is a cache write operation, update the task's ongoing costs
if (cacheWrite && this.options.taskId && inputTokens > 0) {
// Log the ongoing costs for debugging
const ongoingCosts = this.getTaskOngoingCosts(this.options.taskId)
console.log(
`[GeminiHandler] Task ${this.options.taskId} ongoing costs: $${ongoingCosts?.toFixed(6) ?? "unknown"}`,
)
}
yield {
type: "usage",
inputTokens,
outputTokens,
cacheReadTokens,
cacheWriteTokens: 0,
cacheWriteTokens,
totalCost,
}
}
}
/**
* Lists all caches for the current API key.
*
* According to the Gemini API documentation, you can retrieve metadata for all uploaded caches
* using the caches.list() method. This is useful for monitoring cache usage and cleanup.
*
* @param pageSize Optional number of caches to return per page (default: 10)
* @returns A promise that resolves to an array of cache metadata objects
*/
public async listCaches(pageSize: number = 10): Promise<any[]> {
try {
const caches: any[] = []
const pager = await this.client.caches.list({ config: { pageSize } })
let page = pager.page
while (true) {
for (const cache of page) {
caches.push(cache)
}
if (!pager.hasNextPage()) {
break
}
page = await pager.nextPage()
}
return caches
} catch (error) {
console.error(`[GeminiHandler] Failed to list caches:`, error)
return []
}
}
/**
* Updates the content of a cache for a specific task.
*
* Since the Gemini API doesn't support incremental updates to cache content,
* this method:
* 1. Creates a new cache with the full content (old + new)
* 2. Deletes the old cache if it exists
* 3. Updates our local tracking to point to the new cache
*
* @param taskId The ID of the task whose cache should be updated
* @param model The model to use for the cache
* @param contents The full content to cache (including both old and new messages)
* @param systemInstruction The system instruction to include in the cache
*/
private async updateCacheContent(
taskId: string,
model: string,
contents: Content[],
systemInstruction: string,
): Promise<void> {
if (this.isCacheBusy) {
console.log(`[GeminiHandler] Cache is busy, skipping update for task ${taskId}`)
return
}
this.isCacheBusy = true
const timestamp = Date.now()
const existingCacheName = this.taskCacheNames.get(taskId)
try {
// 1. Create a new cache with the full content
const result = await this.client.caches.create({
model,
config: {
contents,
systemInstruction,
ttl: `${DEFAULT_CACHE_TTL_SECONDS}s`,
httpOptions: { timeout: 120_000 },
},
})
const { name, usageMetadata } = result
if (name) {
// 2. Delete the old cache if it exists (non-blocking)
// We don't await this operation to avoid blocking the main flow if deletion fails
if (existingCacheName) {
// Schedule cache deletion in the background
setTimeout(() => {
this.client.caches
.delete({ name: existingCacheName })
.then(() => {
console.log(`[GeminiHandler] Deleted old cache ${existingCacheName} for task ${taskId}`)
})
.catch((error) => {
console.error(`[GeminiHandler] Failed to delete old cache ${existingCacheName}:`, error)
console.log(`[GeminiHandler] Continuing without deleting old cache. It will expire after TTL.`)
})
}, 1000)
}
// 3. Update our local tracking
this.contentCaches.set<{ key: string; count: number }>(taskId, {
key: name,
count: contents.length,
})
this.taskCacheNames.set(taskId, name)
// Track total tokens in cache for ongoing cost calculation
const totalTokens = usageMetadata?.totalTokenCount ?? 0
this.taskCacheTokens.set(taskId, totalTokens)
const operation = existingCacheName ? "Updated" : "Created new"
console.log(
`[GeminiHandler] ${operation} cache for task ${taskId}: ${contents.length} messages (${totalTokens} tokens) in ${Date.now() - timestamp}ms`,
)
return // Indicate that a cache write occurred
}
return
} catch (error) {
console.error(`[GeminiHandler] Failed to update cache for task ${taskId}:`, error)
return
} finally {
this.isCacheBusy = false
}
}
/**
* Updates the TTL of an existing cache.
*
* According to the Gemini API documentation, you can update the TTL of a cache
* using the caches.update() method. This is useful for extending the lifetime
* of a cache that's still being used.
*
* @param taskId The ID of the task whose cache TTL should be updated
* @param ttlSeconds The new TTL in seconds
* @returns A promise that resolves to the updated cache, or undefined if the update fails
*/
public async updateCacheTTL(taskId: string, ttlSeconds: number = DEFAULT_CACHE_TTL_SECONDS): Promise<any> {
const cacheName = this.taskCacheNames.get(taskId)
if (!cacheName) {
console.warn(`[GeminiHandler] No cache found for task ${taskId}, cannot update TTL`)
return
}
try {
const updatedCache = await this.client.caches.update({
name: cacheName,
config: { ttl: `${ttlSeconds}s` },
})
console.log(`[GeminiHandler] Updated TTL for cache ${cacheName} to ${ttlSeconds}s`)
return updatedCache
} catch (error) {
console.error(`[GeminiHandler] Failed to update TTL for cache ${cacheName}:`, error)
}
}
/**
* Calculate the ongoing costs for a task based on cache storage.
*
* This method calculates the cost of holding tokens in cache for the TTL period.
* These costs are separate from the immediate costs of API calls and should be
* tracked at the task level rather than the message level.
*
* TODO: Surface these ongoing costs to the user in the UI, possibly in:
* - The task header/summary
* - A dedicated "costs" panel or tooltip
* - As part of the total cost calculation for the task
*
* @param taskId The ID of the task to calculate ongoing costs for
* @returns The ongoing cost in dollars, or undefined if no cache exists for the task
*/
public getTaskOngoingCosts(taskId: string): number | undefined {
const tokens = this.taskCacheTokens.get(taskId)
if (!tokens) {
return undefined
}
const { info } = this.getModel()
if (!info.cacheWritesPrice) {
return undefined
}
// Calculate the cost of holding tokens in cache for the TTL period
// (tokens / 1M) * (price per 1M tokens) * (cache TTL in hours)
return info.cacheWritesPrice * (tokens / 1_000_000) * (DEFAULT_CACHE_TTL_SECONDS / 3600)
}
/**
* Calculate the immediate dollar cost of the API call based on token usage and model pricing.
*
@@ -155,18 +430,21 @@ export class GeminiHandler implements ApiHandler {
* - Input token costs (for uncached tokens)
* - Output token costs
* - Cache read costs
* - Gemini implicit caching has no write costs
*
* It does NOT include ongoing costs like cache storage, which are tracked separately
* at the task level through getTaskOngoingCosts().
*/
public calculateCost({
info,
inputTokens,
outputTokens,
cacheWriteTokens = 0,
cacheReadTokens = 0,
}: {
info: ModelInfo
inputTokens: number
outputTokens: number
cacheWriteTokens?: number
cacheReadTokens?: number
}) {
// Exit early if any required pricing information is missing
@@ -176,7 +454,9 @@ export class GeminiHandler implements ApiHandler {
let inputPrice = info.inputPrice
let outputPrice = info.outputPrice
let cacheWritesPrice = info.cacheWritesPrice ?? 0
// Right now, we only show the immediate costs of caching and not the ongoing costs of storing the cache
cacheWritesPrice = 0
let cacheReadsPrice = info.cacheReadsPrice ?? 0
// If there's tiered pricing then adjust prices based on the input tokens used
@@ -185,6 +465,7 @@ export class GeminiHandler implements ApiHandler {
if (tier) {
inputPrice = tier.inputPrice ?? inputPrice
outputPrice = tier.outputPrice ?? outputPrice
cacheWritesPrice = tier.cacheWritesPrice ?? cacheWritesPrice
cacheReadsPrice = tier.cacheReadsPrice ?? cacheReadsPrice
}
}
@@ -221,6 +502,27 @@ export class GeminiHandler implements ApiHandler {
return totalCost
}
/**
* Calculate the total length of all messages for cache eligibility check
*/
private getMessagesLength(contents: Content[]): number {
return contents.reduce((total, content) => {
if (!content.parts) {
return total
}
return (
total +
content.parts.reduce((partTotal, part) => {
if (typeof part.text === "string") {
return partTotal + part.text.length
}
return partTotal
}, 0)
)
}, 0)
}
/**
* Get the model ID and info for the current configuration
*/
+2 -2
View File
@@ -65,7 +65,7 @@ export class LiteLlmHandler implements ApiHandler {
const reasoningOn = budgetTokens !== 0 ? true : false
const thinkingConfig = reasoningOn ? { type: "enabled", budget_tokens: budgetTokens } : undefined
let temperature: number | undefined = this.options.liteLlmModelInfo?.temperature ?? 0
let temperature: number | undefined = 0
if (isOminiModel && reasoningOn) {
temperature = undefined // Thinking mode doesn't support temperature
@@ -169,7 +169,7 @@ export class LiteLlmHandler implements ApiHandler {
getModel() {
return {
id: this.options.liteLlmModelId || liteLlmDefaultModelId,
info: this.options.liteLlmModelInfo || liteLlmModelInfoSaneDefaults,
info: liteLlmModelInfoSaneDefaults,
}
}
}
+1 -2
View File
@@ -18,8 +18,7 @@ export class OpenAiHandler implements ApiHandler {
// Use azureApiVersion to determine if this is an Azure endpoint, since the URL may not always contain 'azure.com'
if (
this.options.azureApiVersion ||
((this.options.openAiBaseUrl?.toLowerCase().includes("azure.com") ||
this.options.openAiBaseUrl?.toLowerCase().includes("azure.us")) &&
(this.options.openAiBaseUrl?.toLowerCase().includes("azure.com") &&
!this.options.openAiModelId?.toLowerCase().includes("deepseek"))
) {
this.client = new AzureOpenAI({
+2 -1
View File
@@ -105,8 +105,9 @@ export class OpenRouterHandler implements ApiHandler {
// console.log("OpenRouter generation details:", generation)
return {
type: "usage",
// at this time there's no support for gatting cached_tokens from generation endpoint
cacheWriteTokens: 0,
cacheReadTokens: generation?.native_tokens_cached || 0,
cacheReadTokens: 0,
// openrouter generation endpoint fails often
inputTokens: generation?.native_tokens_prompt || 0,
outputTokens: generation?.native_tokens_completion || 0,
+47 -1
View File
@@ -21,7 +21,7 @@ export async function createOpenRouterStream(
// prompt caching: https://openrouter.ai/docs/prompt-caching
// this was initially specifically for claude models (some models may 'support prompt caching' automatically without this)
// handles direct model.id match logic
// includes custom support for gemini which does not have iterative caching
switch (model.id) {
case "anthropic/claude-3.7-sonnet":
case "anthropic/claude-3.7-sonnet:beta":
@@ -71,6 +71,52 @@ export async function createOpenRouterStream(
}
})
break
case "google/gemini-2.5-pro-preview-03-25":
case "google/gemini-2.0-flash-001":
case "google/gemini-flash-1.5":
case "google/gemini-pro-1.5":
// gemini only uses the last breakpoint for caching, so the others will be ignored
openAiMessages[0] = {
role: "system",
content: [
{
type: "text",
text: systemPrompt,
// @ts-ignore-next-line
cache_control: { type: "ephemeral" },
},
],
}
const GEMINI_CACHE_USER_MESSAGE_INTERVAL = 4 // add new breakpoint every 4 turns
const userMessages = openAiMessages.filter((msg) => msg.role === "user")
const userMessageCount = userMessages.length
const targetUserMessageNumber =
Math.floor(userMessageCount / GEMINI_CACHE_USER_MESSAGE_INTERVAL) * GEMINI_CACHE_USER_MESSAGE_INTERVAL
if (targetUserMessageNumber > 0) {
// otherwise dont need to add a breakpoint
const msg = userMessages[targetUserMessageNumber - 1]
if (msg) {
if (typeof msg.content === "string") {
msg.content = [{ type: "text", text: msg.content }]
}
if (Array.isArray(msg.content)) {
// NOTE: this is fine since env details will always be added at the end. but if it weren't there, and the user added a image_url type message, it would pop a text part before it and then move it after to the end.
let lastTextPart = msg.content.filter((part) => part.type === "text").pop()
if (!lastTextPart) {
lastTextPart = { type: "text", text: "..." }
msg.content.push(lastTextPart)
}
// @ts-ignore-next-line
lastTextPart["cache_control"] = { type: "ephemeral" }
}
}
}
break
default:
break
}
+1 -7
View File
@@ -1,6 +1,6 @@
export type AssistantMessageContent = TextContent | ToolUse
export { parseAssistantMessageV1, parseAssistantMessageV2 } from "./parse-assistant-message"
export { parseAssistantMessage } from "./parse-assistant-message"
export interface TextContent {
type: "text"
@@ -25,7 +25,6 @@ export const toolUseNames = [
"attempt_completion",
"new_task",
"condense",
"report_bug",
"new_rule",
] as const
@@ -54,11 +53,6 @@ export const toolParamNames = [
"response",
"result",
"context",
"title",
"what_happened",
"steps_to_reproduce",
"api_request_output",
"additional_context",
] as const
export type ToolParamName = (typeof toolParamNames)[number]
@@ -1,24 +1,6 @@
import { AssistantMessageContent, TextContent, ToolUse, ToolParamName, toolParamNames, toolUseNames, ToolUseName } from "." // Assuming types are defined in index.ts or a similar file
import { AssistantMessageContent, TextContent, ToolUse, ToolParamName, toolParamNames, toolUseNames, ToolUseName } from "."
/**
* @description **Version 1**
* Parses an assistant message string potentially containing mixed text and tool usage blocks
* marked with XML-like tags into an array of structured content objects.
*
* This version iterates through the message character by character, building an accumulator string.
* It maintains state to track whether it's currently parsing text, a tool use block, or a specific tool parameter.
* It detects the start and end of tool uses and parameters by checking if the accumulator ends with
* the corresponding opening or closing tags.
* Special handling is included for `write_to_file` and `new_rule` tool uses to correctly parse
* the `content` parameter, which might contain the closing tag itself, by looking for the *last*
* occurrence of the closing tag.
* If the input string ends mid-tag or mid-content, the last block (text or tool use) is marked as partial.
*
* @param assistantMessage The raw string output from the assistant.
* @returns An array of `AssistantMessageContent` objects, which can be `TextContent` or `ToolUse`.
* Blocks that were not fully closed by the end of the input string will have their `partial` flag set to `true`.
*/
export function parseAssistantMessageV1(assistantMessage: string): AssistantMessageContent[] {
export function parseAssistantMessage(assistantMessage: string) {
const contentBlocks: AssistantMessageContent[] = []
let currentTextContent: TextContent | undefined = undefined
let currentTextContentStartIndex = 0
@@ -32,56 +14,46 @@ export function parseAssistantMessageV1(assistantMessage: string): AssistantMess
const char = assistantMessage[i]
accumulator += char
// --- State: Parsing a Tool Parameter ---
// there should not be a param without a tool use
if (currentToolUse && currentParamName) {
const currentParamValue = accumulator.slice(currentParamValueStartIndex)
const paramClosingTag = `</${currentParamName}>`
if (currentParamValue.endsWith(paramClosingTag)) {
// End of param value found
// end of param value
currentToolUse.params[currentParamName] = currentParamValue.slice(0, -paramClosingTag.length).trim()
currentParamName = undefined // Go back to parsing tool content or looking for next param
continue // Move to next character
currentParamName = undefined
continue
} else {
// Partial param value is accumulating
continue // Move to next character
// partial param value is accumulating
continue
}
}
// --- State: Parsing a Tool Use (but not a specific parameter) ---
// no currentParamName
if (currentToolUse) {
const currentToolValue = accumulator.slice(currentToolUseStartIndex)
const toolUseClosingTag = `</${currentToolUse.name}>`
if (currentToolValue.endsWith(toolUseClosingTag)) {
// End of a tool use found
// end of a tool use
currentToolUse.partial = false
contentBlocks.push(currentToolUse)
currentToolUse = undefined // Go back to parsing text or looking for next tool
// Reset text start index in case text follows immediately
currentTextContentStartIndex = i + 1
continue // Move to next character
currentToolUse = undefined
continue
} else {
// Check if starting a new parameter within the current tool use
const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`)
let foundParamStart = false
for (const paramOpeningTag of possibleParamOpeningTags) {
if (accumulator.endsWith(paramOpeningTag)) {
// Start of a new parameter found
// start of a new parameter
currentParamName = paramOpeningTag.slice(1, -1) as ToolParamName
currentParamValueStartIndex = accumulator.length
foundParamStart = true
break
}
}
if (foundParamStart) {
continue // Move to next character
}
// Special case for write_to_file/new_rule content param allowing nested tags
// Check if a </content> tag appears, potentially indicating the end of the content param
// even if the main tool closing tag hasn't been seen yet.
// there's no current param, and not starting a new param
// special case for write_to_file where file contents could contain the closing tag, in which case the param would have closed and we end up with the rest of the file contents here. To work around this, we get the string between the starting content tag and the LAST content tag.
const contentParamName: ToolParamName = "content"
if (
(currentToolUse.name === "write_to_file" || currentToolUse.name === "new_rule") &&
@@ -91,385 +63,73 @@ export function parseAssistantMessageV1(assistantMessage: string): AssistantMess
const contentStartTag = `<${contentParamName}>`
const contentEndTag = `</${contentParamName}>`
const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length
// Use lastIndexOf to handle cases where </content> might appear within the content itself
const contentEndIndex = toolContent.lastIndexOf(contentEndTag)
// Ensure we found valid start/end tags and end is after start
if (
contentStartIndex !== -1 &&
contentEndIndex !== -1 &&
contentEndIndex > contentStartIndex - contentStartTag.length // Ensure end tag is after start tag begins
) {
// Check if this content param was already being parsed. If so, update it.
// If not, and we just found the closing tag, assign it.
// This handles cases where the </content> detection might fire before
// the <content> tag detection logic, or if the content is very short.
if (currentParamName === contentParamName) {
// Already parsing content, now we found the end tag
currentToolUse.params[contentParamName] = toolContent.slice(contentStartIndex, contentEndIndex).trim()
currentParamName = undefined // Finished with this param
} else if (currentParamName === undefined) {
// Not parsing a param, but found </content>. Assume it closes the content block.
currentToolUse.params[contentParamName] = toolContent.slice(contentStartIndex, contentEndIndex).trim()
// We stay in the "parsing tool use" state, looking for more params or the tool end tag.
}
if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) {
currentToolUse.params[contentParamName] = toolContent.slice(contentStartIndex, contentEndIndex).trim()
}
}
// If none of the above, partial tool value is accumulating
continue // Move to next character
// partial tool value is accumulating
continue
}
}
// --- State: Parsing Text (or looking for start of a tool use) ---
// no currentToolUse
let didStartToolUse = false
const possibleToolUseOpeningTags = toolUseNames.map((name) => `<${name}>`)
for (const toolUseOpeningTag of possibleToolUseOpeningTags) {
if (accumulator.endsWith(toolUseOpeningTag)) {
// Start of a new tool use found
const toolName = toolUseOpeningTag.slice(1, -1) as ToolUseName
// start of a new tool use
currentToolUse = {
type: "tool_use",
name: toolName,
name: toolUseOpeningTag.slice(1, -1) as ToolUseName,
params: {},
partial: true,
}
currentToolUseStartIndex = accumulator.length
// This also indicates the end of the current text content block (if any)
// this also indicates the end of the current text content
if (currentTextContent) {
currentTextContent.partial = false
// Extract text content, removing the part that formed the tool opening tag
const textEndIndex = accumulator.length - toolUseOpeningTag.length
currentTextContent.content = accumulator.slice(currentTextContentStartIndex, textEndIndex).trim()
// Only add if there's actual content
if (currentTextContent.content.length > 0) {
contentBlocks.push(currentTextContent)
}
// remove the partially accumulated tool use tag from the end of text (<tool)
currentTextContent.content = currentTextContent.content
.slice(0, -toolUseOpeningTag.slice(0, -1).length)
.trim()
contentBlocks.push(currentTextContent)
currentTextContent = undefined
} else {
// Check if there was text before this tool use started
const textEndIndex = accumulator.length - toolUseOpeningTag.length
const potentialText = accumulator.slice(currentTextContentStartIndex, textEndIndex).trim()
if (potentialText.length > 0) {
contentBlocks.push({
type: "text",
content: potentialText,
partial: false, // Ended because tool use started
})
}
}
didStartToolUse = true
break // Found tool start, stop checking for others
break
}
}
if (!didStartToolUse) {
// No tool use started, so it must be text content accumulating
// (or continuing after a closed tool use)
// no tool use, so it must be text either at the beginning or between tools
if (currentTextContent === undefined) {
// Start of a new text block
currentTextContentStartIndex = i - (accumulator.length - currentTextContentStartIndex - 1) // Adjust start index based on how much we've accumulated since the last block ended or the beginning
// If accumulator starts from 0, start index is i
if (contentBlocks.length === 0 && currentToolUse === undefined) {
currentTextContentStartIndex = accumulator.length - 1 // i
} else {
// Re-calculate based on the actual start of the current text segment
// Find the end of the last block
let lastBlockEndIndex = 0
if (contentBlocks.length > 0) {
const lastBlock = contentBlocks[contentBlocks.length - 1]
// Approximation: find where the accumulator matches the end of the message string representation of the last block. This is complex.
// Simpler: Assume text starts right after the last block ended implicitly at index i.
lastBlockEndIndex = i // Where the loop *was* when the last block finished processing
// Need a more robust way to track the end index of the *raw string* corresponding to the last block.
// Let's stick to the accumulator slice approach for simplicity in this version.
// The start index should be where the current *unmatched* text began.
let lastProcessedIndex = -1
if (contentBlocks.length > 0) {
// This requires knowing the raw string length of the previous block, which V1 doesn't explicitly track easily.
// We'll approximate based on the current accumulator and start index logic.
// The issue arises if a tool tag was just closed. accumulator contains everything up to i.
// lastBlockEndIndex should point to the character *after* the closing tag of the last block.
}
// Reset start index to the beginning of the *current* potential text block
currentTextContentStartIndex = accumulator.length - 1 // Start accumulating from the current character `i`
}
// If we just closed a tool, text starts *after* its closing tag
// The logic needs refinement here for accurate start index after a tool closure.
// Let's assume for now the start index logic inside the loop handles it via slicing.
}
currentTextContent = {
type: "text",
content: "", // Content will be filled by slicing accumulator
partial: true,
}
currentTextContentStartIndex = i
}
currentTextContent = {
type: "text",
content: accumulator.slice(currentTextContentStartIndex).trim(),
partial: true,
}
// Update text content based on the accumulator from its start index
currentTextContent.content = accumulator.slice(currentTextContentStartIndex).trimStart() // Trim start to avoid leading space if text follows tool
}
} // End of loop
}
// --- Finalization after loop ---
// If a tool use was open at the end
if (currentToolUse) {
// If a parameter was open within that tool use
// stream did not complete tool call, add it as partial
if (currentParamName) {
// The remaining accumulator content belongs to this partial parameter
// tool call has a parameter that was not completed
currentToolUse.params[currentParamName] = accumulator.slice(currentParamValueStartIndex).trim()
}
// Add the potentially partial tool use block
contentBlocks.push(currentToolUse)
}
// If text content was being accumulated at the end
// Note: Only one of currentToolUse or currentTextContent can be defined here,
// as starting a tool use finalizes the preceding text block.
else if (currentTextContent) {
// Update content one last time
currentTextContent.content = accumulator.slice(currentTextContentStartIndex).trim()
// Add the potentially partial text block only if it contains content
if (currentTextContent.content.length > 0) {
contentBlocks.push(currentTextContent)
}
}
return contentBlocks
}
/**
* @description **Version 2**
* Parses an assistant message string potentially containing mixed text and tool usage blocks
* marked with XML-like tags into an array of structured content objects.
*
* This version aims for efficiency by avoiding the character-by-character accumulator of V1.
* It iterates through the string using an index `i`. At each position, it checks if the substring
* *ending* at `i` matches any known opening or closing tags for tools or parameters using `startsWith`
* with an offset.
* It uses pre-computed Maps (`toolUseOpenTags`, `toolParamOpenTags`) for quick tag lookups.
* State is managed using indices (`currentTextContentStart`, `currentToolUseStart`, `currentParamValueStart`)
* pointing to the start of the current block within the original `assistantMessage` string.
* Slicing is used to extract content only when a block (text, parameter, or tool use) is completed.
* Special handling for `write_to_file` and `new_rule` content parameters is included, using `indexOf`
* and `lastIndexOf` on the relevant slice to handle potentially nested closing tags.
* If the input string ends mid-block, the last open block is added and marked as partial.
*
* @param assistantMessage The raw string output from the assistant.
* @returns An array of `AssistantMessageContent` objects, which can be `TextContent` or `ToolUse`.
* Blocks that were not fully closed by the end of the input string will have their `partial` flag set to `true`.
*/
export function parseAssistantMessageV2(assistantMessage: string): AssistantMessageContent[] {
const contentBlocks: AssistantMessageContent[] = []
let currentTextContentStart = 0 // Index where the current text block started
let currentTextContent: TextContent | undefined = undefined
let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use
let currentToolUse: ToolUse | undefined = undefined
let currentParamValueStart = 0 // Index *after* the opening tag of the current param
let currentParamName: ToolParamName | undefined = undefined
// Precompute tags for faster lookups
const toolUseOpenTags = new Map<string, ToolUseName>()
const toolParamOpenTags = new Map<string, ToolParamName>()
for (const name of toolUseNames) {
toolUseOpenTags.set(`<${name}>`, name)
}
for (const name of toolParamNames) {
toolParamOpenTags.set(`<${name}>`, name)
}
const len = assistantMessage.length
for (let i = 0; i < len; i++) {
const currentCharIndex = i
// --- State: Parsing a Tool Parameter ---
if (currentToolUse && currentParamName) {
const closeTag = `</${currentParamName}>`
// Check if the string *ending* at index `i` matches the closing tag
if (
currentCharIndex >= closeTag.length - 1 &&
assistantMessage.startsWith(
closeTag,
currentCharIndex - closeTag.length + 1, // Start checking from potential start of tag
)
) {
// Found the closing tag for the parameter
const value = assistantMessage
.slice(
currentParamValueStart, // Start after the opening tag
currentCharIndex - closeTag.length + 1, // End before the closing tag
)
.trim()
currentToolUse.params[currentParamName] = value
currentParamName = undefined // Go back to parsing tool content
// We don't continue loop here, need to check for tool close or other params at index i
} else {
continue // Still inside param value, move to next char
}
}
// --- State: Parsing a Tool Use (but not a specific parameter) ---
if (currentToolUse && !currentParamName) {
// Ensure we are not inside a parameter already
// Check if starting a new parameter
let startedNewParam = false
for (const [tag, paramName] of toolParamOpenTags.entries()) {
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
currentParamName = paramName
currentParamValueStart = currentCharIndex + 1 // Value starts after the tag
startedNewParam = true
break
}
}
if (startedNewParam) {
continue // Handled start of param, move to next char
}
// Check if closing the current tool use
const toolCloseTag = `</${currentToolUse.name}>`
if (
currentCharIndex >= toolCloseTag.length - 1 &&
assistantMessage.startsWith(toolCloseTag, currentCharIndex - toolCloseTag.length + 1)
) {
// End of the tool use found
// Special handling for content params *before* finalizing the tool
const toolContentSlice = assistantMessage.slice(
currentToolUseStart, // From after the tool opening tag
currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag
)
// Check if content parameter needs special handling (write_to_file/new_rule)
// This check is important if the closing </content> tag was missed by the parameter parsing logic
// (e.g., if content is empty or parsing logic prioritizes tool close)
const contentParamName: ToolParamName = "content"
if (
(currentToolUse.name === "write_to_file" || currentToolUse.name === "new_rule") &&
!(contentParamName in currentToolUse.params) && // Only if not already parsed
toolContentSlice.includes(`<${contentParamName}>`) // Check if tag exists
) {
const contentStartTag = `<${contentParamName}>`
const contentEndTag = `</${contentParamName}>`
const contentStart = toolContentSlice.indexOf(contentStartTag)
// Use lastIndexOf for robustness against nested tags
const contentEnd = toolContentSlice.lastIndexOf(contentEndTag)
if (contentStart !== -1 && contentEnd !== -1 && contentEnd > contentStart) {
const contentValue = toolContentSlice.slice(contentStart + contentStartTag.length, contentEnd).trim()
currentToolUse.params[contentParamName] = contentValue
}
}
currentToolUse.partial = false // Mark as complete
contentBlocks.push(currentToolUse)
currentToolUse = undefined // Reset state
currentTextContentStart = currentCharIndex + 1 // Potential text starts after this tag
continue // Move to next char
}
// If not starting a param and not closing the tool, continue accumulating tool content implicitly
continue
}
// --- State: Parsing Text / Looking for Tool Start ---
if (!currentToolUse) {
// Check if starting a new tool use
let startedNewTool = false
for (const [tag, toolName] of toolUseOpenTags.entries()) {
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
// End current text block if one was active
if (currentTextContent) {
currentTextContent.content = assistantMessage
.slice(
currentTextContentStart, // From where text started
currentCharIndex - tag.length + 1, // To before the tool tag starts
)
.trim()
currentTextContent.partial = false // Ended because tool started
if (currentTextContent.content.length > 0) {
contentBlocks.push(currentTextContent)
}
currentTextContent = undefined
} else {
// Check for any text between the last block and this tag
const potentialText = assistantMessage
.slice(
currentTextContentStart, // From where text *might* have started
currentCharIndex - tag.length + 1, // To before the tool tag starts
)
.trim()
if (potentialText.length > 0) {
contentBlocks.push({
type: "text",
content: potentialText,
partial: false,
})
}
}
// Start the new tool use
currentToolUse = {
type: "tool_use",
name: toolName,
params: {},
partial: true, // Assume partial until closing tag is found
}
currentToolUseStart = currentCharIndex + 1 // Tool content starts after the opening tag
startedNewTool = true
break
}
}
if (startedNewTool) {
continue // Handled start of tool, move to next char
}
// If not starting a tool, it must be text content
if (!currentTextContent) {
// Start a new text block if we aren't already in one
currentTextContentStart = currentCharIndex // Text starts at the current character
// Check if the current char is the start of potential text *immediately* after a tag
// This needs the previous state - simpler to let slicing handle it later.
// Resetting start index accurately is key.
// It should be the index *after* the last processed tag.
// The logic managing currentTextContentStart after closing tags handles this.
currentTextContent = {
type: "text",
content: "", // Will be determined by slicing at the end or when a tool starts
partial: true,
}
}
// Continue accumulating text implicitly; content is extracted later.
}
} // End of loop
// --- Finalization after loop ---
// Finalize any open parameter within an open tool use
if (currentToolUse && currentParamName) {
currentToolUse.params[currentParamName] = assistantMessage
.slice(currentParamValueStart) // From param start to end of string
.trim()
// Tool use remains partial
}
// Finalize any open tool use (which might contain the finalized partial param)
if (currentToolUse) {
// Tool use is partial because the loop finished before its closing tag
contentBlocks.push(currentToolUse)
}
// Finalize any trailing text content
// Only possible if a tool use wasn't open at the very end
else if (currentTextContent) {
currentTextContent.content = assistantMessage
.slice(currentTextContentStart) // From text start to end of string
.trim()
// Text is partial because the loop finished
if (currentTextContent.content.length > 0) {
contentBlocks.push(currentTextContent)
}
// Note: it doesn't matter if check for currentToolUse or currentTextContent, only one of them will be defined since only one can be partial at a time
if (currentTextContent) {
// stream did not complete text content, add it as partial
contentBlocks.push(currentTextContent)
}
return contentBlocks
@@ -2,7 +2,6 @@ import * as vscode from "vscode"
import crypto from "crypto"
import { Controller } from "../index"
import { storeSecret } from "../../storage/state"
import { EmptyRequest, String } from "../../../shared/proto/common"
/**
* Handles the user clicking the login link in the UI.
@@ -12,7 +11,7 @@ import { EmptyRequest, String } from "../../../shared/proto/common"
* @param controller The controller instance.
* @returns The login URL as a string.
*/
export async function accountLoginClicked(controller: Controller, unused: EmptyRequest): Promise<String> {
export async function accountLoginClicked(controller: Controller): Promise<String> {
// Generate nonce for state validation
const nonce = crypto.randomBytes(32).toString("hex")
await storeSecret(controller.context, "authNonce", nonce)
@@ -26,8 +25,6 @@ export async function accountLoginClicked(controller: Controller, unused: EmptyR
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 {
value: authUrl.toString(),
}
vscode.env.openExternal(authUrl)
return authUrl.toString()
}
+3 -11
View File
@@ -1,22 +1,14 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../grpc-service"
import { StreamingResponseHandler } from "../grpc-handler"
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
// Create account service registry
const accountService = createServiceRegistry("account")
// Export the method handler types and registration function
// Export the method handler type and registration function
export type AccountMethodHandler = ServiceMethodHandler
export type AccountStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = accountService.registerMethod
// Export the request handlers
// Export the request handler
export const handleAccountServiceRequest = accountService.handleRequest
export const handleAccountServiceStreamingRequest = accountService.handleStreamingRequest
export const isStreamingMethod = accountService.isStreamingMethod
// Register all account methods
registerAllMethods()
+3 -10
View File
@@ -1,22 +1,15 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../grpc-service"
import { StreamingResponseHandler } from "../grpc-handler"
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
// Create browser service registry
const browserService = createServiceRegistry("browser")
// Export the method handler types and registration function
// Export the method handler type and registration function
export type BrowserMethodHandler = ServiceMethodHandler
export type BrowserStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = browserService.registerMethod
// Export the request handlers
// Export the request handler
export const handleBrowserServiceRequest = browserService.handleRequest
export const handleBrowserServiceStreamingRequest = browserService.handleStreamingRequest
export const isStreamingMethod = browserService.isStreamingMethod
// Register all browser methods
registerAllMethods()
@@ -1,8 +1,8 @@
import { UpdateBrowserSettingsRequest } from "../../../shared/proto/browser"
import { Boolean } from "../../../shared/proto/common"
import { Controller } from "../index"
import { updateGlobalState, getGlobalState } from "../../storage/state"
import { BrowserSettings as SharedBrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../../shared/BrowserSettings"
import { updateGlobalState } from "../../storage/state"
import { BrowserSettings as SharedBrowserSettings } from "../../../shared/BrowserSettings"
/**
* Update browser settings
@@ -12,39 +12,23 @@ import { BrowserSettings as SharedBrowserSettings, DEFAULT_BROWSER_SETTINGS } fr
*/
export async function updateBrowserSettings(controller: Controller, request: UpdateBrowserSettingsRequest): Promise<Boolean> {
try {
// Get current browser settings to preserve fields not in the request
const currentSettings = (await getGlobalState(controller.context, "browserSettings")) as SharedBrowserSettings | undefined
const mergedWithDefaults = { ...DEFAULT_BROWSER_SETTINGS, ...currentSettings }
// Convert from protobuf format to shared format, merging with existing settings
const newBrowserSettings: SharedBrowserSettings = {
...mergedWithDefaults, // Start with existing settings (and defaults)
// Convert from protobuf format to shared format
const browserSettings: SharedBrowserSettings = {
viewport: {
// Apply updates from request
width: request.viewport?.width || mergedWithDefaults.viewport.width,
height: request.viewport?.height || mergedWithDefaults.viewport.height,
width: request.viewport?.width || 900,
height: request.viewport?.height || 600,
},
// Explicitly handle optional boolean and string fields from the request
remoteBrowserEnabled:
request.remoteBrowserEnabled === undefined
? mergedWithDefaults.remoteBrowserEnabled
: request.remoteBrowserEnabled,
remoteBrowserHost:
request.remoteBrowserHost === undefined ? mergedWithDefaults.remoteBrowserHost : request.remoteBrowserHost,
chromeExecutablePath:
// If chromeExecutablePath is explicitly in the request (even as ""), use it.
// Otherwise, fall back to mergedWithDefaults.
"chromeExecutablePath" in request ? request.chromeExecutablePath : mergedWithDefaults.chromeExecutablePath,
disableToolUse: request.disableToolUse === undefined ? mergedWithDefaults.disableToolUse : request.disableToolUse,
remoteBrowserEnabled: request.remoteBrowserEnabled || false,
remoteBrowserHost: request.remoteBrowserHost || undefined,
}
// Update global state with new settings
await updateGlobalState(controller.context, "browserSettings", newBrowserSettings)
await updateGlobalState(controller.context, "browserSettings", browserSettings)
// Update task browser settings if task exists
if (controller.task) {
controller.task.browserSettings = newBrowserSettings
controller.task.browserSession.browserSettings = newBrowserSettings
controller.task.browserSettings = browserSettings
controller.task.browserSession.browserSettings = browserSettings
}
// Post updated state to webview
+3 -10
View File
@@ -1,22 +1,15 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../grpc-service"
import { StreamingResponseHandler } from "../grpc-handler"
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
// Create checkpoints service registry
const checkpointsService = createServiceRegistry("checkpoints")
// Export the method handler types and registration function
// Export the method handler type and registration function
export type CheckpointsMethodHandler = ServiceMethodHandler
export type CheckpointsStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = checkpointsService.registerMethod
// Export the request handlers
// Export the request handler
export const handleCheckpointsServiceRequest = checkpointsService.handleRequest
export const handleCheckpointsServiceStreamingRequest = checkpointsService.handleStreamingRequest
export const isStreamingMethod = checkpointsService.isStreamingMethod
// Register all checkpoints methods
registerAllMethods()
+3 -10
View File
@@ -1,22 +1,15 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../grpc-service"
import { StreamingResponseHandler } from "../grpc-handler"
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
// Create file service registry
const fileService = createServiceRegistry("file")
// Export the method handler types and registration function
// Export the method handler type and registration function
export type FileMethodHandler = ServiceMethodHandler
export type FileStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = fileService.registerMethod
// Export the request handlers
// Export the request handler
export const handleFileServiceRequest = fileService.handleRequest
export const handleFileServiceStreamingRequest = fileService.handleStreamingRequest
export const isStreamingMethod = fileService.isStreamingMethod
// Register all file methods
registerAllMethods()
-2
View File
@@ -9,7 +9,6 @@ import { getRelativePaths } from "./getRelativePaths"
import { openFile } from "./openFile"
import { openImage } from "./openImage"
import { searchCommits } from "./searchCommits"
import { searchFiles } from "./searchFiles"
// Register all file service methods
export function registerAllMethods(): void {
@@ -20,5 +19,4 @@ export function registerAllMethods(): void {
registerMethod("openFile", openFile)
registerMethod("openImage", openImage)
registerMethod("searchCommits", searchCommits)
registerMethod("searchFiles", searchFiles)
}
-55
View File
@@ -1,55 +0,0 @@
import { Controller } from ".."
import { FileSearchRequest, FileSearchResults } from "@shared/proto/file"
import { searchWorkspaceFiles } from "@services/search/file-search"
import { getWorkspacePath } from "@utils/path"
import { FileMethodHandler } from "./index"
import { convertSearchResultsToProtoFileInfos } from "@shared/proto-conversions/file/search-result-conversion"
/**
* Searches for files in the workspace with fuzzy matching
* @param controller The controller instance
* @param request The request containing search query and optionally a mentionsRequestId
* @returns Results containing matching files/folders
*/
export const searchFiles: FileMethodHandler = async (
controller: Controller,
request: FileSearchRequest,
): Promise<FileSearchResults> => {
const workspacePath = getWorkspacePath()
if (!workspacePath) {
// Handle case where workspace path is not available
console.error("Error in searchFiles: No workspace path available")
return FileSearchResults.create({
results: [],
mentionsRequestId: request.mentionsRequestId,
})
}
try {
// Call file search service with query from request
const searchResults = await searchWorkspaceFiles(
request.query || "",
workspacePath,
request.limit || 20, // Use default limit of 20 if not specified
)
// Convert search results to proto FileInfo objects using the conversion function
const protoResults = convertSearchResultsToProtoFileInfos(searchResults)
// Return successful results
return FileSearchResults.create({
results: protoResults,
mentionsRequestId: request.mentionsRequestId,
})
} catch (error) {
// Log the error but don't include it in the response, following the pattern in searchCommits
console.error("Error in searchFiles:", error instanceof Error ? error.message : String(error))
// Return empty results without error message
return FileSearchResults.create({
results: [],
mentionsRequestId: request.mentionsRequestId,
})
}
}
+48 -146
View File
@@ -1,11 +1,11 @@
import { Controller } from "./index"
import { serviceHandlers } from "./grpc-service-config"
import { GrpcRequestRegistry } from "./grpc-request-registry"
/**
* Type definition for a streaming response handler
*/
export type StreamingResponseHandler = (response: any, isLast?: boolean, sequenceNumber?: number) => Promise<void>
import { handleAccountServiceRequest } from "./account"
import { handleBrowserServiceRequest } from "./browser/index"
import { handleFileServiceRequest } from "./file"
import { handleTaskServiceRequest } from "./task"
import { handleCheckpointsServiceRequest } from "./checkpoints"
import { handleMcpServiceRequest } from "./mcp"
import { handleWebContentServiceRequest } from "./web-content"
/**
* Handles gRPC requests from the webview
@@ -19,37 +19,57 @@ export class GrpcHandler {
* @param method The method name
* @param message The request message
* @param requestId The request ID for response correlation
* @param isStreaming Whether this is a streaming request
* @returns The response message or error for unary requests, void for streaming requests
* @returns The response message or error
*/
async handleRequest(
service: string,
method: string,
message: any,
requestId: string,
isStreaming: boolean = false,
): Promise<{
message?: any
error?: string
request_id: string
} | void> {
}> {
try {
// If this is a streaming request, use the streaming handler
if (isStreaming) {
await this.handleStreamingRequest(service, method, message, requestId)
return
}
// Get the service handler from the config
const serviceConfig = serviceHandlers[service]
if (!serviceConfig) {
throw new Error(`Unknown service: ${service}`)
}
// Handle unary request
return {
message: await serviceConfig.requestHandler(this.controller, method, message),
request_id: requestId,
switch (service) {
case "cline.AccountService":
return {
message: await handleAccountServiceRequest(this.controller, method, message),
request_id: requestId,
}
case "cline.BrowserService":
return {
message: await handleBrowserServiceRequest(this.controller, method, message),
request_id: requestId,
}
case "cline.CheckpointsService":
return {
message: await handleCheckpointsServiceRequest(this.controller, method, message),
request_id: requestId,
}
case "cline.FileService":
return {
message: await handleFileServiceRequest(this.controller, method, message),
request_id: requestId,
}
case "cline.TaskService":
return {
message: await handleTaskServiceRequest(this.controller, method, message),
request_id: requestId,
}
case "cline.McpService":
return {
message: await handleMcpServiceRequest(this.controller, method, message),
request_id: requestId,
}
case "cline.WebContentService":
return {
message: await handleWebContentServiceRequest(this.controller, method, message),
request_id: requestId,
}
default:
throw new Error(`Unknown service: ${service}`)
}
} catch (error) {
return {
@@ -58,66 +78,8 @@ export class GrpcHandler {
}
}
}
/**
* Handle a streaming gRPC request
* @param service The service name
* @param method The method name
* @param message The request message
* @param requestId The request ID for response correlation
*/
private async handleStreamingRequest(service: string, method: string, message: any, requestId: string): Promise<void> {
// Create a response stream function
const responseStream: StreamingResponseHandler = async (
response: any,
isLast: boolean = false,
sequenceNumber?: number,
) => {
await this.controller.postMessageToWebview({
type: "grpc_response",
grpc_response: {
message: response,
request_id: requestId,
is_streaming: !isLast,
sequence_number: sequenceNumber,
},
})
}
try {
// Get the service handler from the config
const serviceConfig = serviceHandlers[service]
if (!serviceConfig) {
throw new Error(`Unknown service: ${service}`)
}
// Check if the service supports streaming
if (!serviceConfig.streamingHandler) {
throw new Error(`Service ${service} does not support streaming`)
}
// Handle streaming request and pass the requestId to all streaming handlers
await serviceConfig.streamingHandler(this.controller, method, message, responseStream, requestId)
// Don't send a final message here - the stream should stay open for future updates
// The stream will be closed when the client disconnects or when the service explicitly ends it
} catch (error) {
// Send error response
await this.controller.postMessageToWebview({
type: "grpc_response",
grpc_response: {
error: error instanceof Error ? error.message : String(error),
request_id: requestId,
is_streaming: false,
},
})
}
}
}
// Registry to track active gRPC requests and their cleanup functions
const requestRegistry = new GrpcRequestRegistry()
/**
* Handle a gRPC request from the webview
* @param controller The controller instance
@@ -130,35 +92,11 @@ export async function handleGrpcRequest(
method: string
message: any
request_id: string
is_streaming?: boolean
},
) {
try {
const grpcHandler = new GrpcHandler(controller)
// For streaming requests, handleRequest handles sending responses directly
if (request.is_streaming) {
try {
await grpcHandler.handleRequest(request.service, request.method, request.message, request.request_id, true)
} finally {
// Note: We don't automatically clean up here anymore
// The request will be cleaned up when it completes or is cancelled
}
return
}
// For unary requests, we get a response and send it back
const response = (await grpcHandler.handleRequest(
request.service,
request.method,
request.message,
request.request_id,
false,
)) as {
message?: any
error?: string
request_id: string
}
const response = await grpcHandler.handleRequest(request.service, request.method, request.message, request.request_id)
// Send the response back to the webview
await controller.postMessageToWebview({
@@ -176,39 +114,3 @@ export async function handleGrpcRequest(
})
}
}
/**
* Handle a gRPC request cancellation from the webview
* @param controller The controller instance
* @param request The cancellation request
*/
export async function handleGrpcRequestCancel(
controller: Controller,
request: {
request_id: string
},
) {
const cancelled = requestRegistry.cancelRequest(request.request_id)
if (cancelled) {
// Send a cancellation confirmation
await controller.postMessageToWebview({
type: "grpc_response",
grpc_response: {
message: { cancelled: true },
request_id: request.request_id,
is_streaming: false,
},
})
} else {
console.log(`[DEBUG] Request not found for cancellation: ${request.request_id}`)
}
}
/**
* Get the request registry instance
* This allows other parts of the code to access the registry
*/
export function getRequestRegistry(): GrpcRequestRegistry {
return requestRegistry
}
@@ -1,124 +0,0 @@
import { StreamingResponseHandler } from "./grpc-handler"
/**
* Information about a registered gRPC request
*/
export interface RequestInfo {
/**
* Function to clean up resources when the request is cancelled or completed
*/
cleanup: () => void
/**
* Optional metadata about the request
*/
metadata?: any
/**
* Timestamp when the request was registered
*/
timestamp: Date
/**
* The streaming response handler for this request
*/
responseStream?: StreamingResponseHandler
}
/**
* Registry for managing gRPC request lifecycles
* This class provides a centralized way to track active requests and their cleanup functions
*/
export class GrpcRequestRegistry {
/**
* Map of request IDs to request information
*/
private activeRequests = new Map<string, RequestInfo>()
/**
* Register a new request with its cleanup function
* @param requestId The unique ID of the request
* @param cleanup Function to clean up resources when the request is cancelled
* @param metadata Optional metadata about the request
* @param responseStream Optional streaming response handler
*/
public registerRequest(
requestId: string,
cleanup: () => void,
metadata?: any,
responseStream?: StreamingResponseHandler,
): void {
this.activeRequests.set(requestId, {
cleanup,
metadata,
timestamp: new Date(),
responseStream,
})
console.log(`[DEBUG] Registered request: ${requestId}`)
}
/**
* Cancel a request and clean up its resources
* @param requestId The ID of the request to cancel
* @returns True if the request was found and cancelled, false otherwise
*/
public cancelRequest(requestId: string): boolean {
const requestInfo = this.activeRequests.get(requestId)
if (requestInfo) {
try {
requestInfo.cleanup()
console.log(`[DEBUG] Cleaned up request: ${requestId}`)
} catch (error) {
console.error(`Error cleaning up request ${requestId}:`, error)
}
this.activeRequests.delete(requestId)
return true
}
return false
}
/**
* Get information about a request
* @param requestId The ID of the request
* @returns The request information, or undefined if not found
*/
public getRequestInfo(requestId: string): RequestInfo | undefined {
return this.activeRequests.get(requestId)
}
/**
* Check if a request exists in the registry
* @param requestId The ID of the request
* @returns True if the request exists, false otherwise
*/
public hasRequest(requestId: string): boolean {
return this.activeRequests.has(requestId)
}
/**
* Get all active requests
* @returns An array of [requestId, requestInfo] pairs
*/
public getAllRequests(): [string, RequestInfo][] {
return Array.from(this.activeRequests.entries())
}
/**
* Clean up stale requests that have been active for too long
* @param maxAgeMs Maximum age in milliseconds before a request is considered stale
* @returns The number of requests that were cleaned up
*/
public cleanupStaleRequests(maxAgeMs: number): number {
const now = new Date()
let cleanedCount = 0
for (const [requestId, info] of this.activeRequests.entries()) {
if (now.getTime() - info.timestamp.getTime() > maxAgeMs) {
this.cancelRequest(requestId)
cleanedCount++
}
}
return cleanedCount
}
}
@@ -1,75 +0,0 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { Controller } from "./index"
import { StreamingResponseHandler } from "./grpc-handler"
import { handleAccountServiceRequest, handleAccountServiceStreamingRequest } from "./account/index"
import { handleBrowserServiceRequest, handleBrowserServiceStreamingRequest } from "./browser/index"
import { handleCheckpointsServiceRequest, handleCheckpointsServiceStreamingRequest } from "./checkpoints/index"
import { handleFileServiceRequest, handleFileServiceStreamingRequest } from "./file/index"
import { handleMcpServiceRequest, handleMcpServiceStreamingRequest } from "./mcp/index"
import { handleStateServiceRequest, handleStateServiceStreamingRequest } from "./state/index"
import { handleTaskServiceRequest, handleTaskServiceStreamingRequest } from "./task/index"
import { handleWebServiceRequest, handleWebServiceStreamingRequest } from "./web/index"
import { handleModelsServiceRequest, handleModelsServiceStreamingRequest } from "./models/index"
import { handleSlashServiceRequest, handleSlashServiceStreamingRequest } from "./slash/index"
/**
* Configuration for a service handler
*/
export interface ServiceHandlerConfig {
requestHandler: (controller: Controller, method: string, message: any) => Promise<any>
streamingHandler: (
controller: Controller,
method: string,
message: any,
responseStream: StreamingResponseHandler,
requestId?: string,
) => Promise<void>
}
/**
* Map of service names to their handler configurations
*/
export const serviceHandlers: Record<string, ServiceHandlerConfig> = {
"cline.AccountService": {
requestHandler: handleAccountServiceRequest,
streamingHandler: handleAccountServiceStreamingRequest,
},
"cline.BrowserService": {
requestHandler: handleBrowserServiceRequest,
streamingHandler: handleBrowserServiceStreamingRequest,
},
"cline.CheckpointsService": {
requestHandler: handleCheckpointsServiceRequest,
streamingHandler: handleCheckpointsServiceStreamingRequest,
},
"cline.FileService": {
requestHandler: handleFileServiceRequest,
streamingHandler: handleFileServiceStreamingRequest,
},
"cline.McpService": {
requestHandler: handleMcpServiceRequest,
streamingHandler: handleMcpServiceStreamingRequest,
},
"cline.StateService": {
requestHandler: handleStateServiceRequest,
streamingHandler: handleStateServiceStreamingRequest,
},
"cline.TaskService": {
requestHandler: handleTaskServiceRequest,
streamingHandler: handleTaskServiceStreamingRequest,
},
"cline.WebService": {
requestHandler: handleWebServiceRequest,
streamingHandler: handleWebServiceStreamingRequest,
},
"cline.ModelsService": {
requestHandler: handleModelsServiceRequest,
streamingHandler: handleModelsServiceStreamingRequest,
},
"cline.SlashService": {
requestHandler: handleSlashServiceRequest,
streamingHandler: handleSlashServiceStreamingRequest,
},
}
+4 -92
View File
@@ -1,36 +1,16 @@
import { Controller } from "./index"
import { StreamingResponseHandler } from "./grpc-handler"
/**
* Generic type for service method handlers
*/
export type ServiceMethodHandler = (controller: Controller, message: any) => Promise<any>
/**
* Type for streaming method handlers
*/
export type StreamingMethodHandler = (
controller: Controller,
message: any,
responseStream: StreamingResponseHandler,
requestId?: string,
) => Promise<void>
/**
* Method metadata including streaming information
*/
export interface MethodMetadata {
isStreaming: boolean
}
/**
* Generic service registry for gRPC services
*/
export class ServiceRegistry {
private serviceName: string
private methodRegistry: Record<string, ServiceMethodHandler> = {}
private streamingMethodRegistry: Record<string, StreamingMethodHandler> = {}
private methodMetadata: Record<string, MethodMetadata> = {}
/**
* Create a new service registry
@@ -44,37 +24,10 @@ export class ServiceRegistry {
* Register a method handler
* @param methodName The name of the method to register
* @param handler The handler function for the method
* @param metadata Optional metadata about the method
*/
registerMethod(methodName: string, handler: ServiceMethodHandler | StreamingMethodHandler, metadata?: MethodMetadata): void {
const isStreaming = metadata?.isStreaming || false
if (isStreaming) {
this.streamingMethodRegistry[methodName] = handler as StreamingMethodHandler
} else {
this.methodRegistry[methodName] = handler as ServiceMethodHandler
}
this.methodMetadata[methodName] = { isStreaming, ...metadata }
console.log(`Registered ${this.serviceName} method: ${methodName}${isStreaming ? " (streaming)" : ""}`)
}
/**
* Check if a method is a streaming method
* @param method The method name
* @returns True if the method is a streaming method
*/
isStreamingMethod(method: string): boolean {
return this.methodMetadata[method]?.isStreaming || false
}
/**
* Get a streaming method handler
* @param method The method name
* @returns The streaming method handler or undefined if not found
*/
getStreamingHandler(method: string): StreamingMethodHandler | undefined {
return this.streamingMethodRegistry[method]
registerMethod(methodName: string, handler: ServiceMethodHandler): void {
this.methodRegistry[methodName] = handler
console.log(`Registered ${this.serviceName} method: ${methodName}`)
}
/**
@@ -88,41 +41,11 @@ export class ServiceRegistry {
const handler = this.methodRegistry[method]
if (!handler) {
if (this.isStreamingMethod(method)) {
throw new Error(`Method ${method} is a streaming method and should be handled with handleStreamingRequest`)
}
throw new Error(`Unknown ${this.serviceName} method: ${method}`)
}
return handler(controller, message)
}
/**
* Handle a streaming service request
* @param controller The controller instance
* @param method The method name
* @param message The request message
* @param responseStream The streaming response handler
* @param requestId The request ID for correlation and cleanup
*/
async handleStreamingRequest(
controller: Controller,
method: string,
message: any,
responseStream: StreamingResponseHandler,
requestId?: string,
): Promise<void> {
const handler = this.streamingMethodRegistry[method]
if (!handler) {
if (this.methodRegistry[method]) {
throw new Error(`Method ${method} is not a streaming method and should be handled with handleRequest`)
}
throw new Error(`Unknown ${this.serviceName} streaming method: ${method}`)
}
await handler(controller, message, responseStream, requestId)
}
}
/**
@@ -134,20 +57,9 @@ export function createServiceRegistry(serviceName: string) {
const registry = new ServiceRegistry(serviceName)
return {
registerMethod: (methodName: string, handler: ServiceMethodHandler | StreamingMethodHandler, metadata?: MethodMetadata) =>
registry.registerMethod(methodName, handler, metadata),
registerMethod: (methodName: string, handler: ServiceMethodHandler) => registry.registerMethod(methodName, handler),
handleRequest: (controller: Controller, method: string, message: any) =>
registry.handleRequest(controller, method, message),
handleStreamingRequest: (
controller: Controller,
method: string,
message: any,
responseStream: StreamingResponseHandler,
requestId?: string,
) => registry.handleStreamingRequest(controller, method, message, responseStream, requestId),
isStreamingMethod: (method: string) => registry.isStreamingMethod(method),
}
}
+489 -176
View File
@@ -1,14 +1,13 @@
import { Anthropic } from "@anthropic-ai/sdk"
import axios from "axios"
import type { AxiosRequestConfig } from "axios"
import fs from "fs/promises"
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
import pWaitFor from "p-wait-for"
import * as path from "path"
import * as vscode from "vscode"
import { handleGrpcRequest, handleGrpcRequestCancel } from "./grpc-handler"
import { handleModelsServiceRequest } from "./models"
import { EmptyRequest } from "@shared/proto/common"
import { handleGrpcRequest } from "./grpc-handler"
import { buildApiHandler } from "@api/index"
import { cleanupLegacyCheckpoints } from "@integrations/checkpoints/CheckpointMigration"
import { downloadTask } from "@integrations/misc/export-markdown"
@@ -20,7 +19,8 @@ import WorkspaceTracker from "@integrations/workspace/WorkspaceTracker"
import { ClineAccountService } from "@services/account/ClineAccountService"
import { BrowserSession } from "@services/browser/BrowserSession"
import { McpHub } from "@services/mcp/McpHub"
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
import { searchWorkspaceFiles } from "@services/search/file-search"
import { telemetryService } from "@services/telemetry/TelemetryService"
import { ApiProvider, ModelInfo } from "@shared/api"
import { ChatContent } from "@shared/ChatContent"
import { ChatSettings } from "@shared/ChatSettings"
@@ -28,10 +28,10 @@ import { ExtensionMessage, ExtensionState, Invoke, Platform } from "@shared/Exte
import { HistoryItem } from "@shared/HistoryItem"
import { McpDownloadResponse, McpMarketplaceCatalog, McpServer } from "@shared/mcp"
import { TelemetrySetting } from "@shared/TelemetrySetting"
import { WebviewMessage } from "@shared/WebviewMessage"
import { ClineCheckpointRestore, WebviewMessage } from "@shared/WebviewMessage"
import { fileExistsAtPath } from "@utils/fs"
import { getWorkingState } from "@utils/git"
import { extractCommitMessage } from "@integrations/git/commit-message-generator"
import { searchCommits } from "@utils/git"
import { getWorkspacePath } from "@utils/path"
import { getTotalTasksSize } from "@utils/storage"
import { openMention } from "../mentions"
import { ensureMcpServersDirectoryExists, ensureSettingsDirectoryExists, GlobalFileNames } from "../storage/disk"
@@ -48,7 +48,6 @@ import {
} from "../storage/state"
import { Task, cwd } from "../task"
import { ClineRulesToggles } from "@shared/cline-rules"
import { sendStateUpdate } from "./state/subscribeToState"
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
import { refreshExternalRulesToggles } from "@core/context/instructions/user-instructions/external-rules"
@@ -66,7 +65,7 @@ export class Controller {
workspaceTracker: WorkspaceTracker
mcpHub: McpHub
accountService: ClineAccountService
private latestAnnouncementId = "may-09-2025_17:11:00" // update to some unique identifier when we add a new announcement
private latestAnnouncementId = "may-02-2025_16:27:00" // update to some unique identifier when we add a new announcement
constructor(
readonly context: vscode.ExtensionContext,
@@ -231,15 +230,15 @@ export class Controller {
}
})
this.silentlyRefreshMcpMarketplace()
handleModelsServiceRequest(this, "refreshOpenRouterModels", EmptyRequest.create()).then(async (response) => {
if (response && response.models) {
this.refreshOpenRouterModels().then(async (openRouterModels) => {
if (openRouterModels) {
// update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
const { apiConfiguration } = await getAllExtensionState(this.context)
if (apiConfiguration.openRouterModelId && response.models[apiConfiguration.openRouterModelId]) {
if (apiConfiguration.openRouterModelId) {
await updateGlobalState(
this.context,
"openRouterModelInfo",
response.models[apiConfiguration.openRouterModelId],
openRouterModels[apiConfiguration.openRouterModelId],
)
await this.postStateToWebview()
}
@@ -249,7 +248,7 @@ export class Controller {
// If user already opted in to telemetry, enable telemetry service
this.getStateToPostToWebview().then((state) => {
const { telemetrySetting } = state
const isOptedIn = telemetrySetting !== "disabled"
const isOptedIn = telemetrySetting === "enabled"
telemetryService.updateTelemetryState(isOptedIn)
})
break
@@ -271,6 +270,9 @@ export class Controller {
// initializing new instance of Cline will make sure that any agentically running promises in old instance don't affect our new task. this essentially creates a fresh slate for the new task
await this.initTask(message.text, message.images)
break
case "condense":
this.task?.handleWebviewAskResponse("yesButtonClicked")
break
case "apiConfiguration":
if (message.apiConfiguration) {
await updateApiConfiguration(this.context, message.apiConfiguration)
@@ -325,9 +327,50 @@ export class Controller {
images,
})
break
case "exportCurrentTask":
const currentTaskId = this.task?.taskId
if (currentTaskId) {
this.exportTaskWithId(currentTaskId)
}
break
case "showTaskWithId":
this.showTaskWithId(message.text!)
break
case "exportTaskWithId":
this.exportTaskWithId(message.text!)
break
case "resetState":
await this.resetState()
break
case "requestOllamaModels":
const ollamaModels = await this.getOllamaModels(message.text)
this.postMessageToWebview({
type: "ollamaModels",
ollamaModels,
})
break
case "requestLmStudioModels":
const lmStudioModels = await this.getLmStudioModels(message.text)
this.postMessageToWebview({
type: "lmStudioModels",
lmStudioModels,
})
break
case "requestVsCodeLmModels":
const vsCodeLmModels = await this.getVsCodeLmModels()
this.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
break
case "refreshOpenRouterModels":
await this.refreshOpenRouterModels()
break
case "refreshRequestyModels":
await this.refreshRequestyModels()
break
case "refreshOpenAiModels":
const { apiConfiguration } = await getAllExtensionState(this.context)
const openAiModels = await this.getOpenAiModels(apiConfiguration.openAiBaseUrl, apiConfiguration.openAiApiKey)
this.postMessageToWebview({ type: "openAiModels", openAiModels })
break
case "refreshClineRules":
await refreshClineRulesToggles(this.context, cwd)
await refreshExternalRulesToggles(this.context, cwd)
@@ -350,6 +393,9 @@ export class Controller {
}
break
}
case "getLatestState":
await this.postStateToWebview()
break
case "accountLogoutClicked": {
await this.handleSignOut()
break
@@ -373,6 +419,19 @@ export class Controller {
await this.fetchMcpMarketplace(message.bool)
break
}
case "downloadMcp": {
if (message.mcpId) {
// 1. Toggle to act mode if we are in plan mode
const { chatSettings } = await this.getStateToPostToWebview()
if (chatSettings.mode === "plan") {
await this.togglePlanActModeWithChatSettings({ mode: "act" })
}
// 2. download MCP
await this.downloadMcp(message.mcpId)
}
break
}
case "silentlyRefreshMcpMarketplace": {
await this.silentlyRefreshMcpMarketplace()
break
@@ -571,18 +630,11 @@ export class Controller {
}
case "clearAllTaskHistory": {
const answer = await vscode.window.showWarningMessage(
"What would you like to delete?",
{ modal: true },
"Delete All Except Favorites",
"Delete Everything",
"Are you sure you want to delete all history?",
"Delete",
"Cancel",
)
if (answer === "Delete All Except Favorites") {
await this.deleteNonFavoriteTaskHistory()
await this.postStateToWebview()
this.refreshTotalTasksSize()
} else if (answer === "Delete Everything") {
if (answer === "Delete") {
await this.deleteAllTaskHistory()
await this.postStateToWebview()
this.refreshTotalTasksSize()
@@ -590,15 +642,73 @@ export class Controller {
this.postMessageToWebview({ type: "relinquishControl" })
break
}
case "grpc_request": {
if (message.grpc_request) {
await handleGrpcRequest(this, message.grpc_request)
case "searchFiles": {
const workspacePath = getWorkspacePath()
if (!workspacePath) {
// Handle case where workspace path is not available
await this.postMessageToWebview({
type: "fileSearchResults",
results: [],
mentionsRequestId: message.mentionsRequestId,
error: "No workspace path available",
})
break
}
try {
// Call file search service with query from message
const results = await searchWorkspaceFiles(
message.query || "",
workspacePath,
20, // Use default limit, as filtering is now done in the backend
)
// debug logging to be removed
//console.log(`controller/index.ts: Search results: ${results.length}`)
// Send results back to webview
await this.postMessageToWebview({
type: "fileSearchResults",
results,
mentionsRequestId: message.mentionsRequestId,
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
// Send error response to webview
await this.postMessageToWebview({
type: "fileSearchResults",
results: [],
error: errorMessage,
mentionsRequestId: message.mentionsRequestId,
})
}
break
}
case "grpc_request_cancel": {
if (message.grpc_request_cancel) {
await handleGrpcRequestCancel(this, message.grpc_request_cancel)
case "toggleFavoriteModel": {
if (message.modelId) {
const { apiConfiguration } = await getAllExtensionState(this.context)
const favoritedModelIds = apiConfiguration.favoritedModelIds || []
// Toggle favorite status
const updatedFavorites = favoritedModelIds.includes(message.modelId)
? favoritedModelIds.filter((id) => id !== message.modelId)
: [...favoritedModelIds, message.modelId]
await updateGlobalState(this.context, "favoritedModelIds", updatedFavorites)
// Capture telemetry for model favorite toggle
const isFavorited = !favoritedModelIds.includes(message.modelId)
telemetryService.captureModelFavoritesUsage(message.modelId, isFavorited)
// Post state to webview without changing any other configuration
await this.postStateToWebview()
}
break
}
case "grpc_request": {
if (message.grpc_request) {
await handleGrpcRequest(this, message.grpc_request)
}
break
}
@@ -633,7 +743,7 @@ export class Controller {
async updateTelemetrySetting(telemetrySetting: TelemetrySetting) {
await updateGlobalState(this.context, "telemetrySetting", telemetrySetting)
const isOptedIn = telemetrySetting !== "disabled"
const isOptedIn = telemetrySetting === "enabled"
telemetryService.updateTelemetryState(isOptedIn)
}
@@ -713,7 +823,6 @@ export class Controller {
break
case "litellm":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
break
case "requesty":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.requestyModelId)
@@ -767,8 +876,7 @@ export class Controller {
await updateGlobalState(this.context, "lmStudioModelId", newModelId)
break
case "litellm":
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.liteLlmModelId)
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.liteLlmModelInfo)
await updateGlobalState(this.context, "liteLlmModelId", newModelId)
break
case "requesty":
await updateGlobalState(this.context, "requestyModelId", newModelId)
@@ -840,6 +948,56 @@ export class Controller {
}
}
// VSCode LM API
private async getVsCodeLmModels() {
try {
const models = await vscode.lm.selectChatModels({})
return models || []
} catch (error) {
console.error("Error fetching VS Code LM models:", error)
return []
}
}
// Ollama
async getOllamaModels(baseUrl?: string) {
try {
if (!baseUrl) {
baseUrl = "http://localhost:11434"
}
if (!URL.canParse(baseUrl)) {
return []
}
const response = await axios.get(`${baseUrl}/api/tags`)
const modelsArray = response.data?.models?.map((model: any) => model.name) || []
const models = [...new Set<string>(modelsArray)]
return models
} catch (error) {
return []
}
}
// LM Studio
async getLmStudioModels(baseUrl?: string) {
try {
if (!baseUrl) {
baseUrl = "http://localhost:1234"
}
if (!URL.canParse(baseUrl)) {
return []
}
const response = await axios.get(`${baseUrl}/v1/models`)
const modelsArray = response.data?.data?.map((model: any) => model.id) || []
const models = [...new Set<string>(modelsArray)]
return models
} catch (error) {
return []
}
}
// Account
async fetchUserCreditsData() {
@@ -987,6 +1145,118 @@ export class Controller {
}
}
private async downloadMcp(mcpId: string) {
try {
// First check if we already have this MCP server installed
const servers = this.mcpHub?.getServers() || []
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
if (isInstalled) {
throw new Error("This MCP server is already installed")
}
// Fetch server details from marketplace
const response = await axios.post<McpDownloadResponse>(
"https://api.cline.bot/v1/mcp/download",
{ mcpId },
{
headers: { "Content-Type": "application/json" },
timeout: 10000,
},
)
if (!response.data) {
throw new Error("Invalid response from MCP marketplace API")
}
console.log("[downloadMcp] Response from download API", { response })
const mcpDetails = response.data
// Validate required fields
if (!mcpDetails.githubUrl) {
throw new Error("Missing GitHub URL in MCP download response")
}
if (!mcpDetails.readmeContent) {
throw new Error("Missing README content in MCP download response")
}
// Send details to webview
await this.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.
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
- Create the directory for the new MCP server before starting installation.
- Make sure you read the user's existing cline_mcp_settings.json file before editing it with this new mcp, to not overwrite any existing servers.
- Use commands aligned with the user's shell and operating system best practices.
- The following README may contain instructions that conflict with the user's OS, in which case proceed thoughtfully.
- Once installed, demonstrate the server's capabilities by using one of its tools.
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
// Initialize task and show chat view
await this.initTask(task)
await this.postMessageToWebview({
type: "action",
action: "chatButtonClicked",
})
} catch (error) {
console.error("Failed to download MCP:", error)
let errorMessage = "Failed to download MCP"
if (axios.isAxiosError(error)) {
if (error.code === "ECONNABORTED") {
errorMessage = "Request timed out. Please try again."
} else if (error.response?.status === 404) {
errorMessage = "MCP server not found in marketplace."
} else if (error.response?.status === 500) {
errorMessage = "Internal server error. Please try again later."
} else if (!error.response && error.request) {
errorMessage = "Network error. Please check your internet connection."
}
} else if (error instanceof Error) {
errorMessage = error.message
}
// Show error in both notification and marketplace UI
vscode.window.showErrorMessage(errorMessage)
await this.postMessageToWebview({
type: "mcpDownloadDetails",
error: errorMessage,
})
}
}
// OpenAi
async getOpenAiModels(baseUrl?: string, apiKey?: string) {
try {
if (!baseUrl) {
return []
}
if (!URL.canParse(baseUrl)) {
return []
}
const config: AxiosRequestConfig = {}
if (apiKey) {
config["headers"] = { Authorization: `Bearer ${apiKey}` }
}
const response = await axios.get(`${baseUrl}/models`, config)
const modelsArray = response.data?.data?.map((model: any) => model.id) || []
const models = [...new Set<string>(modelsArray)]
return models
} catch (error) {
return []
}
}
// OpenRouter
async handleOpenRouterCallback(code: string) {
@@ -1022,7 +1292,6 @@ export class Controller {
return cacheDir
}
// Read OpenRouter models from disk cache
async readOpenRouterModels(): Promise<Record<string, ModelInfo> | undefined> {
const openRouterModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
const fileExists = await fileExistsAtPath(openRouterModelsFilePath)
@@ -1033,6 +1302,190 @@ export class Controller {
return undefined
}
async refreshOpenRouterModels() {
const openRouterModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
let models: Record<string, ModelInfo> = {}
try {
const response = await axios.get("https://openrouter.ai/api/v1/models")
/*
{
"id": "anthropic/claude-3.5-sonnet",
"name": "Anthropic: Claude 3.5 Sonnet",
"created": 1718841600,
"description": "Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: Autonomously writes, edits, and runs code with reasoning and troubleshooting\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal",
"context_length": 200000,
"architecture": {
"modality": "text+image-\u003Etext",
"tokenizer": "Claude",
"instruct_type": null
},
"pricing": {
"prompt": "0.000003",
"completion": "0.000015",
"image": "0.0048",
"request": "0"
},
"top_provider": {
"context_length": 200000,
"max_completion_tokens": 8192,
"is_moderated": true
},
"per_request_limits": null
},
*/
if (response.data?.data) {
const rawModels = response.data.data
const parsePrice = (price: any) => {
if (price) {
return parseFloat(price) * 1_000_000
}
return undefined
}
for (const rawModel of rawModels) {
const modelInfo: ModelInfo = {
maxTokens: rawModel.top_provider?.max_completion_tokens,
contextWindow: rawModel.context_length,
supportsImages: rawModel.architecture?.modality?.includes("image"),
supportsPromptCache: false,
inputPrice: parsePrice(rawModel.pricing?.prompt),
outputPrice: parsePrice(rawModel.pricing?.completion),
description: rawModel.description,
}
switch (rawModel.id) {
case "anthropic/claude-3-7-sonnet":
case "anthropic/claude-3-7-sonnet:beta":
case "anthropic/claude-3.7-sonnet":
case "anthropic/claude-3.7-sonnet:beta":
case "anthropic/claude-3.7-sonnet:thinking":
case "anthropic/claude-3.5-sonnet":
case "anthropic/claude-3.5-sonnet:beta":
// NOTE: this needs to be synced with api.ts/openrouter default model info
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 3.75
modelInfo.cacheReadsPrice = 0.3
break
case "anthropic/claude-3.5-sonnet-20240620":
case "anthropic/claude-3.5-sonnet-20240620:beta":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 3.75
modelInfo.cacheReadsPrice = 0.3
break
case "anthropic/claude-3-5-haiku":
case "anthropic/claude-3-5-haiku:beta":
case "anthropic/claude-3-5-haiku-20241022":
case "anthropic/claude-3-5-haiku-20241022:beta":
case "anthropic/claude-3.5-haiku":
case "anthropic/claude-3.5-haiku:beta":
case "anthropic/claude-3.5-haiku-20241022":
case "anthropic/claude-3.5-haiku-20241022:beta":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 1.25
modelInfo.cacheReadsPrice = 0.1
break
case "anthropic/claude-3-opus":
case "anthropic/claude-3-opus:beta":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 18.75
modelInfo.cacheReadsPrice = 1.5
break
case "anthropic/claude-3-haiku":
case "anthropic/claude-3-haiku:beta":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 0.3
modelInfo.cacheReadsPrice = 0.03
break
case "deepseek/deepseek-chat":
modelInfo.supportsPromptCache = true
// see api.ts/deepSeekModels for more info
modelInfo.inputPrice = 0
modelInfo.cacheWritesPrice = 0.14
modelInfo.cacheReadsPrice = 0.014
break
case "google/gemini-2.5-pro-preview-03-25":
case "google/gemini-2.0-flash-001":
case "google/gemini-flash-1.5":
case "google/gemini-pro-1.5":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
break
default:
if (rawModel.id.startsWith("openai/")) {
modelInfo.cacheReadsPrice = parsePrice(rawModel.pricing?.input_cache_read)
if (modelInfo.cacheReadsPrice) {
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = parsePrice(rawModel.pricing?.input_cache_write)
// openrouter charges no cache write pricing for openAI models
}
}
break
}
models[rawModel.id] = modelInfo
}
} else {
console.error("Invalid response from OpenRouter API")
}
await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
console.log("OpenRouter models fetched and saved", models)
} catch (error) {
console.error("Error fetching OpenRouter models:", error)
}
await this.postMessageToWebview({
type: "openRouterModels",
openRouterModels: models,
})
return models
}
async refreshRequestyModels() {
const parsePrice = (price: any) => {
if (price) {
return parseFloat(price) * 1_000_000
}
return undefined
}
let models: Record<string, ModelInfo> = {}
try {
const apiKey = await getSecret(this.context, "requestyApiKey")
const headers = {
Authorization: `Bearer ${apiKey}`,
}
const response = await axios.get("https://router.requesty.ai/v1/models", { headers })
if (response.data?.data) {
for (const model of response.data.data) {
const modelInfo: ModelInfo = {
maxTokens: model.max_output_tokens || undefined,
contextWindow: model.context_window,
supportsImages: model.supports_vision || undefined,
supportsPromptCache: model.supports_caching || undefined,
inputPrice: parsePrice(model.input_price),
outputPrice: parsePrice(model.output_price),
cacheWritesPrice: parsePrice(model.caching_price),
cacheReadsPrice: parsePrice(model.cached_price),
description: model.description,
}
models[model.id] = modelInfo
}
console.log("Requesty models fetched", models)
} else {
console.error("Invalid response from Requesty API")
}
} catch (error) {
console.error("Error fetching Requesty models:", error)
}
await this.postMessageToWebview({
type: "requestyModels",
requestyModels: models,
})
return models
}
// Context menus and code actions
getFileMentionFromPath(filePath: string) {
@@ -1207,43 +1660,6 @@ export class Controller {
// await this.postStateToWebview()
}
async deleteNonFavoriteTaskHistory() {
await this.clearTask()
const taskHistory = ((await getGlobalState(this.context, "taskHistory")) as HistoryItem[]) || []
const favoritedTasks = taskHistory.filter((task) => task.isFavorited === true)
// If user has no favorited tasks, show a warning message
if (favoritedTasks.length === 0) {
vscode.window.showWarningMessage("No favorited tasks found. Please favorite tasks before using this option.")
await this.postStateToWebview()
return
}
await updateGlobalState(this.context, "taskHistory", favoritedTasks)
// Delete non-favorited task directories
try {
const preserveTaskIds = favoritedTasks.map((task) => task.id)
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks")
if (await fileExistsAtPath(taskDirPath)) {
const taskDirs = await fs.readdir(taskDirPath)
for (const taskDir of taskDirs) {
if (!preserveTaskIds.includes(taskDir)) {
await fs.rm(path.join(taskDirPath, taskDir), { recursive: true, force: true })
}
}
}
} catch (error) {
vscode.window.showErrorMessage(
`Error deleting task history: ${error instanceof Error ? error.message : String(error)}`,
)
}
await this.postStateToWebview()
}
async refreshTotalTasksSize() {
getTotalTasksSize(this.context.globalStorageUri.fsPath)
.then((newTotalSize) => {
@@ -1316,7 +1732,7 @@ export class Controller {
async postStateToWebview() {
const state = await this.getStateToPostToWebview()
await sendStateUpdate(state)
this.postMessageToWebview({ type: "state", state })
}
async getStateToPostToWebview(): Promise<ExtensionState> {
@@ -1471,109 +1887,6 @@ export class Controller {
}
}
// Git commit message generation
async generateGitCommitMessage() {
try {
// Check if there's a workspace folder open
const cwd = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
if (!cwd) {
vscode.window.showErrorMessage("No workspace folder open")
return
}
// Get the git diff
const gitDiff = await getWorkingState(cwd)
if (gitDiff === "No changes in working directory") {
vscode.window.showInformationMessage("No changes in workspace for commit message")
return
}
// Show a progress notification
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Generating commit message...",
cancellable: false,
},
async (progress, token) => {
try {
// Format the git diff into a prompt
const prompt = `Based on the following git diff, generate a concise and descriptive commit message:
${gitDiff.length > 5000 ? gitDiff.substring(0, 5000) + "\n\n[Diff truncated due to size]" : gitDiff}
The commit message should:
1. Start with a short summary (50-72 characters)
2. Use the imperative mood (e.g., "Add feature" not "Added feature")
3. Describe what was changed and why
4. Be clear and descriptive
Commit message:`
// Get the current API configuration
const { apiConfiguration } = await getAllExtensionState(this.context)
// Build the API handler
const apiHandler = buildApiHandler(apiConfiguration)
// Create a system prompt
const systemPrompt =
"You are a helpful assistant that generates concise and descriptive git commit messages based on git diffs."
// Create a message for the API
const messages = [
{
role: "user" as const,
content: prompt,
},
]
// Call the API directly
const stream = apiHandler.createMessage(systemPrompt, messages)
// Collect the response
let response = ""
for await (const chunk of stream) {
if (chunk.type === "text") {
response += chunk.text
}
}
// Extract the commit message
const commitMessage = extractCommitMessage(response)
// Apply the commit message to the Git input box
if (commitMessage) {
// Get the Git extension API
const gitExtension = vscode.extensions.getExtension("vscode.git")?.exports
if (gitExtension) {
const api = gitExtension.getAPI(1)
if (api && api.repositories.length > 0) {
const repo = api.repositories[0]
repo.inputBox.value = commitMessage
vscode.window.showInformationMessage("Commit message generated and applied")
} else {
vscode.window.showErrorMessage("No Git repositories found")
}
} else {
vscode.window.showErrorMessage("Git extension not found")
}
} else {
vscode.window.showErrorMessage("Failed to generate commit message")
}
} catch (innerError) {
const innerErrorMessage = innerError instanceof Error ? innerError.message : String(innerError)
vscode.window.showErrorMessage(`Failed to generate commit message: ${innerErrorMessage}`)
}
},
)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
vscode.window.showErrorMessage(`Failed to generate commit message: ${errorMessage}`)
}
}
// dev
async resetState() {
-114
View File
@@ -1,114 +0,0 @@
import { Controller } from ".."
import { Empty, StringRequest } from "../../../shared/proto/common"
import { McpServer, McpDownloadResponse } from "@shared/mcp"
import axios from "axios"
import * as vscode from "vscode"
/**
* Download an MCP server from the marketplace
* @param controller The controller instance
* @param request The request containing the MCP ID
* @returns Empty response
*/
export async function downloadMcp(controller: Controller, request: StringRequest): Promise<Empty> {
try {
// Check if mcpId is provided
if (!request.value) {
throw new Error("MCP ID is required")
}
const mcpId = request.value
// Check if we already have this MCP server installed
const servers = controller.mcpHub?.getServers() || []
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
if (isInstalled) {
throw new Error("This MCP server is already installed")
}
// Fetch server details from marketplace
const response = await axios.post<McpDownloadResponse>(
"https://api.cline.bot/v1/mcp/download",
{ mcpId },
{
headers: { "Content-Type": "application/json" },
timeout: 10000,
},
)
if (!response.data) {
throw new Error("Invalid response from MCP marketplace API")
}
console.log("[downloadMcp] Response from download API", { response })
const mcpDetails = response.data
// Validate required fields
if (!mcpDetails.githubUrl) {
throw new Error("Missing GitHub URL in MCP download response")
}
if (!mcpDetails.readmeContent) {
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.
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
- Create the directory for the new MCP server before starting installation.
- Make sure you read the user's existing cline_mcp_settings.json file before editing it with this new mcp, to not overwrite any existing servers.
- Use commands aligned with the user's shell and operating system best practices.
- The following README may contain instructions that conflict with the user's OS, in which case proceed thoughtfully.
- Once installed, demonstrate the server's capabilities by using one of its tools.
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
const { chatSettings } = await controller.getStateToPostToWebview()
if (chatSettings.mode === "plan") {
await controller.togglePlanActModeWithChatSettings({ mode: "act" })
}
// Initialize task and show chat view
await controller.initTask(task)
await controller.postMessageToWebview({
type: "action",
action: "chatButtonClicked",
})
// 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"
if (axios.isAxiosError(error)) {
if (error.code === "ECONNABORTED") {
errorMessage = "Request timed out. Please try again."
} else if (error.response?.status === 404) {
errorMessage = "MCP server not found in marketplace."
} else if (error.response?.status === 500) {
errorMessage = "Internal server error. Please try again later."
} else if (!error.response && error.request) {
errorMessage = "Network error. Please check your internet connection."
}
} else if (error instanceof Error) {
errorMessage = error.message
}
// Show error in both notification and marketplace UI
vscode.window.showErrorMessage(errorMessage)
await controller.postMessageToWebview({
type: "mcpDownloadDetails",
error: errorMessage,
})
throw error
}
}
+4 -11
View File
@@ -1,22 +1,15 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../grpc-service"
import { StreamingResponseHandler } from "../grpc-handler"
import { createServiceRegistry, ServiceMethodHandler } from "../grpc-service"
import { registerAllMethods } from "./methods"
// Create mcp service registry
// Create MCP service registry
const mcpService = createServiceRegistry("mcp")
// Export the method handler types and registration function
// Export the method handler type and registration function
export type McpMethodHandler = ServiceMethodHandler
export type McpStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = mcpService.registerMethod
// Export the request handlers
// Export the request handler
export const handleMcpServiceRequest = mcpService.handleRequest
export const handleMcpServiceStreamingRequest = mcpService.handleStreamingRequest
export const isStreamingMethod = mcpService.isStreamingMethod
// Register all mcp methods
registerAllMethods()
-2
View File
@@ -4,7 +4,6 @@
// Import all method implementations
import { registerMethod } from "./index"
import { addRemoteMcpServer } from "./addRemoteMcpServer"
import { downloadMcp } from "./downloadMcp"
import { toggleMcpServer } from "./toggleMcpServer"
import { updateMcpTimeout } from "./updateMcpTimeout"
@@ -12,7 +11,6 @@ import { updateMcpTimeout } from "./updateMcpTimeout"
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("addRemoteMcpServer", addRemoteMcpServer)
registerMethod("downloadMcp", downloadMcp)
registerMethod("toggleMcpServer", toggleMcpServer)
registerMethod("updateMcpTimeout", updateMcpTimeout)
}
@@ -1,27 +0,0 @@
import { Controller } from ".."
import { StringArray, StringRequest } from "../../../shared/proto/common"
import axios from "axios"
/**
* Fetches available models from LM Studio
* @param controller The controller instance
* @param request The request containing the base URL (optional)
* @returns Array of model names
*/
export async function getLmStudioModels(controller: Controller, request: StringRequest): Promise<StringArray> {
try {
let baseUrl = request.value || "http://localhost:1234"
if (!URL.canParse(baseUrl)) {
return StringArray.create({ values: [] })
}
const response = await axios.get(`${baseUrl}/v1/models`)
const modelsArray = response.data?.data?.map((model: any) => model.id) || []
const models = [...new Set<string>(modelsArray)]
return StringArray.create({ values: models })
} catch (error) {
return StringArray.create({ values: [] })
}
}

Some files were not shown because too many files have changed in this diff Show More