mirror of
https://github.com/cline/cline.git
synced 2026-09-04 11:44:01 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d9d96e1e5 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: mcp servers are not started when disabled
|
||||
@@ -1,392 +0,0 @@
|
||||
# General writing guide
|
||||
|
||||
# How I want you to write
|
||||
|
||||
I'm gonna write something technical.
|
||||
|
||||
It's often less about the nitty-gritty details of the tech stuff and more about learning something new or getting a solution handed to me on a silver platter.
|
||||
|
||||
Look, when I read, I want something out of it. So when I write, I gotta remember that my readers want something too. This whole piece? It's about cluing in anyone who writes for me, or wants me to write for them, on how I see this whole writing product thing.
|
||||
|
||||
I'm gonna lay out a checklist of stuff I'd like to have. It'll make the whole writing gig a bit smoother, you know?
|
||||
|
||||
## Crafting Compelling Titles
|
||||
|
||||
I often come across titles like "How to do X with Y,Z technology." These don't excite me because X or Y are usually unfamiliar unless they're already well-known. Its rarely the dream to use X unless X is the dream.
|
||||
|
||||
My dream isn’t to use instructor, its to do something valueble with the data it extracts
|
||||
|
||||
An effective title should:
|
||||
|
||||
- Evoke an emotional response
|
||||
- Highlight someone's goal
|
||||
- Offer a dream or aspiration
|
||||
- Challenge or comment on a belief
|
||||
- Address someone's problems
|
||||
|
||||
I believe it's more impactful to write about specific problems. If this approach works, you can replicate it across various scenarios rather than staying too general.
|
||||
|
||||
- Time management for everyone can be a 15$ ebook
|
||||
- Time management for executives is a 2000$ workshop
|
||||
|
||||
Aim for titles that answer questions you think everyone is asking, or address thoughts people have but can't quite articulate.
|
||||
|
||||
Instead of "How I do something" or "How to do something," frame it from the reader's perspective with "How you can do something." This makes the title more engaging. Just make sure the difference is advisory if the content is subjective. “How I made a million dollars” might be more reasonable than “How to make a million dollars” since you are the subject and the goal might be to share your story in hopes of helping others.
|
||||
|
||||
This approach ultimately trains the reader to have a stronger emotional connection to your content.
|
||||
|
||||
- "How I do X"
|
||||
- "How You Can do X"
|
||||
|
||||
Between these two titles, it's obvious which one resonates more emotionally.
|
||||
|
||||
You can take it further by adding specific conditions. For instance, you could target a particular audience or set a timeframe:
|
||||
|
||||
- How to set up Braintrust
|
||||
- How to set up Braintrust in 5 minutes
|
||||
|
||||
## NO adjectiives
|
||||
|
||||
I want you to almost always avoid adjectives and try to use evidence instead. Instead of saying "production ready," you can write something like "scaling this to 100 servers or 1 million documents per second." Numbers like that will tell you exactly what the specificity of your product is. If you have to use adjectives rather than evidence, you are probably making something up.
|
||||
|
||||
There's no reason to say something like "blazingly fast" unless those things are already known phrases.
|
||||
|
||||
Instead, say "200 times faster" or "30% faster." A 30% improvement in recommendation system speed is insane.
|
||||
|
||||
There's a 200 times performance improvement because we went from one programming language to another. It's just something that's a little bit more expected and understandable.
|
||||
|
||||
Another test that I really like using recently is tracking whether or not the statements you make can be:
|
||||
|
||||
- Visualized
|
||||
- Proven false
|
||||
- Said only by you
|
||||
|
||||
If you can nail all three, the claim you make will be more likely to resonate with an audience because only you can say it.
|
||||
|
||||
Earlier this year, I had an example where I embedded all of Wikipedia in 17 minutes with 20 bucks, and it got half a million views. All we posted was a video of me kicking off the job, and then you can see all the log lines go through. You see the number of containers go from 1 out of 50 to 50 out of 50.
|
||||
|
||||
It was easy to visualize and could have been proven false by being unreproducible. Lastly, Modal is the only company that could do that in such an effortless way, which made it unique.
|
||||
|
||||
## Keep It Digestible
|
||||
- Aim for 5-minute reads
|
||||
- Write at a Grade 10 reading level
|
||||
- Break up long paragraphs
|
||||
- Use headers and bullet points
|
||||
|
||||
## Make It Scannable
|
||||
- Bold key points
|
||||
- Use subheadings every 3-4 paragraphs
|
||||
- Include plenty of white space
|
||||
- Add relevant examples
|
||||
|
||||
This structure works whether you're writing a tweet thread or a full blog post. The key is making complex ideas accessible.
|
||||
|
||||
# Guide to Writing Cline Documentation
|
||||
|
||||
## Some general principles for explaining features
|
||||
|
||||
If you're talking about a feature, it's helpful to start with a human-readable explanations that cover what the feature is in simple terms. Skip jargon and explain it like you're talking to someone who's never seen it before. This sets the foundation for everything that follows.
|
||||
|
||||
Combine location and usage into one flowing section. Tell users exactly where to find the feature and how to use it, but weave the instructions into natural prose with a good balance of bullet points, numbered lists, code examples (if applicable), mintlify components, and headers/subheaders. Users shouldn't have to jump between separate "where is it" and "how do I use it" sections.
|
||||
|
||||
Show the feature in action with real examples like actual files, workflows, or code. Users need to see concrete implementations, not just abstract descriptions. This is where understanding turns into practical knowledge.
|
||||
|
||||
When talking about a feature, include an inspiration section that sparks imagination. This section pushes people from understanding to action by showing them what becomes possible when they use this feature creatively. It's what separates good documentation from great documentation.
|
||||
|
||||
## Writing Principles That Actually Work
|
||||
|
||||
### Write for Action, Not Just Understanding
|
||||
|
||||
Documentation should motivate users to try things. Instead of just explaining how something works, focus on what users can accomplish with it. The inspiration section is crucial - it's what transforms passive readers into active users.
|
||||
|
||||
### Create a Natural Story Flow
|
||||
|
||||
It should feel like a conversation that naturally progresses from "what is this?" to "how do I use it?" to "here's a real example" to "imagine what you could do with this."
|
||||
|
||||
### Show Real Examples, Not Toy Demos
|
||||
|
||||
Provide actual workflow files, real code snippets, and concrete implementations that users can copy and adapt. Abstract examples don't help anyone - users want to see exactly what they'll be working with.
|
||||
|
||||
### Keep It Scannable But Not Fragmented
|
||||
|
||||
Write in prose that flows naturally when read completely, but structure it so users can quickly find specific information when they're troubleshooting. Avoid dense walls of text, but also avoid over-formatting with excessive bullet points and bold headers. There should be a nice visual heirarchy of balance between all elements, so you can quickly scan the page and find what you're looking for.
|
||||
|
||||
## Language and Tone Guidelines
|
||||
|
||||
Write clearly without dumbing things down. Use simple language when possible, but don't avoid technical terms that users need to know. Explain concepts in terms of what users can achieve rather than how the software works internally.
|
||||
|
||||
Make your writing conversational and encouraging. Phrases like "you can also try" or "when that works" feel more natural than rigid instructional language. Help users feel confident about trying new things.
|
||||
|
||||
Keep content concise and purposeful. Every sentence should either help users understand something or help them do something. If it doesn't serve one of those purposes, cut it.
|
||||
|
||||
Build in context and reasoning. Users want to understand why they're doing something, not just what to do. This builds confidence and helps them troubleshoot when things don't work exactly as expected.
|
||||
|
||||
## Practical Implementation
|
||||
|
||||
Structure each feature page consistently with the four-section approach, but let the content flow naturally within that structure. Use visual assets like videos and screenshots to complement the written content - they often communicate more effectively than paragraphs of description.
|
||||
|
||||
Link generously to related resources, examples, and deeper documentation. Users should never feel stuck or wonder where to go next. Maintain a repository of real examples that users can reference and adapt to their own needs.
|
||||
|
||||
The goal is documentation that feels more like helpful guidance from an experienced colleague than a technical manual. Users should finish reading feeling excited about what they can accomplish, not just informed about what the feature does.
|
||||
|
||||
## Balance Structure with Flexibility
|
||||
|
||||
While they discuss having consistent documentation structure, there's also mention of making content feel less rigid and more natural. The writing should follow guidelines while still feeling conversational and engaging.
|
||||
|
||||
## Bad examples
|
||||
|
||||
I personally hate this pattern of bullet point **Bold Text** colon and then more text:
|
||||
<bad_example_of_writing>
|
||||
#### macOS
|
||||
|
||||
1. **Switch to bash**: Go to Cline Settings → Terminal → Default Terminal Profile → Select "bash"
|
||||
2. **Disable Oh-My-Zsh temporarily**: If using zsh, try `mv ~/.zshrc ~/.zshrc.backup` and restart VSCode
|
||||
3. **Set environment**: Add to your shell config: `export TERM=xterm-256color`
|
||||
|
||||
#### Windows
|
||||
|
||||
1. **Use PowerShell 7**: Install from Microsoft Store, then select it in Cline settings
|
||||
2. **Disable Windows ConPTY**: VSCode Settings → Terminal › Integrated: Windows Enable Conpty → Uncheck
|
||||
3. **Try Command Prompt**: Sometimes simpler is better - switch to cmd.exe
|
||||
|
||||
#### Linux
|
||||
|
||||
1. **Use bash**: Most reliable option - select in Cline settings
|
||||
2. **Check permissions**: Ensure VSCode has terminal access permissions
|
||||
3. **Disable custom prompts**: Comment out prompt customizations in `.bashrc`
|
||||
|
||||
</bad_example_of_writing>
|
||||
|
||||
We should instead strive to write beautiful docs that read well. We can use bullet points and numbered lists but it should read naturally and be delightful to look at hierachally when scanning through the doc. There should be a good balance between blocks of text, code snippets, paragraphs, numbered lists, and bullet points. When scanning the documentation visually, you should feel like you're adminiring a tasteful art piece.
|
||||
|
||||
<good_example_of_writing>
|
||||
#### macOS
|
||||
|
||||
The most common fix is switching to bash. Navigate to Cline Settings → Terminal → Default Terminal Profile and select "bash" from the dropdown.
|
||||
|
||||
If you're still having issues, Oh-My-Zsh might be interfering with terminal integration. Try temporarily disabling it:
|
||||
- Run `mv ~/.zshrc ~/.zshrc.backup`
|
||||
- Restart VSCode
|
||||
|
||||
You can also add `export TERM=xterm-256color` to your shell configuration file to improve compatibility.
|
||||
|
||||
#### Windows
|
||||
|
||||
PowerShell 7 provides the most reliable experience. Install it from the Microsoft Store, then select it in your Cline settings.
|
||||
|
||||
Still seeing problems? Try these solutions:
|
||||
- Disable Windows ConPTY: VSCode Settings → Terminal › Integrated: Windows Enable Conpty → uncheck
|
||||
- Switch to Command Prompt (cmd.exe) - sometimes simpler shells work better
|
||||
|
||||
#### Linux
|
||||
|
||||
Bash is your most dependable option. Select it in Cline settings if you haven't already.
|
||||
|
||||
Check these common issues:
|
||||
- Ensure VSCode has terminal access permissions
|
||||
- Temporarily comment out custom prompt configurations in your `.bashrc`
|
||||
</good_example_of_writing>
|
||||
|
||||
This is much more natural to read. Writing this way creates a conversational flow, and bullet points are used idiomatically.
|
||||
|
||||
# Using Mintlify Components Idiomatically
|
||||
|
||||
Mintlify's custom components can transform basic documentation into engaging, scannable content that users actually want to read. Here's how to use them effectively.
|
||||
|
||||
## Visual Content with Frames
|
||||
|
||||
Videos and images should be wrapped in `<Frame>` components rather than using raw HTML or markdown. This creates consistent styling and proper responsive behavior.
|
||||
|
||||
For videos, embed them directly rather than linking externally. Users are much more likely to watch a 30-second demonstration than click through to another platform:
|
||||
|
||||
```jsx
|
||||
<Frame>
|
||||
<iframe
|
||||
style={{ width: "100%", aspectRatio: "16/9" }}
|
||||
src="https://www.youtube.com/embed/your-video-id"
|
||||
title="Feature demonstration"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
/>
|
||||
</Frame>
|
||||
```
|
||||
|
||||
Screenshots work similarly - the frame provides visual polish and consistency:
|
||||
|
||||
```jsx
|
||||
<Frame>
|
||||
<img src="/path/to/screenshot.png" alt="Descriptive alt text" />
|
||||
</Frame>
|
||||
```
|
||||
|
||||
## Cards for Navigation and Overview
|
||||
|
||||
Cards excel at creating scannable overviews that link to detailed documentation. They're perfect for feature listings, getting started guides, or any section where users need to choose their path.
|
||||
|
||||
Use the two-column layout for related features:
|
||||
|
||||
```jsx
|
||||
<Columns cols={2}>
|
||||
<Card title="Feature Name" icon="relevant-icon" href="/link/to/docs">
|
||||
Brief description that explains what this feature does and why someone would use it.
|
||||
</Card>
|
||||
|
||||
<Card title="Related Feature" icon="another-icon" href="/another/link">
|
||||
Another concise explanation that helps users understand the value proposition.
|
||||
</Card>
|
||||
</Columns>
|
||||
```
|
||||
|
||||
The key is writing card descriptions that are informative enough to help users decide whether to click through, but concise enough to scan quickly. Each card should answer "what does this do?" and "why would I need this?"
|
||||
|
||||
## Tips and Notes for Context
|
||||
|
||||
Use `<Tip>` components for helpful information that enhances the main content without cluttering it:
|
||||
|
||||
```jsx
|
||||
<Tip>
|
||||
Pro tip: You can combine multiple @ mentions in a single message to give Cline
|
||||
comprehensive context about your issue.
|
||||
</Tip>
|
||||
```
|
||||
|
||||
`<Note>` components work well for important caveats or technical limitations:
|
||||
|
||||
```jsx
|
||||
<Note>
|
||||
Due to VS Code limitations, some features require specific settings to work properly.
|
||||
</Note>
|
||||
```
|
||||
|
||||
`<Info>` is also cool:
|
||||
|
||||
<Info>
|
||||
**Quick Fix**: If you're experiencing terminal issues, try switching to a simpler shell like `bash` in the Cline settings.
|
||||
This resolves 90% of terminal integration problems.
|
||||
</Info>
|
||||
|
||||
**Never** fall into that awful **Bold Text** - description pattern that we specifically identified as bad writing. The content should flow naturally as connected thoughts rather than feeling like a templated AI response with forced formatting.
|
||||
|
||||
|
||||
## When to Use Bullet Points and Numbered Lists Strategically
|
||||
|
||||
Bullet points serve functional purposes - use them for:
|
||||
|
||||
**Sequential actions or troubleshooting steps** where users need to follow a specific order:
|
||||
1. Install the extension
|
||||
2. Restart VSCode
|
||||
3. Check the settings panel
|
||||
|
||||
**Lists of related options** where users need to choose one approach:
|
||||
- Try PowerShell 7 for the most reliable experience
|
||||
- Switch to Command Prompt if you're still having issues
|
||||
- Use WSL Bash for Linux compatibility
|
||||
|
||||
**Quick reference items** that users might need to scan quickly when problem-solving.
|
||||
|
||||
**Improving Visual Hierarchy** when there's a wall of text - that's a good time to introduce bullet points or numbered lists.
|
||||
|
||||
Each bulleted item or numbered list should be a discrete action or piece of information that benefits from being visually separated. This is a key weapon you can employ when going for that artwork experience I mentioned earlier.
|
||||
|
||||
<good_example_of_bullet_points>
|
||||
## Finding and Configuring Terminal Settings
|
||||
|
||||
You can access Cline's terminal settings by clicking the settings icon in the Cline sidebar, then navigating to the Terminal section. These settings control how Cline interacts with your system's terminal.
|
||||
|
||||
- The **Default Terminal Profile** setting determines which shell Cline uses for executing commands. If you're experiencing issues, this is usually the first thing to change. I personally keep this set to `bash` on all my systems because it's the most reliable option, even though I use `zsh` for my regular terminal work.
|
||||
|
||||
- **Shell Integration Timeout** controls how long Cline waits for the terminal to become ready. The default is 4 seconds, but if you have a heavy shell configuration (lots of plugins, slow startup scripts), you might need to increase this to 10 or even 15 seconds. I've found that WSL environments and SSH connections often need longer timeouts.
|
||||
|
||||
- The **Enable Aggressive Terminal Reuse** setting determines whether Cline tries to reuse existing terminals even when they're not in the correct directory. When this causes problems (commands running in the wrong directory, virtual environment issues), disabling it creates more terminal instances but ensures each command runs in a clean state.
|
||||
|
||||
- **Terminal Output Line Limit** sets how many lines of output Cline will read from commands. The default of 500 lines works for most cases, but you might need to adjust it. For verbose build outputs, increase it. For commands with progress bars that spam thousands of lines, decrease it to around 100 to avoid consuming too many tokens.
|
||||
</good_example_of_bullet_points>
|
||||
|
||||
## Write Like a Human, Not an AI
|
||||
|
||||
Keep it short and direct. If you can say something in fewer words, do it. Long explanations often confuse more than they help.
|
||||
|
||||
Use normal sentence lengths. Mix short and long sentences naturally, like you would when talking to someone. Avoid those meandering compound sentences that go on forever.
|
||||
|
||||
Cut the corporate speak. Instead of "utilize," say "use." Instead of "in order to," just say "to." Write like you're explaining something to a colleague, not drafting a press release.
|
||||
|
||||
Don't over-explain obvious things. If users are reading documentation, they probably understand basic concepts. Respect their intelligence.
|
||||
|
||||
## Never use em dashes or emojis
|
||||
|
||||
That's pretty much it. Never use them. Only AI writes with em dashes or emojis.
|
||||
|
||||
# Anthropomorphizing Cline
|
||||
|
||||
When referring to Cline, always call him a "him" not an "it".
|
||||
|
||||
Bad example:
|
||||
- When Cline can’t execute commands or read their output, you lose access to one of its most powerful capabilities.
|
||||
|
||||
Good Example:
|
||||
|
||||
- When Cline can’t execute commands or read their output, you lose access to one of his most powerful capabilities.
|
||||
|
||||
# Using "I" when sharing your workflow
|
||||
|
||||
Adding a personal touch goes a long way. There are great examples in the docs currently where I use "I" to share how I personally use cline, from dev to dev. It's a great technique.
|
||||
|
||||
# Crosslinking relevant documentation pages
|
||||
|
||||
Make sure you crosslink when you're done writing the docs. If there are relevant docs, just link to them.
|
||||
|
||||
# Brevity is the soul of wit
|
||||
|
||||
Don't ramble if you don't need to. Use bullet points and numbered lists. Keep things easy to read.
|
||||
|
||||
<bad_example>
|
||||
|
||||
When Cline can't execute commands or read their output, you lose access to one of his most powerful capabilities. Terminal integration problems are frustrating, but they're usually fixable with a few simple changes.
|
||||
|
||||
## The Most Common Problem: Shell Integration Issues
|
||||
|
||||
If you're seeing "Shell integration unavailable" or Cline isn't getting command output, the issue is almost always your shell configuration. Complex shell setups with custom prompts, plugins, and fancy configurations can interfere with VSCode's terminal integration.
|
||||
|
||||
**Switch to bash first.** This fixes the problem 90% of the time. Navigate to Cline Settings → Terminal → Default Terminal Profile and select "bash" from the dropdown. Restart VSCode after making this change.
|
||||
|
||||
Still having issues? Try increasing the shell integration timeout. Go to Cline Settings → Terminal → Shell Integration Timeout and change it from 4 seconds to 10 seconds. Heavy shell configurations need more time to initialize properly.
|
||||
|
||||
If commands are running in the wrong directories or you're seeing weird behavior, disable aggressive terminal reuse. In Cline Settings → Terminal, uncheck "Enable aggressive terminal reuse." This creates more terminal instances but ensures each command runs in a clean environment.
|
||||
|
||||
|
||||
</bad_exaxmple>
|
||||
|
||||
The first part is total filler, useless to any serious developer. You can tell it's written by a non technical person that doesn't value clean, straightforward information.
|
||||
|
||||
<good_example>
|
||||
## Shell Integration Issues
|
||||
|
||||
If you're seeing "Shell integration unavailable" or Cline can't read command output, your shell configuration is interfering with VSCode's terminal integration.
|
||||
|
||||
**Switch to bash first.** Go to Cline Settings → Terminal → Default Terminal Profile and select "bash." This fixes 90% of problems.
|
||||
|
||||
Still broken? Try these:
|
||||
- Increase shell integration timeout to 10 seconds in Cline Settings → Terminal
|
||||
- Disable "aggressive terminal reuse" if commands run in wrong directories
|
||||
- Restart VSCode after making changes
|
||||
</good_example>
|
||||
|
||||
The good version cuts straight to the problem and solution. No hand-holding, no emotional language about frustration, just the facts: what's wrong, how to fix it, what to try next. Respects that developers want information, not sympathy.RetryClaude can make mistakes. Please double-check responses.
|
||||
|
||||
ALWAYS consider your audience. And your audience is devs who don't want their time wasted. Give them the info. I cannot stress this enough. Use bullet points and numbered lists. Prose is good, but every word should actually mean something to the dev reading it.
|
||||
|
||||
# Lastly, before you start writing docs
|
||||
|
||||
1. Internalize these guidelines. I mean it.
|
||||
|
||||
2. Read `docs/docs.json` and get an understanding of the structure of the docs. This will come in handly at the end when you're doing a final pass so you can cross link to docs where relevant.
|
||||
|
||||
3. Read some good examples that I personally wrote and am proud of:
|
||||
|
||||
- docs/features/slash-commands/workflows.mdx
|
||||
- docs/features/slash-commands/new-task.mdx
|
||||
- docs/features/at-mentions/overview.mdx
|
||||
- docs/features/drag-and-drop.mdx
|
||||
|
||||
4. If the user specifies any other instructions make sure you follow them.
|
||||
@@ -56,29 +56,6 @@ jobs:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
# Cache VS Code installation
|
||||
- name: Cache VS Code
|
||||
uses: actions/cache@v4
|
||||
id: vscode-cache
|
||||
with:
|
||||
path: .vscode-test
|
||||
key: vscode-${{ runner.os }}-stable-${{ hashFiles('.vscode-test.mjs', 'package.json') }}
|
||||
restore-keys: |
|
||||
vscode-${{ runner.os }}-stable-
|
||||
|
||||
# Cache Playwright browsers
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
~/Library/Caches/ms-playwright
|
||||
~/AppData/Local/ms-playwright
|
||||
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
playwright-browsers-${{ runner.os }}-
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
@@ -91,14 +68,17 @@ jobs:
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
# Run optimized E2E tests (eliminates redundant builds)
|
||||
# Build the extension before running tests
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
- name: Run E2E tests - Linux
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: xvfb-run -a npm run test:e2e:optimal
|
||||
run: xvfb-run -a npm run test:e2e
|
||||
|
||||
- name: Run E2E tests - Non-Linux
|
||||
if: matrix.runner != 'ubuntu'
|
||||
run: npm run test:e2e:optimal
|
||||
run: npm run test:e2e
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() }}
|
||||
|
||||
@@ -94,7 +94,6 @@ jobs:
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
run: |
|
||||
# Required to generate the .vsix
|
||||
vsce package --out "cline-${{ steps.get_version.outputs.version }}.vsix"
|
||||
|
||||
+7
-1
@@ -22,14 +22,20 @@ coverage
|
||||
|
||||
*evals.env
|
||||
|
||||
## Generated files ##
|
||||
# Generated files
|
||||
src/generated/
|
||||
# Core
|
||||
src/core/controller/*/methods.ts
|
||||
src/core/controller/*/index.ts
|
||||
src/core/controller/grpc-service-config.ts
|
||||
# Shared
|
||||
src/shared/proto/*.ts
|
||||
src/shared/proto/host/*.ts
|
||||
# Webview
|
||||
webview-ui/src/services/grpc-client.ts
|
||||
# Host bridge
|
||||
src/hosts/vscode/client/host-grpc-client.ts
|
||||
src/standalone/server-setup.ts
|
||||
|
||||
# E2E Tests
|
||||
test-results
|
||||
|
||||
+1
-3
@@ -5,6 +5,4 @@ webview-ui/build/
|
||||
package-lock.json
|
||||
src/core/prompts/system.ts
|
||||
src/core/prompts/model_prompts/claude4.ts
|
||||
evals/
|
||||
docs/
|
||||
out/
|
||||
evals/
|
||||
Vendored
+2
-4
@@ -14,8 +14,7 @@
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "production"
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -38,8 +37,7 @@
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"TEMP_PROFILE": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "production"
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.19.7]
|
||||
|
||||
- Add Hugging Face as a new API provider with support for their inference API models
|
||||
- Improve Claude Code error messages with better guidance for common setup issues (Thanks @BarreiroT!)
|
||||
- Fix authentication sync issues when using multiple VSCode windows
|
||||
|
||||
## [3.19.6]
|
||||
|
||||
- Improve Kimi K2 model provider routing with additional provider options for better availability and performance
|
||||
- Fixed terminal bug where Cline failed to capture output of certain fast-running commands
|
||||
- Fixed bug with increasing auto approved number of requests not resetting the counter mid-task
|
||||
|
||||
## [3.19.5]
|
||||
|
||||
- Add Groq as a new API provider with support for all Groq models including Kimi-K2
|
||||
|
||||
+2
-2
@@ -61,6 +61,7 @@
|
||||
"getting-started/installing-cline",
|
||||
"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"
|
||||
@@ -159,8 +160,7 @@
|
||||
"provider-config/openai",
|
||||
"provider-config/openai-compatible",
|
||||
"provider-config/openrouter",
|
||||
"provider-config/requesty",
|
||||
"provider-config/sap-aicore"
|
||||
"provider-config/requesty"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
title: "Our Favorite Tech Stack"
|
||||
description: "A curated list of our recommended technologies and tools for building modern web applications with Cline."
|
||||
---
|
||||
|
||||
## Recommended Stack for New Cline Users (2025)
|
||||
|
||||
### Your Complete Development Environment
|
||||
|
||||
#### Development Tools
|
||||
|
||||
- **VS Code** - Your code editor, [download here](https://code.visualstudio.com/)
|
||||
- **GitHub** - Where your code lives, [sign up here](https://github.com)
|
||||
|
||||
#### Frontend
|
||||
|
||||
- **Next.js 14+** - React framework with App Router
|
||||
- **Tailwind CSS** - Beautiful styling without writing CSS
|
||||
- **TypeScript** - JavaScript, but safer and smarter
|
||||
|
||||
#### Backend
|
||||
|
||||
- **Supabase** - Your complete backend solution, [sign up with GitHub](https://supabase.com)
|
||||
- PostgreSQL database
|
||||
- Authentication
|
||||
- File storage
|
||||
- Real-time updates
|
||||
|
||||
#### Deployment
|
||||
|
||||
- **Vercel** - Where your app runs, [sign up with GitHub](https://vercel.com)
|
||||
- Automatic deployments from GitHub
|
||||
- Preview deployments for testing
|
||||
- Production-ready CDN
|
||||
|
||||
#### AI Development
|
||||
|
||||
Choose your AI assistant based on your needs:
|
||||
|
||||
| Model | Input Cost (per 1M tokens) | Output Cost (per 1M tokens) | Best For |
|
||||
| ----------------- | -------------------------- | --------------------------- | ------------------------------ |
|
||||
| Claude 3.5 Sonnet | $3.00 | $15.00 | Production apps, complex tasks |
|
||||
| DeepSeek R1 | $1.00 | $3.00 | Budget-conscious production |
|
||||
| DeepSeek V3 | $0.14 | $2.20 | Budget-conscious development |
|
||||
|
||||
#### Free Tier Benefits
|
||||
|
||||
**Vercel (Hobby)**
|
||||
|
||||
- 100 GB data transfer/month
|
||||
- 100k serverless function invocations
|
||||
- 100 MB deployment size
|
||||
- Automatic HTTPS & CI/CD
|
||||
|
||||
**Supabase (Free)**
|
||||
|
||||
- 500 MB database storage
|
||||
- 1 GB file storage
|
||||
- 50k monthly active users
|
||||
- 2M real-time messages/month
|
||||
|
||||
**GitHub (Free)**
|
||||
|
||||
- Unlimited public repositories
|
||||
- GitHub Actions CI/CD
|
||||
- Project management tools
|
||||
- Collaboration features
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Install the development essentials:
|
||||
- Follow our [Development Essentials Installation Guide](https://docs.cline.bot/getting-started/installing-dev-essentials)
|
||||
2. Set up Cline's Memory Bank:
|
||||
- Follow the [Memory Bank setup instructions](https://docs.cline.bot/prompting/cline-memory-bank)
|
||||
- Create an empty `cline_docs` folder in your project root
|
||||
- Create `projectBrief.md` in the `cline_docs` folder (see example below)
|
||||
- Tell Cline to "initialize memory bank"
|
||||
3. Add our recommended stack configuration:
|
||||
- Create `.clinerules` file (see template below)
|
||||
- Let Cline handle the rest!
|
||||
|
||||
#### Example Project Brief
|
||||
|
||||
```markdown
|
||||
# Project Brief
|
||||
|
||||
## Overview
|
||||
|
||||
Building a [type of application] that will [main purpose].
|
||||
|
||||
## Core Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
|
||||
## Target Users
|
||||
|
||||
[Describe who will use your application]
|
||||
|
||||
## Technical Preferences (optional)
|
||||
|
||||
- Any specific technologies you want to use
|
||||
- Any specific requirements or constraints
|
||||
```
|
||||
|
||||
### .clinerules Template
|
||||
|
||||
```markdown
|
||||
# Project Configuration
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Next.js 14+ with App Router
|
||||
- Tailwind CSS for styling
|
||||
- Supabase for backend
|
||||
- Vercel for deployment
|
||||
- GitHub for version control
|
||||
|
||||
## Project Structure
|
||||
|
||||
/src
|
||||
/app # Next.js App Router pages
|
||||
/components # React components
|
||||
/lib # Utility functions
|
||||
/types # TypeScript types
|
||||
/supabase
|
||||
/migrations # SQL migration files
|
||||
/seed # Seed data files
|
||||
/public # Static assets
|
||||
|
||||
## Database Migrations
|
||||
|
||||
SQL files in /supabase/migrations should:
|
||||
|
||||
- Use sequential numbering: 001, 002, etc.
|
||||
- Include descriptive names
|
||||
- Be reviewed by Cline before execution
|
||||
Example: 001_create_users_table.sql
|
||||
|
||||
## Development Workflow
|
||||
|
||||
- Cline helps write and review code changes
|
||||
- Vercel automatically deploys from main branch
|
||||
- Database migrations reviewed by Cline before execution
|
||||
|
||||
## Security
|
||||
|
||||
DO NOT read or modify:
|
||||
|
||||
- .env files
|
||||
- \*_/config/secrets._
|
||||
- Any file containing API keys or credentials
|
||||
```
|
||||
|
||||
### Learning Resources (2025)
|
||||
|
||||
Want to learn more about the technologies we're using? Here are some great resources:
|
||||
|
||||
#### Next.js and React
|
||||
|
||||
- [Official Learn Next.js Course](https://nextjs.org/learn) - Interactive tutorial
|
||||
- [NextJS App Router: Modern Web Dev in 1 Hour](https://www.youtube.com/nextjs-modern) - Quick overview
|
||||
- [Building Real-World Apps with Next.js](https://www.youtube.com/nextjs-real-world) - Practical examples
|
||||
|
||||
#### Supabase
|
||||
|
||||
- [Supabase From Scratch](https://www.udemy.com/supabase-scratch) - Comprehensive course
|
||||
- [Official Quickstart Guides](https://supabase.com/docs/guides/getting-started)
|
||||
- [Real-Time Apps with Next.js and Supabase](https://www.newline.co/courses/supabase-nextjs)
|
||||
|
||||
#### Tailwind CSS
|
||||
|
||||
- [Tailwind CSS Tutorial for Beginners](https://www.youtube.com/tailwind-2025)
|
||||
- [Official Tailwind Documentation](https://tailwindcss.com/docs)
|
||||
- Interactive course at [Scrimba Tailwind CSS Course](https://scrimba.com/learn/tailwind)
|
||||
|
||||
### Other Things to Know
|
||||
|
||||
#### Working with Git & GitHub
|
||||
|
||||
Git helps you track changes in your code and collaborate with others. Here are the essential commands you'll use:
|
||||
|
||||
**Daily Development**
|
||||
|
||||
```bash
|
||||
# Save your changes (do this often!)
|
||||
git add . # Stage all changed files
|
||||
git commit -m "Add login page" # Save changes with a clear message
|
||||
|
||||
# Share your changes
|
||||
git push origin main # Upload to GitHub
|
||||
```
|
||||
|
||||
**Common Workflow**
|
||||
|
||||
1. **Start of day**: Get latest changes
|
||||
|
||||
```bash
|
||||
git pull origin main # Download latest code
|
||||
```
|
||||
|
||||
2. **During development**: Save work regularly
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Clear message about changes"
|
||||
```
|
||||
|
||||
3. **End of day**: Share your progress
|
||||
|
||||
```bash
|
||||
git push origin main # Upload to GitHub
|
||||
```
|
||||
|
||||
**Best Practices**
|
||||
|
||||
- Commit often with clear messages
|
||||
- Pull before starting new work
|
||||
- Push completed work to share with others
|
||||
- Use `.gitignore` to avoid committing sensitive files
|
||||
|
||||
> **Tip**: Vercel automatically deploys when you push to main!
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
- Store secrets in `.env.local` for development
|
||||
- Add them to Vercel project settings for production
|
||||
- Never commit `.env` files to Git
|
||||
|
||||
#### Getting Help
|
||||
|
||||
1. Use `/help` in Cline chat for immediate assistance
|
||||
2. Check [Cline Documentation](https://docs.cline.bot)
|
||||
3. Join our [Discord Community](https://discord.gg/cline)
|
||||
4. Search GitHub issues for common problems
|
||||
|
||||
Remember: Cline is here to help at every step. Just ask for guidance or clarification when needed!
|
||||
Generated
+10118
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -4,15 +4,13 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "mintlify dev",
|
||||
"check": "mintlify broken-links",
|
||||
"rename": "mintlify rename"
|
||||
"dev": "mintlify dev"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.23"
|
||||
"mintlify": "^4.0.538"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,31 +32,6 @@ First, you'll need to install and authenticate Claude Code on your system:
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
<br />
|
||||
|
||||
<Accordion title="Windows Setup">
|
||||
On Windows, Cline supports integrating with Claude Code through WSL.
|
||||
Windows doesn't support long commands, and Claude Code only accepts the system prompt through a flag, which means we can't properly prompt Claude through Claude Code. Anthropic is [working on a workaround to streamline this](https://github.com/anthropics/claude-code/issues/3411).
|
||||
|
||||
1. **Make sure you have WSL set-up**. You can follow [this](https://code.visualstudio.com/docs/remote/wsl#_installation) guide to do it.
|
||||
|
||||
2. **Open VSCode from WSL** and verify it's properly set-up. You should see an indicator in the bottom left that says "WSL". You can find an image of the indicator [here](https://code.visualstudio.com/docs/remote/wsl#_from-the-wsl-terminal).
|
||||
|
||||
3. Install Cline within WSL and make sure it shows the following:
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline_wsl_installed_extension.webp"
|
||||
alt="Indicator that an extension is installed on WSL"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
4. Clone or [move](https://stackoverflow.com/a/42586455) your project over to WSL
|
||||
|
||||
5. Follow the [instructions on how to set up Claude Code normally](#setup), but from the WSL terminal.
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Finding your Claude Code path
|
||||
|
||||
If you're not sure where Claude Code is installed:
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
title: "SAP AI Core"
|
||||
description: "Learn how to configure and use LLM models from Generative AI Hub in SAP AI Core with Cline."
|
||||
---
|
||||
|
||||
SAP AI Core and the generative AI hub help you to integrate LLMs and AI into new business processes in a cost-efficient manner.
|
||||
|
||||
**Website:** [SAP Help Portal](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/what-is-sap-ai-core)
|
||||
|
||||
### Getting a Service Binding
|
||||
|
||||
> 💡 **Information**
|
||||
>
|
||||
> SAP AI Core, and Generative AI Hub, are offerings from SAP BTP.
|
||||
> You need an active SAP BTP contract and a existing subaccount with a SAP AI Core instance to perform these steps.
|
||||
|
||||
1. **Access:** Go to your subaccount via [BTP Cloud Cockpit](cockpit.btp.cloud.sap/cockpit)
|
||||
2. **Create a Service Binding:** Go to "Instances and Subscriptions", select your SAP AI Core service instance and click on Service Bindings > Create.
|
||||
3. **Copy the Service Binding:** Copy the service binding values.
|
||||
|
||||
### Supported Models
|
||||
|
||||
SAP AI Core supports a large and growing number of models.
|
||||
Refer to the [Generative AI Hub Supported Models page](https://me.sap.com/notes/3437766) for the complete and up-to-date list.
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "SAP AI Core" from the "API Provider" dropdown.
|
||||
3. **Enter Client Id:** Add the `.clientid` field from the service binding into the "AI Core Client Id" field.
|
||||
4. **Enter Client Secret:** Add the `.clientsecret` field from the service binding into the "AI Core Client Secret" field.
|
||||
5. **Enter Base URL:** Add the `.serviceurls.AI_API_URL` field from the service binding into the "AI Core Base URL" field.
|
||||
6. **Enter Auth URL:** Add the `.url` field from the service binding into the "AI Core Auth URL" field.
|
||||
7. **Enter Resource Group:** Add the resource group where you have your model deployments. See [Create a Deployment for a Generative AI Model](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-deployment-for-generative-ai-model-in-sap-ai-core).
|
||||
8. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
### Tips and Notes
|
||||
|
||||
- **Model Selection:** SAP AI Core offers a wide range of models. You won't be able to use the model, even if selected, if a deployment doesn't exist in the provided resource group.
|
||||
@@ -11,11 +11,8 @@ const disallowedApis = {
|
||||
"vscode.workspace.fs.stat": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.fs.writeFile": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.workspaceFolders": {
|
||||
messageId: "useHostBridgeWorkspace",
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.asRelativePath": {
|
||||
messageId: "usePathUtils",
|
||||
@@ -23,29 +20,6 @@ const disallowedApis = {
|
||||
"vscode.workspace.getWorkspaceFolder": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
"vscode.window.showTextDocument": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.applyEdit": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
// "vscode.env.openExternal": {
|
||||
// messageId: "useUtils",
|
||||
// },
|
||||
// "vscode.window.showWarningMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
"vscode.window.showOpenDialog": {
|
||||
messageId: "useHostBridgeShowMessage",
|
||||
},
|
||||
// There are too many warnings for these calls, uncomment the following
|
||||
// when the migration is finished.
|
||||
// "vscode.window.showErrorMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
// "vscode.window.showInformationMessage": {
|
||||
// messageId: "useHostBridgeShowMessage",
|
||||
// },
|
||||
}
|
||||
|
||||
module.exports = createRule({
|
||||
@@ -63,28 +37,16 @@ module.exports = createRule({
|
||||
"Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).\n" +
|
||||
"Found: {{code}}",
|
||||
useFsUtils:
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs\n" +
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs.stat.\n" +
|
||||
"Example: import { isDirectory } from '@/utils/fs' or use the file system methods from the host bridge provider.\n" +
|
||||
"Found: {{code}}",
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridgeWorkspace:
|
||||
useHostBridge:
|
||||
"Use getHostBridgeProvider().workspaceClient.getWorkspacePaths({}) instead of vscode.workspace.workspaceFolders.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridgeShowMessage:
|
||||
"Use getHostBridgeProvider().windowClient.showMessage instead of the vscode.window.showMessage.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridge:
|
||||
"Use the host bridge instead of calling vscode APIs directly.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
useUtils:
|
||||
"Use utilities in @/utils instead of calling vscode APIs directly.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of direct VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
},
|
||||
schema: [],
|
||||
@@ -92,10 +54,17 @@ module.exports = createRule({
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
const filename = context.filename
|
||||
const isGrpcClientBase = path.basename(filename) === "grpc-client-base.ts"
|
||||
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
const isExceptionDirectory = filename.includes("/src/hosts/vscode/") || filename.includes("/standalone/runtime-files/")
|
||||
|
||||
// Pattern for checking memberExpressions like vscode.workspace.fs.stat
|
||||
function checkMemberExpression(node) {
|
||||
if (isExcluded(context.filename)) {
|
||||
// Skip if this file is being excluded.
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -174,20 +143,6 @@ module.exports = createRule({
|
||||
})
|
||||
}
|
||||
|
||||
function isExcluded(filename) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
if (path.basename(filename) === "grpc-client-base.ts") {
|
||||
return true
|
||||
}
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
if (filename.includes("/src/hosts/vscode/")) {
|
||||
return true
|
||||
}
|
||||
if (filename.includes("/standalone/runtime-files/")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Detect basic member expressions (e.g., vscode.postMessage)
|
||||
MemberExpression(node) {
|
||||
@@ -197,7 +152,7 @@ module.exports = createRule({
|
||||
// Detect property access through destructuring
|
||||
VariableDeclarator(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isExcluded(context.filename)) {
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -925,7 +925,7 @@ async function main() {
|
||||
.option("--max-attempts-per-case <number>", "Maximum total attempts per test case (default: 10x valid attempts)")
|
||||
.option("--max-cases <number>", "Maximum number of test cases to run (limits total cases loaded)")
|
||||
.option("--parsing-function <name>", "The parsing function to use", "parseAssistantMessageV2")
|
||||
.option("--diff-edit-function <name>", "The diff editing function to use", "diff-06-26-25")
|
||||
.option("--diff-edit-function <name>", "The diff editing function to use", "diff-06-25-25")
|
||||
.option("--thinking-budget <tokens>", "Set the thinking tokens budget", "0")
|
||||
.option("--parallel", "Run tests in parallel", false)
|
||||
.option("--replay", "Run evaluation from a pre-recorded LLM output, skipping the API call", false)
|
||||
|
||||
@@ -937,18 +937,8 @@ def main():
|
||||
|
||||
# Build current URL
|
||||
# Dynamically derive the base URL
|
||||
try:
|
||||
# For older Streamlit versions
|
||||
server_address = st.server.server_address
|
||||
server_port = st.server.server_port
|
||||
except AttributeError:
|
||||
# Fallback for newer Streamlit versions where st.server is removed
|
||||
# We can't reliably get the server address/port from within the script anymore.
|
||||
# We'll default to localhost and the default port.
|
||||
# The user can see the correct network URL in the terminal.
|
||||
server_address = "localhost"
|
||||
server_port = 8501
|
||||
|
||||
server_address = st.server.server_address if hasattr(st.server, 'server_address') else "localhost"
|
||||
server_port = st.server.server_port if hasattr(st.server, 'server_port') else "8501"
|
||||
base_url = f"http://{server_address}:{server_port}"
|
||||
current_url = f"{base_url}/?run_id={st.session_state.selected_run_id}"
|
||||
if st.session_state.drill_down_model:
|
||||
|
||||
Generated
+12557
-1031
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -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.19.7",
|
||||
"version": "3.19.5",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -347,7 +347,6 @@
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"e2e": "playwright test -c playwright.config.ts",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"test:e2e:optimal": "vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"install:all": "npm install && cd webview-ui && npm install",
|
||||
"dev:webview": "cd webview-ui && npm run dev",
|
||||
"build:webview": "cd webview-ui && npm run build",
|
||||
@@ -357,9 +356,9 @@
|
||||
"prepare": "husky",
|
||||
"changeset": "changeset",
|
||||
"version-packages": "changeset version",
|
||||
"docs": "cd docs && npm run dev",
|
||||
"docs:check-links": "cd docs && npm run check",
|
||||
"docs:rename-file": "cd docs && npm run rename",
|
||||
"docs": "cd docs && mintlify dev",
|
||||
"docs:check-links": "cd docs && mintlify broken-links",
|
||||
"docs:rename-file": "cd docs && mintlify rename",
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -397,6 +396,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
"minimatch": "^3.0.3",
|
||||
"mintlify": "^4.0.515",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"protoc-gen-ts": "^0.8.7",
|
||||
|
||||
+2
-40
@@ -10,16 +10,7 @@ import "common.proto";
|
||||
service DiffService {
|
||||
// Open the diff view/editor.
|
||||
rpc openDiff(OpenDiffRequest) returns (OpenDiffResponse);
|
||||
// Get the contents of the diff view.
|
||||
rpc getDocumentText(GetDocumentTextRequest) returns (GetDocumentTextResponse);
|
||||
// Replace a text selection in the diff.
|
||||
rpc replaceText(ReplaceTextRequest) returns (ReplaceTextResponse);
|
||||
// Truncate the diff document.
|
||||
rpc truncateDocument(TruncateDocumentRequest) returns (TruncateDocumentResponse);
|
||||
// Save the diff document.
|
||||
rpc saveDocument(SaveDocumentRequest) returns (SaveDocumentResponse);
|
||||
// Close the diff editor UI.
|
||||
rpc closeDiff(CloseDiffRequest) returns (CloseDiffResponse);
|
||||
}
|
||||
|
||||
message OpenDiffRequest {
|
||||
@@ -35,15 +26,6 @@ message OpenDiffResponse {
|
||||
optional string diff_id = 1;
|
||||
}
|
||||
|
||||
message GetDocumentTextRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
|
||||
message GetDocumentTextResponse {
|
||||
optional string content = 1;
|
||||
}
|
||||
|
||||
message ReplaceTextRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
@@ -52,26 +34,6 @@ message ReplaceTextRequest {
|
||||
optional int32 end_line = 5;
|
||||
}
|
||||
|
||||
message ReplaceTextResponse {}
|
||||
|
||||
message TruncateDocumentRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
optional int32 end_line = 5;
|
||||
message ReplaceTextResponse {
|
||||
// TBD
|
||||
}
|
||||
|
||||
message TruncateDocumentResponse {}
|
||||
|
||||
message CloseDiffRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
|
||||
message CloseDiffResponse {}
|
||||
|
||||
message SaveDocumentRequest {
|
||||
optional cline.Metadata metadata = 1;
|
||||
optional string diff_id = 2;
|
||||
}
|
||||
|
||||
message SaveDocumentResponse {}
|
||||
|
||||
@@ -6,10 +6,6 @@ option java_multiple_files = true;
|
||||
|
||||
import "common.proto";
|
||||
|
||||
/**
|
||||
* The watch service is only here as example of a streaming rpc in the host bridge.
|
||||
* This being replaced with a native JS file watcher.
|
||||
*/
|
||||
// WatchService provides methods for watching files in the IDE
|
||||
service WatchService {
|
||||
// Subscribe to file changes
|
||||
|
||||
+81
-118
@@ -15,8 +15,6 @@ service ModelsService {
|
||||
rpc getVsCodeLmModels(EmptyRequest) returns (VsCodeLmModelsArray);
|
||||
// Refreshes and returns OpenRouter models
|
||||
rpc refreshOpenRouterModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns Hugging Face models
|
||||
rpc refreshHuggingFaceModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns OpenAI models
|
||||
rpc refreshOpenAiModels(OpenAiModelsRequest) returns (StringArray);
|
||||
// Refreshes and returns Requesty models
|
||||
@@ -128,7 +126,6 @@ enum ApiProvider {
|
||||
SAPAICORE = 25;
|
||||
CLAUDE_CODE = 26;
|
||||
MOONSHOT = 27;
|
||||
HUGGINGFACE = 28;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
@@ -168,119 +165,85 @@ message LiteLLMModelInfo {
|
||||
|
||||
// Main ApiConfiguration message
|
||||
message ModelsApiConfiguration {
|
||||
// Global configuration fields (not mode-specific)
|
||||
optional string api_key = 1;
|
||||
optional string cline_api_key = 2;
|
||||
optional string task_id = 3;
|
||||
optional string lite_llm_base_url = 4;
|
||||
optional string lite_llm_api_key = 5;
|
||||
optional bool lite_llm_use_prompt_cache = 6;
|
||||
map<string, string> open_ai_headers = 7;
|
||||
optional string anthropic_base_url = 8;
|
||||
optional string open_router_api_key = 9;
|
||||
optional string open_router_provider_sorting = 10;
|
||||
optional string aws_access_key = 11;
|
||||
optional string aws_secret_key = 12;
|
||||
optional string aws_session_token = 13;
|
||||
optional string aws_region = 14;
|
||||
optional bool aws_use_cross_region_inference = 15;
|
||||
optional bool aws_bedrock_use_prompt_cache = 16;
|
||||
optional bool aws_use_profile = 17;
|
||||
optional string aws_profile = 18;
|
||||
optional string aws_bedrock_endpoint = 19;
|
||||
optional string claude_code_path = 20;
|
||||
optional string vertex_project_id = 21;
|
||||
optional string vertex_region = 22;
|
||||
optional string open_ai_base_url = 23;
|
||||
optional string open_ai_api_key = 24;
|
||||
optional string ollama_base_url = 25;
|
||||
optional string ollama_api_options_ctx_num = 26;
|
||||
optional string lm_studio_base_url = 27;
|
||||
optional string gemini_api_key = 28;
|
||||
optional string gemini_base_url = 29;
|
||||
optional string open_ai_native_api_key = 30;
|
||||
optional string deep_seek_api_key = 31;
|
||||
optional string requesty_api_key = 32;
|
||||
optional string together_api_key = 33;
|
||||
optional string fireworks_api_key = 34;
|
||||
optional int32 fireworks_model_max_completion_tokens = 35;
|
||||
optional int32 fireworks_model_max_tokens = 36;
|
||||
optional string qwen_api_key = 37;
|
||||
optional string doubao_api_key = 38;
|
||||
optional string mistral_api_key = 39;
|
||||
optional string azure_api_version = 40;
|
||||
optional string qwen_api_line = 41;
|
||||
optional string nebius_api_key = 42;
|
||||
optional string asksage_api_url = 43;
|
||||
optional string asksage_api_key = 44;
|
||||
optional string xai_api_key = 45;
|
||||
optional string sambanova_api_key = 46;
|
||||
optional string cerebras_api_key = 47;
|
||||
optional int32 request_timeout_ms = 48;
|
||||
optional string sap_ai_core_client_id = 49;
|
||||
optional string sap_ai_core_client_secret = 50;
|
||||
optional string sap_ai_resource_group = 51;
|
||||
optional string sap_ai_core_token_url = 52;
|
||||
optional string sap_ai_core_base_url = 53;
|
||||
optional string moonshot_api_key = 54;
|
||||
optional string moonshot_api_line = 55;
|
||||
optional string aws_authentication = 56;
|
||||
optional string aws_bedrock_api_key = 57;
|
||||
optional string cline_account_id = 58;
|
||||
optional string groq_api_key = 59;
|
||||
optional string hugging_face_api_key = 60;
|
||||
|
||||
// Plan mode configurations
|
||||
optional ApiProvider plan_mode_api_provider = 100;
|
||||
optional string plan_mode_api_model_id = 101;
|
||||
optional int32 plan_mode_thinking_budget_tokens = 102;
|
||||
optional string plan_mode_reasoning_effort = 103;
|
||||
optional LanguageModelChatSelector plan_mode_vs_code_lm_model_selector = 104;
|
||||
optional bool plan_mode_aws_bedrock_custom_selected = 105;
|
||||
optional string plan_mode_aws_bedrock_custom_model_base_id = 106;
|
||||
optional string plan_mode_open_router_model_id = 107;
|
||||
optional OpenRouterModelInfo plan_mode_open_router_model_info = 108;
|
||||
optional string plan_mode_open_ai_model_id = 109;
|
||||
optional OpenAiCompatibleModelInfo plan_mode_open_ai_model_info = 110;
|
||||
optional string plan_mode_ollama_model_id = 111;
|
||||
optional string plan_mode_lm_studio_model_id = 112;
|
||||
optional string plan_mode_lite_llm_model_id = 113;
|
||||
optional LiteLLMModelInfo plan_mode_lite_llm_model_info = 114;
|
||||
optional string plan_mode_requesty_model_id = 115;
|
||||
optional OpenRouterModelInfo plan_mode_requesty_model_info = 116;
|
||||
optional string plan_mode_together_model_id = 117;
|
||||
optional string plan_mode_fireworks_model_id = 118;
|
||||
optional string plan_mode_sap_ai_core_model_id = 119;
|
||||
optional string plan_mode_groq_model_id = 120;
|
||||
optional OpenRouterModelInfo plan_mode_groq_model_info = 121;
|
||||
optional string plan_mode_hugging_face_model_id = 122;
|
||||
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 123;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
optional string act_mode_api_model_id = 201;
|
||||
optional int32 act_mode_thinking_budget_tokens = 202;
|
||||
optional string act_mode_reasoning_effort = 203;
|
||||
optional LanguageModelChatSelector act_mode_vs_code_lm_model_selector = 204;
|
||||
optional bool act_mode_aws_bedrock_custom_selected = 205;
|
||||
optional string act_mode_aws_bedrock_custom_model_base_id = 206;
|
||||
optional string act_mode_open_router_model_id = 207;
|
||||
optional OpenRouterModelInfo act_mode_open_router_model_info = 208;
|
||||
optional string act_mode_open_ai_model_id = 209;
|
||||
optional OpenAiCompatibleModelInfo act_mode_open_ai_model_info = 210;
|
||||
optional string act_mode_ollama_model_id = 211;
|
||||
optional string act_mode_lm_studio_model_id = 212;
|
||||
optional string act_mode_lite_llm_model_id = 213;
|
||||
optional LiteLLMModelInfo act_mode_lite_llm_model_info = 214;
|
||||
optional string act_mode_requesty_model_id = 215;
|
||||
optional OpenRouterModelInfo act_mode_requesty_model_info = 216;
|
||||
optional string act_mode_together_model_id = 217;
|
||||
optional string act_mode_fireworks_model_id = 218;
|
||||
optional string act_mode_sap_ai_core_model_id = 219;
|
||||
optional string act_mode_groq_model_id = 220;
|
||||
optional OpenRouterModelInfo act_mode_groq_model_info = 221;
|
||||
optional string act_mode_hugging_face_model_id = 222;
|
||||
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 223;
|
||||
|
||||
repeated string favorited_model_ids = 300;
|
||||
// From ApiHandlerOptions (excluding onRetryAttempt function)
|
||||
optional string api_model_id = 1;
|
||||
optional string api_key = 2;
|
||||
optional string cline_account_id = 3;
|
||||
optional string task_id = 4;
|
||||
optional string lite_llm_base_url = 5;
|
||||
optional string lite_llm_model_id = 6;
|
||||
optional string lite_llm_api_key = 7;
|
||||
optional bool lite_llm_use_prompt_cache = 8;
|
||||
map<string, string> open_ai_headers = 9;
|
||||
optional LiteLLMModelInfo lite_llm_model_info = 10;
|
||||
optional string anthropic_base_url = 11;
|
||||
optional string open_router_api_key = 12;
|
||||
optional string open_router_model_id = 13;
|
||||
optional OpenRouterModelInfo open_router_model_info = 14;
|
||||
optional string open_router_provider_sorting = 15;
|
||||
optional string aws_access_key = 16;
|
||||
optional string aws_secret_key = 17;
|
||||
optional string aws_session_token = 18;
|
||||
optional string aws_region = 19;
|
||||
optional bool aws_use_cross_region_inference = 20;
|
||||
optional bool aws_bedrock_use_prompt_cache = 21;
|
||||
optional bool aws_use_profile = 22;
|
||||
optional string aws_profile = 23;
|
||||
optional string aws_bedrock_endpoint = 24;
|
||||
optional bool aws_bedrock_custom_selected = 25;
|
||||
optional string aws_bedrock_custom_model_base_id = 26;
|
||||
optional string vertex_project_id = 27;
|
||||
optional string vertex_region = 28;
|
||||
optional string open_ai_base_url = 29;
|
||||
optional string open_ai_api_key = 30;
|
||||
optional string open_ai_model_id = 31;
|
||||
optional OpenAiCompatibleModelInfo open_ai_model_info = 32;
|
||||
optional string ollama_model_id = 33;
|
||||
optional string ollama_base_url = 34;
|
||||
optional string ollama_api_options_ctx_num = 35;
|
||||
optional string lm_studio_model_id = 36;
|
||||
optional string lm_studio_base_url = 37;
|
||||
optional string gemini_api_key = 38;
|
||||
optional string gemini_base_url = 39;
|
||||
optional string open_ai_native_api_key = 40;
|
||||
optional string deep_seek_api_key = 41;
|
||||
optional string requesty_api_key = 42;
|
||||
optional string requesty_model_id = 43;
|
||||
optional OpenRouterModelInfo requesty_model_info = 44;
|
||||
optional string together_api_key = 45;
|
||||
optional string together_model_id = 46;
|
||||
optional string fireworks_api_key = 47;
|
||||
optional string fireworks_model_id = 48;
|
||||
optional int32 fireworks_model_max_completion_tokens = 49;
|
||||
optional int32 fireworks_model_max_tokens = 50;
|
||||
optional string qwen_api_key = 51;
|
||||
optional string doubao_api_key = 52;
|
||||
optional string mistral_api_key = 53;
|
||||
optional string azure_api_version = 54;
|
||||
optional LanguageModelChatSelector vs_code_lm_model_selector = 55;
|
||||
optional string qwen_api_line = 56;
|
||||
optional string nebius_api_key = 57;
|
||||
optional string asksage_api_url = 58;
|
||||
optional string asksage_api_key = 59;
|
||||
optional string xai_api_key = 60;
|
||||
optional int32 thinking_budget_tokens = 61;
|
||||
optional string reasoning_effort = 62;
|
||||
optional string sambanova_api_key = 63;
|
||||
optional string cerebras_api_key = 64;
|
||||
optional int32 request_timeout_ms = 65;
|
||||
optional ApiProvider api_provider = 66;
|
||||
repeated string favorited_model_ids = 67;
|
||||
optional string sap_ai_core_client_id = 68;
|
||||
optional string sap_ai_core_client_secret = 69;
|
||||
optional string sap_ai_resource_group = 70;
|
||||
optional string sap_ai_core_token_url = 71;
|
||||
optional string sap_ai_core_base_url = 72;
|
||||
optional string claude_code_path = 73;
|
||||
optional string aws_authentication = 74;
|
||||
optional string aws_bedrock_api_key = 75;
|
||||
optional string moonshot_api_key = 76;
|
||||
optional string moonshot_api_line = 77;
|
||||
optional string groq_api_key = 78;
|
||||
optional string groq_model_id = 79;
|
||||
optional OpenRouterModelInfo groq_model_info = 80;
|
||||
}
|
||||
|
||||
+115
-102
@@ -118,113 +118,126 @@ message UpdateSettingsRequest {
|
||||
|
||||
// Complete API Configuration message
|
||||
message ApiConfiguration {
|
||||
// Global configuration fields (not mode-specific)
|
||||
optional string api_key = 1; // anthropic
|
||||
optional string cline_api_key = 2;
|
||||
optional string task_id = 3;
|
||||
optional string lite_llm_base_url = 4;
|
||||
optional string lite_llm_api_key = 5;
|
||||
optional bool lite_llm_use_prompt_cache = 6;
|
||||
optional string openai_headers = 7; // JSON string
|
||||
optional string anthropic_base_url = 8;
|
||||
optional string openrouter_api_key = 9;
|
||||
optional string openrouter_provider_sorting = 10;
|
||||
optional string aws_access_key = 11;
|
||||
optional string aws_secret_key = 12;
|
||||
optional string aws_session_token = 13;
|
||||
optional string aws_region = 14;
|
||||
optional bool aws_use_cross_region_inference = 15;
|
||||
optional bool aws_bedrock_use_prompt_cache = 16;
|
||||
optional bool aws_use_profile = 17;
|
||||
optional string aws_profile = 18;
|
||||
optional string aws_bedrock_endpoint = 19;
|
||||
optional string claude_code_path = 20;
|
||||
optional string vertex_project_id = 21;
|
||||
optional string vertex_region = 22;
|
||||
optional string openai_base_url = 23;
|
||||
optional string openai_api_key = 24;
|
||||
optional string ollama_base_url = 25;
|
||||
optional string ollama_api_options_ctx_num = 26;
|
||||
optional string lm_studio_base_url = 27;
|
||||
optional string gemini_api_key = 28;
|
||||
optional string gemini_base_url = 29;
|
||||
optional string openai_native_api_key = 30;
|
||||
optional string deep_seek_api_key = 31;
|
||||
optional string requesty_api_key = 32;
|
||||
optional string together_api_key = 33;
|
||||
optional string fireworks_api_key = 34;
|
||||
optional int64 fireworks_model_max_completion_tokens = 35;
|
||||
optional int64 fireworks_model_max_tokens = 36;
|
||||
optional string qwen_api_key = 37;
|
||||
optional string doubao_api_key = 38;
|
||||
optional string mistral_api_key = 39;
|
||||
optional string azure_api_version = 40;
|
||||
optional string qwen_api_line = 41;
|
||||
optional string nebius_api_key = 42;
|
||||
optional string asksage_api_url = 43;
|
||||
optional string asksage_api_key = 44;
|
||||
optional string xai_api_key = 45;
|
||||
optional string sambanova_api_key = 46;
|
||||
optional string cerebras_api_key = 47;
|
||||
optional int64 request_timeout_ms = 48;
|
||||
optional string sap_ai_core_client_id = 49;
|
||||
optional string sap_ai_core_client_secret = 50;
|
||||
optional string sap_ai_resource_group = 51;
|
||||
optional string sap_ai_core_token_url = 52;
|
||||
optional string sap_ai_core_base_url = 53;
|
||||
optional string moonshot_api_key = 54;
|
||||
optional string moonshot_api_line = 55;
|
||||
// Core API fields
|
||||
optional string api_provider = 1;
|
||||
optional string api_model_id = 2;
|
||||
optional string api_key = 3; // anthropic
|
||||
optional string api_base_url = 4;
|
||||
|
||||
// Plan mode configurations
|
||||
optional string plan_mode_api_provider = 100;
|
||||
optional string plan_mode_api_model_id = 101;
|
||||
optional int64 plan_mode_thinking_budget_tokens = 102;
|
||||
optional string plan_mode_reasoning_effort = 103;
|
||||
optional string plan_mode_vscode_lm_model_selector = 104; // JSON string
|
||||
optional bool plan_mode_aws_bedrock_custom_selected = 105;
|
||||
optional string plan_mode_aws_bedrock_custom_model_base_id = 106;
|
||||
optional string plan_mode_openrouter_model_id = 107;
|
||||
optional string plan_mode_openrouter_model_info = 108; // JSON string
|
||||
optional string plan_mode_openai_model_id = 109;
|
||||
optional string plan_mode_openai_model_info = 110; // JSON string
|
||||
optional string plan_mode_ollama_model_id = 111;
|
||||
optional string plan_mode_lm_studio_model_id = 112;
|
||||
optional string plan_mode_lite_llm_model_id = 113;
|
||||
optional string plan_mode_lite_llm_model_info = 114; // JSON string
|
||||
optional string plan_mode_requesty_model_id = 115;
|
||||
optional string plan_mode_requesty_model_info = 116; // JSON string
|
||||
optional string plan_mode_together_model_id = 117;
|
||||
optional string plan_mode_fireworks_model_id = 118;
|
||||
optional string plan_mode_sap_ai_core_model_id = 119;
|
||||
// Provider-specific API keys
|
||||
optional string cline_account_id = 5;
|
||||
optional string openrouter_api_key = 6;
|
||||
optional string anthropic_base_url = 7;
|
||||
optional string openai_api_key = 8;
|
||||
optional string openai_native_api_key = 9;
|
||||
optional string gemini_api_key = 10;
|
||||
optional string deepseek_api_key = 11;
|
||||
optional string requesty_api_key = 12;
|
||||
optional string together_api_key = 13;
|
||||
optional string fireworks_api_key = 14;
|
||||
optional string qwen_api_key = 15;
|
||||
optional string doubao_api_key = 16;
|
||||
optional string mistral_api_key = 17;
|
||||
optional string nebius_api_key = 18;
|
||||
optional string asksage_api_key = 19;
|
||||
optional string xai_api_key = 20;
|
||||
optional string sambanova_api_key = 21;
|
||||
optional string cerebras_api_key = 22;
|
||||
|
||||
// Act mode configurations
|
||||
optional string act_mode_api_provider = 200;
|
||||
optional string act_mode_api_model_id = 201;
|
||||
optional int64 act_mode_thinking_budget_tokens = 202;
|
||||
optional string act_mode_reasoning_effort = 203;
|
||||
optional string act_mode_vscode_lm_model_selector = 204; // JSON string
|
||||
optional bool act_mode_aws_bedrock_custom_selected = 205;
|
||||
optional string act_mode_aws_bedrock_custom_model_base_id = 206;
|
||||
optional string act_mode_openrouter_model_id = 207;
|
||||
optional string act_mode_openrouter_model_info = 208; // JSON string
|
||||
optional string act_mode_openai_model_id = 209;
|
||||
optional string act_mode_openai_model_info = 210; // JSON string
|
||||
optional string act_mode_ollama_model_id = 211;
|
||||
optional string act_mode_lm_studio_model_id = 212;
|
||||
optional string act_mode_lite_llm_model_id = 213;
|
||||
optional string act_mode_lite_llm_model_info = 214; // JSON string
|
||||
optional string act_mode_requesty_model_id = 215;
|
||||
optional string act_mode_requesty_model_info = 216; // JSON string
|
||||
optional string act_mode_together_model_id = 217;
|
||||
optional string act_mode_fireworks_model_id = 218;
|
||||
optional string act_mode_sap_ai_core_model_id = 219;
|
||||
// Model IDs
|
||||
optional string openrouter_model_id = 23;
|
||||
optional string openai_model_id = 24;
|
||||
optional string anthropic_model_id = 25;
|
||||
optional string bedrock_model_id = 26;
|
||||
optional string vertex_model_id = 27;
|
||||
optional string gemini_model_id = 28;
|
||||
optional string ollama_model_id = 29;
|
||||
optional string lm_studio_model_id = 30;
|
||||
optional string litellm_model_id = 31;
|
||||
optional string requesty_model_id = 32;
|
||||
optional string together_model_id = 33;
|
||||
optional string fireworks_model_id = 34;
|
||||
|
||||
// AWS Bedrock fields
|
||||
optional bool aws_bedrock_custom_selected = 35;
|
||||
optional string aws_bedrock_custom_model_base_id = 36;
|
||||
optional string aws_access_key = 37;
|
||||
optional string aws_secret_key = 38;
|
||||
optional string aws_session_token = 39;
|
||||
optional string aws_region = 40;
|
||||
optional bool aws_use_cross_region_inference = 41;
|
||||
optional bool aws_bedrock_use_prompt_cache = 42;
|
||||
optional bool aws_use_profile = 43;
|
||||
optional string aws_profile = 44;
|
||||
optional string aws_bedrock_endpoint = 45;
|
||||
|
||||
// Vertex AI fields
|
||||
optional string vertex_project_id = 46;
|
||||
optional string vertex_region = 47;
|
||||
|
||||
// Base URLs and endpoints
|
||||
optional string openai_base_url = 48;
|
||||
optional string ollama_base_url = 49;
|
||||
optional string lm_studio_base_url = 50;
|
||||
optional string gemini_base_url = 51;
|
||||
optional string litellm_base_url = 52;
|
||||
optional string asksage_api_url = 53;
|
||||
|
||||
// LiteLLM specific fields
|
||||
optional string litellm_api_key = 54;
|
||||
optional bool litellm_use_prompt_cache = 55;
|
||||
|
||||
// Model configuration
|
||||
optional int64 thinking_budget_tokens = 56;
|
||||
optional string reasoning_effort = 57;
|
||||
optional int64 request_timeout_ms = 58;
|
||||
|
||||
// Fireworks specific
|
||||
optional int64 fireworks_model_max_completion_tokens = 59;
|
||||
optional int64 fireworks_model_max_tokens = 60;
|
||||
|
||||
// Azure specific
|
||||
optional string azure_api_version = 61;
|
||||
|
||||
// Ollama specific
|
||||
optional string ollama_api_options_ctx_num = 62;
|
||||
|
||||
// Qwen specific
|
||||
optional string qwen_api_line = 63;
|
||||
|
||||
// OpenRouter specific
|
||||
optional string openrouter_provider_sorting = 64;
|
||||
|
||||
// VSCode LM (stored as JSON string due to complex type)
|
||||
optional string vscode_lm_model_selector = 65;
|
||||
|
||||
// Model info objects (stored as JSON strings)
|
||||
optional string openrouter_model_info = 66;
|
||||
optional string openai_model_info = 67;
|
||||
optional string requesty_model_info = 68;
|
||||
optional string litellm_model_info = 69;
|
||||
|
||||
// OpenAI headers (stored as JSON string)
|
||||
optional string openai_headers = 70;
|
||||
|
||||
// Favorited model IDs
|
||||
repeated string favorited_model_ids = 300;
|
||||
repeated string favorited_model_ids = 71;
|
||||
|
||||
// SAP AI Core specific
|
||||
optional string sap_ai_core_client_id = 72;
|
||||
optional string sap_ai_core_client_secret = 73;
|
||||
optional string sap_ai_core_base_url = 74;
|
||||
optional string sap_ai_core_token_url = 75;
|
||||
optional string sap_ai_resource_group = 76;
|
||||
|
||||
// Claude Code specific
|
||||
optional string claude_code_path = 77;
|
||||
|
||||
// Extension fields for Bedrock Api Keys
|
||||
optional string aws_authentication = 301;
|
||||
optional string aws_bedrock_api_key = 302;
|
||||
optional string aws_authentication = 78;
|
||||
optional string aws_bedrock_api_key = 79;
|
||||
|
||||
optional string cline_account_id = 303;
|
||||
// Moonshot
|
||||
optional string moonshot_api_key = 80;
|
||||
optional string moonshot_api_line = 81;
|
||||
}
|
||||
|
||||
+176
-13
@@ -9,7 +9,7 @@ import chalk from "chalk"
|
||||
import os from "os"
|
||||
|
||||
import { createRequire } from "module"
|
||||
import { serviceNameMap } from "./build-proto-config.mjs"
|
||||
import { serviceNameMap, hostServiceNameMap } from "./build-proto-config.mjs"
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc")
|
||||
@@ -42,8 +42,6 @@ const serviceDirs = Object.keys(serviceNameMap).map((serviceKey) => path.join("s
|
||||
async function main() {
|
||||
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
|
||||
|
||||
await cleanup()
|
||||
|
||||
// Check for Apple Silicon compatibility before proceeding
|
||||
checkAppleSiliconCompatibility()
|
||||
|
||||
@@ -52,6 +50,8 @@ async function main() {
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
}
|
||||
|
||||
await cleanup()
|
||||
|
||||
// Check for missing proto files for services in serviceNameMap
|
||||
await ensureProtoFilesExist()
|
||||
|
||||
@@ -88,6 +88,9 @@ async function main() {
|
||||
await generateProtoBusMethodRegistrations()
|
||||
await generateProtoBusGrpcClientConfig()
|
||||
|
||||
await generateHostBridgeServiceConfig()
|
||||
await generateHostBridgeMethodRegistrations()
|
||||
|
||||
console.log(chalk.bold.blue("Finished Protocol Buffer code generation."))
|
||||
}
|
||||
|
||||
@@ -417,16 +420,180 @@ service ${serviceClassName} {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate method registration files for host services
|
||||
*/
|
||||
async function generateHostBridgeMethodRegistrations() {
|
||||
log_verbose(chalk.cyan("Generating host method registration files..."))
|
||||
// Host service directories derived from imported hostServiceNameMap
|
||||
const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) =>
|
||||
path.join("src/hosts/vscode/hostbridge", serviceKey),
|
||||
)
|
||||
|
||||
// Parse proto files for streaming methods
|
||||
const hostProtoFiles = await globby("*.proto", { cwd: path.join(PROTO_DIR, "host") })
|
||||
const streamingMethodsMap = await parseProtoForStreamingMethods(hostProtoFiles, path.join(PROTO_DIR, "host"))
|
||||
|
||||
for (const serviceDir of hostServiceDirs) {
|
||||
const serviceName = path.basename(serviceDir)
|
||||
const fullServiceName = hostServiceNameMap[serviceName]
|
||||
const streamingMethods = streamingMethodsMap.get(fullServiceName) || []
|
||||
const outputDir = path.join("src/generated/hosts/vscode/hostbridge", serviceName)
|
||||
|
||||
log_verbose(chalk.cyan(`Generating method registrations for host ${serviceName}...`))
|
||||
|
||||
// Get all TypeScript files in the service directory
|
||||
const files = await globby("*.ts", { cwd: serviceDir })
|
||||
|
||||
// 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
|
||||
// Generated ${SCRIPT_NAME}
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"\n`
|
||||
|
||||
// Import implementations directly
|
||||
for (const file of implementationFiles) {
|
||||
const baseName = path.basename(file, ".ts")
|
||||
methodsContent += `import { ${baseName} } from "@hosts/vscode/hostbridge/${serviceName}/${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`
|
||||
}
|
||||
|
||||
// Add registration function
|
||||
methodsContent += `\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`
|
||||
}
|
||||
}
|
||||
|
||||
// Close the function
|
||||
methodsContent += `}`
|
||||
|
||||
// Write the methods.ts file
|
||||
const registryFile = path.join(outputDir, "methods.ts")
|
||||
await writeFileWithMkdirs(registryFile, methodsContent)
|
||||
log_verbose(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 ${SCRIPT_NAME}
|
||||
|
||||
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "@hosts/vscode/hostbridge-grpc-service"
|
||||
import { StreamingResponseHandler } from "@hosts/vscode/hostbridge-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
|
||||
const indexFile = path.join(outputDir, "index.ts")
|
||||
await writeFileWithMkdirs(indexFile, indexContent)
|
||||
log_verbose(chalk.green(`Generated ${indexFile}`))
|
||||
}
|
||||
|
||||
log_verbose(chalk.green("Host method registration files generated successfully."))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a service configuration file for host services
|
||||
*/
|
||||
async function generateHostBridgeServiceConfig() {
|
||||
log_verbose(chalk.cyan("Generating host service configuration file..."))
|
||||
|
||||
const serviceImports = []
|
||||
const serviceConfigs = []
|
||||
|
||||
// Add all services from the hostServiceNameMap
|
||||
for (const [dirName, fullServiceName] of Object.entries(hostServiceNameMap)) {
|
||||
const capitalizedName = dirName.charAt(0).toUpperCase() + dirName.slice(1)
|
||||
serviceImports.push(
|
||||
`import { handle${capitalizedName}ServiceRequest, handle${capitalizedName}ServiceStreamingRequest } from "@generated/hosts/vscode/hostbridge/${dirName}/index"`,
|
||||
)
|
||||
serviceConfigs.push(`
|
||||
"${fullServiceName}": {
|
||||
requestHandler: handle${capitalizedName}ServiceRequest,
|
||||
streamingHandler: handle${capitalizedName}ServiceStreamingRequest
|
||||
}`)
|
||||
}
|
||||
|
||||
const content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
|
||||
import { StreamingResponseHandler } from "@/hosts/vscode/hostbridge-grpc-handler"
|
||||
${serviceImports.join("\n")}
|
||||
|
||||
/**
|
||||
* Configuration for a host service handler
|
||||
*/
|
||||
export interface HostServiceHandlerConfig {
|
||||
requestHandler: (method: string, message: any) => Promise<any>;
|
||||
streamingHandler: (method: string, message: any, responseStream: StreamingResponseHandler, requestId?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of host service names to their handler configurations
|
||||
*/
|
||||
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {${serviceConfigs.join(",")}
|
||||
};`
|
||||
|
||||
const filePath = "src/generated/hosts/vscode/hostbridge-grpc-service-config.ts"
|
||||
await writeFileWithMkdirs(filePath, content)
|
||||
log_verbose(chalk.green(`Generated host service configuration at ${filePath}`))
|
||||
}
|
||||
|
||||
async function cleanup() {
|
||||
// Clean up existing generated files
|
||||
log_verbose(chalk.cyan("Cleaning up existing generated TypeScript files..."))
|
||||
await rmrf(TS_OUT_DIR)
|
||||
await rmrf("src/generated")
|
||||
const existingFiles = await globby("**/*.ts", { cwd: TS_OUT_DIR })
|
||||
for (const file of existingFiles) {
|
||||
await fs.unlink(path.join(TS_OUT_DIR, file))
|
||||
}
|
||||
await rmdir("src/generated")
|
||||
|
||||
// Clean up generated files that were moved.
|
||||
await rmrf("src/standalone/services/host-grpc-client.ts")
|
||||
await rmrf("src/standalone/server-setup.ts")
|
||||
await rmrf("src/hosts/vscode/host-grpc-service-config.ts")
|
||||
await fs.rm("src/standalone/services/host-grpc-client.ts", { force: true })
|
||||
await rmdir("src/standalone/services")
|
||||
await fs.rm("hosts/vscode", { force: true, recursive: true })
|
||||
await rmdir("hosts")
|
||||
|
||||
await fs.rm("src/standalone/server-setup.ts", { force: true })
|
||||
await fs.rm("src/hosts/vscode/host-grpc-service-config.ts", { force: true })
|
||||
const oldhostbridgefiles = [
|
||||
"src/hosts/vscode/workspace/methods.ts",
|
||||
"src/hosts/vscode/workspace/index.ts",
|
||||
@@ -442,7 +609,7 @@ async function cleanup() {
|
||||
"src/hosts/vscode/uri/index.ts",
|
||||
]
|
||||
for (const file of oldhostbridgefiles) {
|
||||
await rmrf(file)
|
||||
await fs.rm(file, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,10 +635,6 @@ async function rmdir(path) {
|
||||
}
|
||||
}
|
||||
|
||||
async function rmrf(path) {
|
||||
await fs.rm(path, { force: true, recursive: true })
|
||||
}
|
||||
|
||||
// Check for Apple Silicon compatibility
|
||||
function checkAppleSiliconCompatibility() {
|
||||
// Only run check on macOS
|
||||
|
||||
@@ -6,15 +6,10 @@ import * as grpc from "@grpc/grpc-js"
|
||||
import * as protoLoader from "@grpc/proto-loader"
|
||||
import chalk from "chalk"
|
||||
|
||||
const IMPL_FILE = path.resolve("src/generated/standalone/host-bridge-clients.ts")
|
||||
const INTERFACE_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts")
|
||||
const DESCRIPTOR_SET = path.resolve("dist-standalone/proto/descriptor_set.pb")
|
||||
|
||||
// Contains the interface definitions for the host bridge clients.
|
||||
const TYPES_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts")
|
||||
// Contains the ExternalHostBridgeClientManager for the external host bridge clients (using nice-grpc).
|
||||
const EXTERNAL_CLIENT_FILE = path.resolve("src/generated/hosts/standalone/host-bridge-clients.ts")
|
||||
// Contains the handler map for the external host bridge clients (using the custom service registry).
|
||||
const VSCODE_CLIENT_FILE = path.resolve("src/generated/hosts/vscode/hostbridge-grpc-service-config.ts")
|
||||
|
||||
const typeNameToFQN = new Map()
|
||||
|
||||
function addTypeNameToFqn(name, fqn) {
|
||||
@@ -53,23 +48,24 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
await generateTypesFile(hostServices)
|
||||
await generateExternalClientFile(hostServices)
|
||||
await generateVscodeClientFile(hostServices)
|
||||
// Generate interfaces file
|
||||
await generateInterfacesFile(hostServices)
|
||||
|
||||
// // Generate implementation file
|
||||
await generateImplementationFile(hostServices)
|
||||
|
||||
console.log(`Generated host bridge client files at:`)
|
||||
console.log(`- ${TYPES_FILE}`)
|
||||
console.log(`- ${EXTERNAL_CLIENT_FILE}`)
|
||||
console.log(`- ${VSCODE_CLIENT_FILE}`)
|
||||
console.log(`- ${INTERFACE_FILE}`)
|
||||
console.log(`- ${IMPL_FILE}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the client interfaces file.
|
||||
*/
|
||||
async function generateTypesFile(hostServices) {
|
||||
async function generateInterfacesFile(hostServices) {
|
||||
const clientInterfaces = []
|
||||
for (const [name, def] of Object.entries(hostServices)) {
|
||||
const clientInterface = generateClientInterfaceType(name, def)
|
||||
const clientInterface = generateClientInterface(name, def)
|
||||
clientInterfaces.push(clientInterface)
|
||||
}
|
||||
const content = `// GENERATED CODE -- DO NOT EDIT!
|
||||
@@ -80,14 +76,14 @@ import { StreamingCallbacks } from "@hosts/host-provider-types"
|
||||
${clientInterfaces.join("\n\n")}
|
||||
`
|
||||
// Write output file
|
||||
await fs.mkdir(path.dirname(TYPES_FILE), { recursive: true })
|
||||
await fs.writeFile(TYPES_FILE, content)
|
||||
await fs.mkdir(path.dirname(INTERFACE_FILE), { recursive: true })
|
||||
await fs.writeFile(INTERFACE_FILE, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a client interface for a service.
|
||||
*/
|
||||
function generateClientInterfaceType(serviceName, serviceDefinition) {
|
||||
function generateClientInterface(serviceName, serviceDefinition) {
|
||||
// Get the methods from the service definition
|
||||
const methods = Object.entries(serviceDefinition.service)
|
||||
.map(([methodName, methodDef]) => {
|
||||
@@ -114,9 +110,9 @@ ${methods}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the external client implementations file.
|
||||
* Generate the client implementations file.
|
||||
*/
|
||||
async function generateExternalClientFile(hostServices) {
|
||||
async function generateImplementationFile(hostServices) {
|
||||
// Generate imports
|
||||
const imports = []
|
||||
// Add imports for the interfaces
|
||||
@@ -125,7 +121,7 @@ async function generateExternalClientFile(hostServices) {
|
||||
}
|
||||
const clientImplementations = []
|
||||
for (const [name, def] of Object.entries(hostServices)) {
|
||||
clientImplementations.push(generateExternalClientSetup(name, def))
|
||||
clientImplementations.push(generateClientImplementation(name, def))
|
||||
}
|
||||
|
||||
const content = `// GENERATED CODE -- DO NOT EDIT!
|
||||
@@ -140,15 +136,16 @@ ${imports.join("\n")}
|
||||
|
||||
${clientImplementations.join("\n\n")}
|
||||
`
|
||||
|
||||
// Write output file
|
||||
await fs.mkdir(path.dirname(EXTERNAL_CLIENT_FILE), { recursive: true })
|
||||
await fs.writeFile(EXTERNAL_CLIENT_FILE, content)
|
||||
await fs.mkdir(path.dirname(IMPL_FILE), { recursive: true })
|
||||
await fs.writeFile(IMPL_FILE, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a client implementation class for a service
|
||||
*/
|
||||
function generateExternalClientSetup(serviceName, serviceDefinition) {
|
||||
function generateClientImplementation(serviceName, serviceDefinition) {
|
||||
// Get the methods from the service definition
|
||||
const methods = Object.entries(serviceDefinition.service)
|
||||
.map(([methodName, methodDef]) => {
|
||||
@@ -188,71 +185,6 @@ ${methods}
|
||||
}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Vscode client setup file.
|
||||
*/
|
||||
async function generateVscodeClientFile(hostServices) {
|
||||
const imports = []
|
||||
const clientImplementations = []
|
||||
const handlerMap = []
|
||||
for (const [serviceName, serviceDefinition] of Object.entries(hostServices)) {
|
||||
const name = serviceName.replace(/Service$/, "").toLowerCase()
|
||||
for (const [methodName, _methodDef] of Object.entries(serviceDefinition.service)) {
|
||||
imports.push(`import { ${methodName} } from "@/hosts/vscode/hostbridge/${name}/${methodName}"`)
|
||||
}
|
||||
imports.push("")
|
||||
|
||||
clientImplementations.push(generateVscodeClientImplementation(name, serviceDefinition))
|
||||
|
||||
handlerMap.push(` "host.${serviceName}": {
|
||||
requestHandler: ${name}ServiceRegistry.handleRequest,
|
||||
streamingHandler: ${name}ServiceRegistry.handleStreamingRequest,
|
||||
},`)
|
||||
}
|
||||
|
||||
const content = `// GENERATED CODE -- DO NOT EDIT!
|
||||
// Generated by scripts/generate-host-bridge-client.mjs
|
||||
import { createServiceRegistry } from "@hosts/vscode/hostbridge-grpc-service"
|
||||
import { HostServiceHandlerConfig } from "@hosts/vscode/hostbridge-grpc-handler"
|
||||
|
||||
${imports.join("\n")}
|
||||
${clientImplementations.join("\n\n")}
|
||||
|
||||
/**
|
||||
* Map of host service names to their handler configurations
|
||||
*/
|
||||
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {
|
||||
${handlerMap.join("\n")}
|
||||
}
|
||||
`
|
||||
|
||||
// Write output file
|
||||
await fs.mkdir(path.dirname(VSCODE_CLIENT_FILE), { recursive: true })
|
||||
await fs.writeFile(VSCODE_CLIENT_FILE, content)
|
||||
}
|
||||
|
||||
function generateVscodeClientImplementation(serviceName, serviceDefinition) {
|
||||
// Get the methods from the service definition
|
||||
const name = serviceName.replace(/Service$/, "").toLowerCase()
|
||||
|
||||
const methods = Object.entries(serviceDefinition.service)
|
||||
.map(([methodName, methodDef]) => {
|
||||
// Get fully qualified type names
|
||||
const isStreamingResponse = methodDef.responseStream
|
||||
if (!isStreamingResponse) {
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName})`
|
||||
} else {
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName}, { isStreaming: true })`
|
||||
}
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
// Generate the class
|
||||
return `// Setup ${name} service registry
|
||||
const ${name}ServiceRegistry = createServiceRegistry("${name}")
|
||||
${methods}`
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red("Error:"), error)
|
||||
|
||||
@@ -62,7 +62,7 @@ let output = `// GENERATED CODE -- DO NOT EDIT!
|
||||
import * as grpc from "@grpc/grpc-js"
|
||||
import { cline } from "@generated/grpc-js"
|
||||
import { Controller } from "@core/controller"
|
||||
import { GrpcHandlerWrapper, GrpcStreamingResponseHandlerWrapper } from "@hosts/external/grpc-types"
|
||||
import { GrpcHandlerWrapper, GrpcStreamingResponseHandlerWrapper } from "@/standalone/grpc-types"
|
||||
|
||||
${imports}
|
||||
export function addProtobusServices(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu #x
|
||||
# This compiles the cline-core app, installs it to the user's home directory,
|
||||
# and runs the service.
|
||||
|
||||
CORE_DIR=~/.cline/core
|
||||
INSTALL_DIR=$CORE_DIR/0.0.1
|
||||
|
||||
# Build cline core
|
||||
npm run compile-standalone
|
||||
|
||||
# Remove old unpacked versions to force reinstall
|
||||
rm -rf $CORE_DIR/* || true
|
||||
|
||||
mkdir -p $INSTALL_DIR
|
||||
cp dist-standalone/standalone.zip $INSTALL_DIR
|
||||
cd $INSTALL_DIR
|
||||
unp standalone.zip > /dev/null
|
||||
|
||||
pkill -f cline-core.js || true
|
||||
NODE_PATH=./node_modules node cline-core.js
|
||||
+63
-98
@@ -29,8 +29,6 @@ import { SapAiCoreHandler } from "./providers/sapaicore"
|
||||
import { ClaudeCodeHandler } from "./providers/claude-code"
|
||||
import { MoonshotHandler } from "./providers/moonshot"
|
||||
import { GroqHandler } from "./providers/groq"
|
||||
import { Mode } from "../shared/ChatSettings"
|
||||
import { HuggingFaceHandler } from "./providers/huggingface"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream
|
||||
@@ -42,33 +40,27 @@ export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
}
|
||||
|
||||
function createHandlerForProvider(
|
||||
apiProvider: string | undefined,
|
||||
options: Omit<ApiConfiguration, "apiProvider">,
|
||||
mode: Mode,
|
||||
): ApiHandler {
|
||||
function createHandlerForProvider(apiProvider: string | undefined, options: Omit<ApiConfiguration, "apiProvider">): ApiHandler {
|
||||
switch (apiProvider) {
|
||||
case "anthropic":
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
anthropicBaseUrl: options.anthropicBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "openrouter":
|
||||
return new OpenRouterHandler({
|
||||
openRouterApiKey: options.openRouterApiKey,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
openRouterModelId: options.openRouterModelId,
|
||||
openRouterModelInfo: options.openRouterModelInfo,
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler({
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
awsAccessKey: options.awsAccessKey,
|
||||
awsSecretKey: options.awsSecretKey,
|
||||
awsSessionToken: options.awsSessionToken,
|
||||
@@ -80,20 +72,16 @@ function createHandlerForProvider(
|
||||
awsUseProfile: options.awsUseProfile,
|
||||
awsProfile: options.awsProfile,
|
||||
awsBedrockEndpoint: options.awsBedrockEndpoint,
|
||||
awsBedrockCustomSelected:
|
||||
mode === "plan" ? options.planModeAwsBedrockCustomSelected : options.actModeAwsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId:
|
||||
mode === "plan" ? options.planModeAwsBedrockCustomModelBaseId : options.actModeAwsBedrockCustomModelBaseId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
awsBedrockCustomSelected: options.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: options.awsBedrockCustomModelBaseId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "vertex":
|
||||
return new VertexHandler({
|
||||
vertexProjectId: options.vertexProjectId,
|
||||
vertexRegion: options.vertexRegion,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
taskId: options.taskId,
|
||||
@@ -104,21 +92,21 @@ function createHandlerForProvider(
|
||||
openAiBaseUrl: options.openAiBaseUrl,
|
||||
azureApiVersion: options.azureApiVersion,
|
||||
openAiHeaders: options.openAiHeaders,
|
||||
openAiModelId: mode === "plan" ? options.planModeOpenAiModelId : options.actModeOpenAiModelId,
|
||||
openAiModelInfo: mode === "plan" ? options.planModeOpenAiModelInfo : options.actModeOpenAiModelInfo,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
openAiModelId: options.openAiModelId,
|
||||
openAiModelInfo: options.openAiModelInfo,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
})
|
||||
case "ollama":
|
||||
return new OllamaHandler({
|
||||
ollamaBaseUrl: options.ollamaBaseUrl,
|
||||
ollamaModelId: mode === "plan" ? options.planModeOllamaModelId : options.actModeOllamaModelId,
|
||||
ollamaModelId: options.ollamaModelId,
|
||||
ollamaApiOptionsCtxNum: options.ollamaApiOptionsCtxNum,
|
||||
requestTimeoutMs: options.requestTimeoutMs,
|
||||
})
|
||||
case "lmstudio":
|
||||
return new LmStudioHandler({
|
||||
lmStudioBaseUrl: options.lmStudioBaseUrl,
|
||||
lmStudioModelId: mode === "plan" ? options.planModeLmStudioModelId : options.actModeLmStudioModelId,
|
||||
lmStudioModelId: options.lmStudioModelId,
|
||||
})
|
||||
case "gemini":
|
||||
return new GeminiHandler({
|
||||
@@ -126,85 +114,78 @@ function createHandlerForProvider(
|
||||
vertexRegion: options.vertexRegion,
|
||||
geminiApiKey: options.geminiApiKey,
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
case "openai-native":
|
||||
return new OpenAiNativeHandler({
|
||||
openAiNativeApiKey: options.openAiNativeApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "deepseek":
|
||||
return new DeepSeekHandler({
|
||||
deepSeekApiKey: options.deepSeekApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "requesty":
|
||||
return new RequestyHandler({
|
||||
requestyApiKey: options.requestyApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
requestyModelId: mode === "plan" ? options.planModeRequestyModelId : options.actModeRequestyModelId,
|
||||
requestyModelInfo: mode === "plan" ? options.planModeRequestyModelInfo : options.actModeRequestyModelInfo,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
requestyModelId: options.requestyModelId,
|
||||
requestyModelInfo: options.requestyModelInfo,
|
||||
})
|
||||
case "fireworks":
|
||||
return new FireworksHandler({
|
||||
fireworksApiKey: options.fireworksApiKey,
|
||||
fireworksModelId: mode === "plan" ? options.planModeFireworksModelId : options.actModeFireworksModelId,
|
||||
fireworksModelId: options.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: options.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: options.fireworksModelMaxTokens,
|
||||
})
|
||||
case "together":
|
||||
return new TogetherHandler({
|
||||
togetherApiKey: options.togetherApiKey,
|
||||
togetherModelId: mode === "plan" ? options.planModeTogetherModelId : options.actModeTogetherModelId,
|
||||
togetherModelId: options.togetherModelId,
|
||||
})
|
||||
case "qwen":
|
||||
return new QwenHandler({
|
||||
qwenApiKey: options.qwenApiKey,
|
||||
qwenApiLine: options.qwenApiLine,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
case "doubao":
|
||||
return new DoubaoHandler({
|
||||
doubaoApiKey: options.doubaoApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "mistral":
|
||||
return new MistralHandler({
|
||||
mistralApiKey: options.mistralApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "vscode-lm":
|
||||
return new VsCodeLmHandler({
|
||||
vsCodeLmModelSelector:
|
||||
mode === "plan" ? options.planModeVsCodeLmModelSelector : options.actModeVsCodeLmModelSelector,
|
||||
vsCodeLmModelSelector: options.vsCodeLmModelSelector,
|
||||
})
|
||||
case "cline":
|
||||
return new ClineHandler({
|
||||
clineAccountId: options.clineAccountId,
|
||||
taskId: options.taskId,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
openRouterModelId: options.openRouterModelId,
|
||||
openRouterModelInfo: options.openRouterModelInfo,
|
||||
})
|
||||
case "litellm":
|
||||
return new LiteLlmHandler({
|
||||
liteLlmApiKey: options.liteLlmApiKey,
|
||||
liteLlmBaseUrl: options.liteLlmBaseUrl,
|
||||
liteLlmModelId: mode === "plan" ? options.planModeLiteLlmModelId : options.actModeLiteLlmModelId,
|
||||
liteLlmModelInfo: mode === "plan" ? options.planModeLiteLlmModelInfo : options.actModeLiteLlmModelInfo,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
liteLlmModelId: options.liteLlmModelId,
|
||||
liteLlmModelInfo: options.liteLlmModelInfo,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
liteLlmUsePromptCache: options.liteLlmUsePromptCache,
|
||||
taskId: options.taskId,
|
||||
})
|
||||
@@ -212,48 +193,41 @@ function createHandlerForProvider(
|
||||
return new MoonshotHandler({
|
||||
moonshotApiKey: options.moonshotApiKey,
|
||||
moonshotApiLine: options.moonshotApiLine,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
})
|
||||
case "huggingface":
|
||||
return new HuggingFaceHandler({
|
||||
huggingFaceApiKey: options.huggingFaceApiKey,
|
||||
huggingFaceModelId: mode === "plan" ? options.planModeHuggingFaceModelId : options.actModeHuggingFaceModelId,
|
||||
huggingFaceModelInfo:
|
||||
mode === "plan" ? options.planModeHuggingFaceModelInfo : options.actModeHuggingFaceModelInfo,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "nebius":
|
||||
return new NebiusHandler({
|
||||
nebiusApiKey: options.nebiusApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "asksage":
|
||||
return new AskSageHandler({
|
||||
asksageApiKey: options.asksageApiKey,
|
||||
asksageApiUrl: options.asksageApiUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "xai":
|
||||
return new XAIHandler({
|
||||
xaiApiKey: options.xaiApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
reasoningEffort: options.reasoningEffort,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "sambanova":
|
||||
return new SambanovaHandler({
|
||||
sambanovaApiKey: options.sambanovaApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "cerebras":
|
||||
return new CerebrasHandler({
|
||||
cerebrasApiKey: options.cerebrasApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "groq":
|
||||
return new GroqHandler({
|
||||
groqApiKey: options.groqApiKey,
|
||||
groqModelId: mode === "plan" ? options.planModeGroqModelId : options.actModeGroqModelId,
|
||||
groqModelInfo: mode === "plan" ? options.planModeGroqModelInfo : options.actModeGroqModelInfo,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
groqModelId: options.groqModelId,
|
||||
groqModelInfo: options.groqModelInfo,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "sapaicore":
|
||||
return new SapAiCoreHandler({
|
||||
@@ -262,46 +236,37 @@ function createHandlerForProvider(
|
||||
sapAiCoreTokenUrl: options.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: options.sapAiResourceGroup,
|
||||
sapAiCoreBaseUrl: options.sapAiCoreBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
apiModelId: options.apiModelId,
|
||||
})
|
||||
case "claude-code":
|
||||
return new ClaudeCodeHandler({
|
||||
claudeCodePath: options.claudeCodePath,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
default:
|
||||
return new AnthropicHandler({
|
||||
apiKey: options.apiKey,
|
||||
anthropicBaseUrl: options.anthropicBaseUrl,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
apiModelId: options.apiModelId,
|
||||
thinkingBudgetTokens: options.thinkingBudgetTokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): ApiHandler {
|
||||
const { planModeApiProvider, actModeApiProvider, ...options } = configuration
|
||||
|
||||
const apiProvider = mode === "plan" ? planModeApiProvider : actModeApiProvider
|
||||
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
const { apiProvider, ...options } = configuration
|
||||
|
||||
// Validate thinking budget tokens against model's maxTokens to prevent API errors
|
||||
// wrapped in a try-catch for safety, but this should never throw
|
||||
try {
|
||||
const thinkingBudgetTokens = mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens
|
||||
if (thinkingBudgetTokens && thinkingBudgetTokens > 0) {
|
||||
const handler = createHandlerForProvider(apiProvider, options, mode)
|
||||
if (options.thinkingBudgetTokens && options.thinkingBudgetTokens > 0) {
|
||||
const handler = createHandlerForProvider(apiProvider, options)
|
||||
|
||||
const modelInfo = handler.getModel().info
|
||||
if (modelInfo.maxTokens && thinkingBudgetTokens > modelInfo.maxTokens) {
|
||||
if (modelInfo.maxTokens && options.thinkingBudgetTokens > modelInfo.maxTokens) {
|
||||
const clippedValue = modelInfo.maxTokens - 1
|
||||
if (mode === "plan") {
|
||||
options.planModeThinkingBudgetTokens = clippedValue
|
||||
} else {
|
||||
options.actModeThinkingBudgetTokens = clippedValue
|
||||
}
|
||||
options.thinkingBudgetTokens = clippedValue
|
||||
} else {
|
||||
return handler // don't rebuild unless its necessary
|
||||
}
|
||||
@@ -310,5 +275,5 @@ export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): Ap
|
||||
console.error("buildApiHandler error:", error)
|
||||
}
|
||||
|
||||
return createHandlerForProvider(apiProvider, options, mode)
|
||||
return createHandlerForProvider(apiProvider, options)
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ describe("AwsBedrockHandler", () => {
|
||||
})
|
||||
|
||||
const mockOptions: ApiHandlerOptions = {
|
||||
actModeApiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
apiModelId: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
awsRegion: "us-east-1",
|
||||
awsAccessKey: "test-key",
|
||||
awsSecretKey: "test-secret",
|
||||
@@ -214,9 +214,9 @@ describe("AwsBedrockHandler", () => {
|
||||
awsBedrockUsePromptCache: false,
|
||||
awsUseCrossRegionInference: false,
|
||||
awsBedrockEndpoint: "",
|
||||
actModeAwsBedrockCustomSelected: false,
|
||||
actModeAwsBedrockCustomModelBaseId: undefined,
|
||||
actModeThinkingBudgetTokens: 1600,
|
||||
awsBedrockCustomSelected: false,
|
||||
awsBedrockCustomModelBaseId: undefined,
|
||||
thinkingBudgetTokens: 1600,
|
||||
}
|
||||
|
||||
const mockModelInfo = {
|
||||
@@ -616,8 +616,8 @@ describe("AwsBedrockHandler", () => {
|
||||
it("should return raw model ID for custom models", async () => {
|
||||
const customOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId:
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId:
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
}
|
||||
const customHandler = new AwsBedrockHandler(customOptions)
|
||||
@@ -631,8 +631,8 @@ describe("AwsBedrockHandler", () => {
|
||||
it("should not encode custom model IDs with slashes", async () => {
|
||||
const customOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId: "my-namespace/my-custom-model",
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId: "my-namespace/my-custom-model",
|
||||
}
|
||||
const customHandler = new AwsBedrockHandler(customOptions)
|
||||
|
||||
@@ -680,8 +680,8 @@ describe("AwsBedrockHandler", () => {
|
||||
it("should not apply cross-region prefix for custom models even when enabled", async () => {
|
||||
const customCrossRegionOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId: "arn:aws:bedrock:us-west-2:123456789012:custom-model/my-model",
|
||||
awsUseCrossRegionInference: true,
|
||||
}
|
||||
const customCrossRegionHandler = new AwsBedrockHandler(customCrossRegionOptions)
|
||||
@@ -693,10 +693,10 @@ describe("AwsBedrockHandler", () => {
|
||||
it("should handle UltraThink model ARN correctly", async () => {
|
||||
const ultraThinkOptions: ApiHandlerOptions = {
|
||||
...mockOptions,
|
||||
actModeAwsBedrockCustomSelected: true,
|
||||
actModeApiModelId:
|
||||
awsBedrockCustomSelected: true,
|
||||
apiModelId:
|
||||
"arn:aws:bedrock:us-west-2:123456789012:custom-model/anthropic.claude-3-5-sonnet-20241022-v2:0/Qk8MMyLmRd",
|
||||
actModeAwsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsBedrockCustomModelBaseId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
}
|
||||
const ultraThinkHandler = new AwsBedrockHandler(ultraThinkOptions)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("OllamaHandler", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
options = {
|
||||
actModeOllamaModelId: "llama2",
|
||||
ollamaModelId: "llama2",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
}
|
||||
handler = new OllamaHandler(options)
|
||||
|
||||
@@ -13,7 +13,7 @@ interface AnthropicHandlerOptions {
|
||||
}
|
||||
|
||||
export class AnthropicHandler implements ApiHandler {
|
||||
private options: AnthropicHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: Anthropic | undefined
|
||||
|
||||
constructor(options: AnthropicHandlerOptions) {
|
||||
|
||||
@@ -12,7 +12,6 @@ import OpenAI from "openai"
|
||||
import { version as extensionVersion } from "../../../package.json"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { clineEnvConfig } from "@/config"
|
||||
|
||||
interface ClineHandlerOptions {
|
||||
taskId?: string
|
||||
@@ -29,7 +28,10 @@ export class ClineHandler implements ApiHandler {
|
||||
private clineAccountService = ClineAccountService.getInstance()
|
||||
private _authService: AuthService
|
||||
private client: OpenAI | undefined
|
||||
private readonly _baseUrl = clineEnvConfig.apiBaseUrl
|
||||
// TODO: replace this with a global API Host
|
||||
private readonly _baseUrl = "http://localhost:7777"
|
||||
// private readonly _baseUrl = "https://core-api.staging.int.cline.bot"
|
||||
// private readonly _baseUrl = "http://localhost:7777"
|
||||
lastGenerationId?: string
|
||||
private counter = 0
|
||||
|
||||
@@ -177,7 +179,17 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Cline API Error:", error)
|
||||
throw error
|
||||
const requestId = error?.request_id ? `\n | Request ID: ${error.request_id}` : ""
|
||||
if (error.code === "ERR_BAD_REQUEST" || error.status === 401) {
|
||||
throw new Error(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE + requestId)
|
||||
} else if (error.code === "insufficient_credits" || error.status === 402) {
|
||||
if (error.error) {
|
||||
throw new Error(JSON.stringify(error.error))
|
||||
}
|
||||
}
|
||||
const _error = error instanceof Error ? error : new Error(String(error))
|
||||
_error.message = _error.message + requestId
|
||||
throw _error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ interface GeminiHandlerOptions {
|
||||
* 4. Separating immediate costs from ongoing costs to avoid double-counting
|
||||
*/
|
||||
export class GeminiHandler implements ApiHandler {
|
||||
private options: GeminiHandlerOptions
|
||||
private options: ApiHandlerOptions
|
||||
private client: GoogleGenAI | undefined
|
||||
|
||||
constructor(options: GeminiHandlerOptions) {
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, HuggingFaceModelId, ModelInfo, huggingFaceDefaultModelId, huggingFaceModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface HuggingFaceHandlerOptions {
|
||||
huggingFaceApiKey?: string
|
||||
huggingFaceModelId?: string
|
||||
huggingFaceModelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
export class HuggingFaceHandler implements ApiHandler {
|
||||
private options: HuggingFaceHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
private cachedModel: { id: HuggingFaceModelId; info: ModelInfo } | undefined
|
||||
|
||||
constructor(options: HuggingFaceHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
if (!this.options.huggingFaceApiKey) {
|
||||
throw new Error("Hugging Face API key is required")
|
||||
}
|
||||
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://router.huggingface.co/v1",
|
||||
apiKey: this.options.huggingFaceApiKey,
|
||||
defaultHeaders: {
|
||||
"User-Agent": "Cline/1.0",
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Hugging Face client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
|
||||
private async *yieldUsage(info: ModelInfo, usage: OpenAI.Completions.CompletionUsage | undefined): ApiStream {
|
||||
if (!usage) {
|
||||
return
|
||||
}
|
||||
|
||||
const inputTokens = usage.prompt_tokens || 0
|
||||
const outputTokens = usage.completion_tokens || 0
|
||||
const totalCost = calculateApiCostOpenAI(info, inputTokens, outputTokens)
|
||||
|
||||
const usageData = {
|
||||
type: "usage" as const,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: 0,
|
||||
totalCost: totalCost,
|
||||
}
|
||||
|
||||
yield usageData
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const requestParams = {
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
}
|
||||
|
||||
const stream = (await client.chat.completions.create(requestParams)) as any
|
||||
|
||||
let chunkCount = 0
|
||||
let totalContent = ""
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunkCount++
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
totalContent += delta.content
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: HuggingFaceModelId; info: ModelInfo } {
|
||||
// Return cached model if available
|
||||
if (this.cachedModel) {
|
||||
return this.cachedModel
|
||||
}
|
||||
|
||||
const modelId = this.options.huggingFaceModelId
|
||||
|
||||
// List all available models for debugging
|
||||
const availableModels = Object.keys(huggingFaceModels)
|
||||
let result: { id: HuggingFaceModelId; info: ModelInfo }
|
||||
|
||||
if (modelId && modelId in huggingFaceModels) {
|
||||
const id = modelId as HuggingFaceModelId
|
||||
const modelInfo = huggingFaceModels[id]
|
||||
result = { id, info: modelInfo }
|
||||
} else {
|
||||
const defaultInfo = huggingFaceModels[huggingFaceDefaultModelId]
|
||||
result = {
|
||||
id: huggingFaceDefaultModelId,
|
||||
info: defaultInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result for future calls
|
||||
this.cachedModel = result
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,6 @@ interface OpenRouterHandlerOptions {
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
interface OpenRouterHandlerOptions {
|
||||
openRouterApiKey?: string
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
openRouterProviderSorting?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
export class OpenRouterHandler implements ApiHandler {
|
||||
private options: OpenRouterHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
@@ -14,12 +14,6 @@ interface XAIHandlerOptions {
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
interface XAIHandlerOptions {
|
||||
xaiApiKey?: string
|
||||
reasoningEffort?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class XAIHandler implements ApiHandler {
|
||||
private options: XAIHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
@@ -140,7 +140,7 @@ export async function createOpenRouterStream(
|
||||
}
|
||||
|
||||
// hardcoded provider sorting for kimi-k2
|
||||
const isKimiK2 = model.id === "moonshotai/kimi-k2"
|
||||
const isKimiK2 = model.id.startsWith("moonshotai/kimi-k2")
|
||||
openRouterProviderSorting = isKimiK2 ? undefined : openRouterProviderSorting
|
||||
|
||||
// @ts-ignore-next-line
|
||||
@@ -158,9 +158,7 @@ export async function createOpenRouterStream(
|
||||
...(reasoning ? { reasoning } : {}),
|
||||
...(openRouterProviderSorting ? { provider: { sort: openRouterProviderSorting } } : {}),
|
||||
// limit providers to only those that support the 131k context window
|
||||
...(isKimiK2
|
||||
? { provider: { order: ["groq", "together", "baseten", "parasail", "novita", "deepinfra"], allow_fallbacks: false } }
|
||||
: {}),
|
||||
...(isKimiK2 ? { provider: { order: ["groq", "together"], allow_fallbacks: false } } : {}),
|
||||
})
|
||||
|
||||
return stream
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
export type Environment = "production" | "staging" | "local"
|
||||
|
||||
const CLINE_ENVIRONMENT: Environment = (process.env.CLINE_ENVIRONMENT as Environment) || "production"
|
||||
|
||||
interface EnvironmentConfig {
|
||||
appBaseUrl: string
|
||||
apiBaseUrl: string
|
||||
mcpBaseUrl: string
|
||||
firebase: {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
projectId: string
|
||||
storageBucket?: string
|
||||
messagingSenderId?: string
|
||||
appId?: string
|
||||
}
|
||||
}
|
||||
|
||||
const configs: Record<Environment, EnvironmentConfig> = {
|
||||
production: {
|
||||
appBaseUrl: "https://app.cline.bot",
|
||||
apiBaseUrl: "https://api.cline.bot",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyC5rx59Xt8UgwdU3PCfzUF7vCwmp9-K2vk",
|
||||
authDomain: "cline-prod.firebaseapp.com",
|
||||
projectId: "cline-prod",
|
||||
storageBucket: "cline-prod.firebasestorage.app",
|
||||
messagingSenderId: "941048379330",
|
||||
appId: "1:941048379330:web:45058eedeefc5cdfcc485b",
|
||||
},
|
||||
},
|
||||
staging: {
|
||||
appBaseUrl: "https://staging-app.cline.bot",
|
||||
apiBaseUrl: "https://core-api.staging.int.cline.bot",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
|
||||
authDomain: "cline-staging.firebaseapp.com",
|
||||
projectId: "cline-staging",
|
||||
storageBucket: "cline-staging.firebasestorage.app",
|
||||
messagingSenderId: "853479478430",
|
||||
appId: "1:853479478430:web:2de0dba1c63c3262d4578f",
|
||||
},
|
||||
},
|
||||
local: {
|
||||
appBaseUrl: "http://localhost:3000",
|
||||
apiBaseUrl: "http://localhost:7777",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
|
||||
authDomain: "cline-preview.firebaseapp.com",
|
||||
projectId: "cline-preview",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const clineEnvConfig = configs[CLINE_ENVIRONMENT]
|
||||
@@ -44,10 +44,12 @@ export const createRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
|
||||
if (fileExists) {
|
||||
const message = `${fileTypeName} file "${request.filename}" already exists.`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
// Still open it for editing
|
||||
await handleFileServiceRequest(controller, "openFile", { value: filePath })
|
||||
} else {
|
||||
@@ -61,10 +63,12 @@ export const createRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
await handleFileServiceRequest(controller, "openFile", { value: filePath })
|
||||
|
||||
const message = `Created new ${request.isGlobal ? "global" : "workspace"} ${fileTypeName} file: ${request.filename}`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return RuleFile.create({
|
||||
|
||||
@@ -46,10 +46,12 @@ export const deleteRuleFile: FileMethodHandler = async (controller: Controller,
|
||||
const fileTypeName = request.type === "workflow" ? "workflow" : "rule"
|
||||
|
||||
const message = `${fileTypeName} file "${fileName}" deleted successfully`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
|
||||
return RuleFile.create({
|
||||
filePath: request.rulePath,
|
||||
|
||||
+240
-103
@@ -10,7 +10,7 @@ import { ClineAccountService } from "@services/account/ClineAccountService"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ChatContent } from "@shared/ChatContent"
|
||||
import { ChatSettings, Mode, StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { ChatSettings, StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { ExtensionMessage, ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
@@ -36,7 +36,6 @@ import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceC
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { clineEnvConfig } from "@/config"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -50,7 +49,6 @@ export class Controller {
|
||||
|
||||
private disposables: vscode.Disposable[] = []
|
||||
task?: Task
|
||||
|
||||
workspaceTracker: WorkspaceTracker
|
||||
mcpHub: McpHub
|
||||
accountService: ClineAccountService
|
||||
@@ -84,8 +82,8 @@ export class Controller {
|
||||
})
|
||||
}
|
||||
|
||||
async getCurrentMode(): Promise<Mode> {
|
||||
return ((await getGlobalState(this.context, "mode")) as Mode | undefined) || "act"
|
||||
private async getCurrentMode(): Promise<"plan" | "act"> {
|
||||
return ((await getGlobalState(this.context, "mode")) as "plan" | "act" | undefined) || "act"
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -113,20 +111,21 @@ export class Controller {
|
||||
// TODO: update to clineAccountId and then move clineApiKey to a clear function.
|
||||
await storeSecret(this.context, "clineAccountId", undefined)
|
||||
await updateGlobalState(this.context, "userInfo", undefined)
|
||||
await Promise.all([
|
||||
updateGlobalState(this.context, "planModeApiProvider", "openrouter"),
|
||||
updateGlobalState(this.context, "actModeApiProvider", "openrouter"),
|
||||
])
|
||||
await updateGlobalState(this.context, "apiProvider", "openrouter")
|
||||
await this.postStateToWebview()
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Successfully logged out of Cline",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Successfully logged out of Cline",
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Logout failed",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Logout failed",
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,10 +256,153 @@ export class Controller {
|
||||
// Capture mode switch telemetry | Capture regardless of if we know the taskId
|
||||
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", chatSettings.mode)
|
||||
|
||||
// Update API handler with new mode (buildApiHandler now selects provider based on mode)
|
||||
if (this.task) {
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
this.task.api = buildApiHandler(apiConfiguration, chatSettings.mode)
|
||||
// Get previous model info that we will revert to after saving current mode api info
|
||||
const {
|
||||
apiConfiguration,
|
||||
previousModeApiProvider: newApiProvider,
|
||||
previousModeModelId: newModelId,
|
||||
previousModeModelInfo: newModelInfo,
|
||||
previousModeVsCodeLmModelSelector: newVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens: newThinkingBudgetTokens,
|
||||
previousModeReasoningEffort: newReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected: newAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId: newAwsBedrockCustomModelBaseId,
|
||||
previousModeSapAiCoreModelId: newSapAiCoreModelId,
|
||||
planActSeparateModelsSetting,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
const shouldSwitchModel = planActSeparateModelsSetting === true
|
||||
|
||||
if (shouldSwitchModel) {
|
||||
// Save the last model used in this mode
|
||||
await updateGlobalState(this.context, "previousModeApiProvider", apiConfiguration.apiProvider)
|
||||
await updateGlobalState(this.context, "previousModeThinkingBudgetTokens", apiConfiguration.thinkingBudgetTokens)
|
||||
await updateGlobalState(this.context, "previousModeReasoningEffort", apiConfiguration.reasoningEffort)
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
case "openai-native":
|
||||
case "qwen":
|
||||
case "deepseek":
|
||||
case "xai":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomSelected",
|
||||
apiConfiguration.awsBedrockCustomSelected,
|
||||
)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomModelBaseId",
|
||||
apiConfiguration.awsBedrockCustomModelBaseId,
|
||||
)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.openRouterModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
// Important we don't set modelId to this, as it's an object not string (webview expects model id to be a string)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeVsCodeLmModelSelector",
|
||||
apiConfiguration.vsCodeLmModelSelector,
|
||||
)
|
||||
break
|
||||
case "openai":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openAiModelId)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.openAiModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.ollamaModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.lmStudioModelId)
|
||||
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)
|
||||
await updateGlobalState(this.context, "previousModeModelInfo", apiConfiguration.requestyModelInfo)
|
||||
break
|
||||
case "sapaicore":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateGlobalState(this.context, "previousModeSapAiCoreModelId", apiConfiguration.sapAiCoreModelId)
|
||||
break
|
||||
}
|
||||
|
||||
// Restore the model used in previous mode
|
||||
if (
|
||||
newApiProvider ||
|
||||
newModelId ||
|
||||
newThinkingBudgetTokens !== undefined ||
|
||||
newReasoningEffort ||
|
||||
newVsCodeLmModelSelector
|
||||
) {
|
||||
await updateGlobalState(this.context, "apiProvider", newApiProvider)
|
||||
await updateGlobalState(this.context, "thinkingBudgetTokens", newThinkingBudgetTokens)
|
||||
await updateGlobalState(this.context, "reasoningEffort", newReasoningEffort)
|
||||
switch (newApiProvider) {
|
||||
case "anthropic":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
case "openai-native":
|
||||
case "qwen":
|
||||
case "deepseek":
|
||||
case "xai":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomSelected", newAwsBedrockCustomSelected)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomModelBaseId", newAwsBedrockCustomModelBaseId)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateGlobalState(this.context, "openRouterModelId", newModelId)
|
||||
await updateGlobalState(this.context, "openRouterModelInfo", newModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
await updateGlobalState(this.context, "vsCodeLmModelSelector", newVsCodeLmModelSelector)
|
||||
break
|
||||
case "openai":
|
||||
await updateGlobalState(this.context, "openAiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "openAiModelInfo", newModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await updateGlobalState(this.context, "ollamaModelId", newModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await updateGlobalState(this.context, "lmStudioModelId", newModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await updateGlobalState(this.context, "liteLlmModelId", newModelId)
|
||||
await updateGlobalState(this.context, "liteLlmModelInfo", newModelInfo)
|
||||
break
|
||||
case "requesty":
|
||||
await updateGlobalState(this.context, "requestyModelId", newModelId)
|
||||
await updateGlobalState(this.context, "requestyModelInfo", newModelInfo)
|
||||
break
|
||||
case "sapaicore":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "sapAiCoreModelId", newSapAiCoreModelId)
|
||||
break
|
||||
}
|
||||
|
||||
if (this.task) {
|
||||
const { apiConfiguration: updatedApiConfiguration } = await getAllExtensionState(this.context)
|
||||
this.task.api = buildApiHandler(updatedApiConfiguration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save only non-mode properties to global storage
|
||||
@@ -324,47 +466,30 @@ export class Controller {
|
||||
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
|
||||
|
||||
const clineProvider: ApiProvider = "cline"
|
||||
await updateGlobalState(this.context, "apiProvider", clineProvider)
|
||||
|
||||
// Get current settings to determine how to update providers
|
||||
const { planActSeparateModelsSetting } = await getAllExtensionState(this.context)
|
||||
const currentMode = await this.getCurrentMode()
|
||||
// Mark welcome view as completed since user has successfully logged in
|
||||
await updateGlobalState(this.context, "welcomeViewCompleted", true)
|
||||
|
||||
if (planActSeparateModelsSetting) {
|
||||
// Only update the current mode's provider
|
||||
if (currentMode === "plan") {
|
||||
await updateGlobalState(this.context, "planModeApiProvider", clineProvider)
|
||||
} else {
|
||||
await updateGlobalState(this.context, "actModeApiProvider", clineProvider)
|
||||
}
|
||||
} else {
|
||||
// Update both modes to keep them in sync
|
||||
await Promise.all([
|
||||
updateGlobalState(this.context, "planModeApiProvider", clineProvider),
|
||||
updateGlobalState(this.context, "actModeApiProvider", clineProvider),
|
||||
])
|
||||
}
|
||||
|
||||
// Get the updated API configuration (now includes the updated providers)
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
const updatedConfig = {
|
||||
...apiConfiguration,
|
||||
apiProvider: clineProvider,
|
||||
}
|
||||
|
||||
// Mark welcome view as completed since user has successfully logged in
|
||||
await updateGlobalState(this.context, "welcomeViewCompleted", true)
|
||||
|
||||
if (this.task) {
|
||||
this.task.api = buildApiHandler(updatedConfig, currentMode)
|
||||
this.task.api = buildApiHandler(updatedConfig)
|
||||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
} catch (error) {
|
||||
console.error("Failed to handle auth callback:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to log in to Cline",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to log in to Cline",
|
||||
}),
|
||||
)
|
||||
// Even on login failure, we preserve any existing tokens
|
||||
// Only clear tokens on explicit logout
|
||||
}
|
||||
@@ -373,7 +498,7 @@ export class Controller {
|
||||
// MCP Marketplace
|
||||
private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
|
||||
try {
|
||||
const response = await axios.get(`${clineEnvConfig.mcpBaseUrl}/marketplace`, {
|
||||
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -399,10 +524,12 @@ export class Controller {
|
||||
console.error("Failed to fetch MCP marketplace:", error)
|
||||
if (!silent) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
}),
|
||||
)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
@@ -410,7 +537,7 @@ export class Controller {
|
||||
|
||||
private async fetchMcpMarketplaceFromApiRPC(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
|
||||
try {
|
||||
const response = await axios.get(`${clineEnvConfig.mcpBaseUrl}/marketplace`, {
|
||||
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "cline-vscode-extension",
|
||||
@@ -486,10 +613,12 @@ export class Controller {
|
||||
} catch (error) {
|
||||
console.error("Failed to handle cached MCP marketplace:", error)
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: errorMessage,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,21 +639,14 @@ export class Controller {
|
||||
}
|
||||
|
||||
const openrouter: ApiProvider = "openrouter"
|
||||
const currentMode = await this.getCurrentMode()
|
||||
await Promise.all([
|
||||
updateGlobalState(this.context, "planModeApiProvider", openrouter),
|
||||
updateGlobalState(this.context, "actModeApiProvider", openrouter),
|
||||
])
|
||||
await updateGlobalState(this.context, "apiProvider", openrouter)
|
||||
await storeSecret(this.context, "openRouterApiKey", apiKey)
|
||||
await this.postStateToWebview()
|
||||
if (this.task) {
|
||||
// Get the updated API configuration (now includes the updated providers)
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
const updatedConfig = {
|
||||
...apiConfiguration,
|
||||
this.task.api = buildApiHandler({
|
||||
apiProvider: openrouter,
|
||||
openRouterApiKey: apiKey,
|
||||
}
|
||||
this.task.api = buildApiHandler(updatedConfig, currentMode)
|
||||
})
|
||||
}
|
||||
// await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome
|
||||
}
|
||||
@@ -859,20 +981,24 @@ export class Controller {
|
||||
// Check if there's a workspace folder open
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No workspace folder open",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No workspace folder open",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the git diff
|
||||
const gitDiff = await getWorkingState(cwd)
|
||||
if (gitDiff === "No changes in working directory") {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "No changes in workspace for commit message",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "No changes in workspace for commit message",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -900,10 +1026,9 @@ Commit message:`
|
||||
|
||||
// Get the current API configuration
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
const currentMode = await this.getCurrentMode()
|
||||
|
||||
// Build the API handler
|
||||
const apiHandler = buildApiHandler(apiConfiguration, currentMode)
|
||||
const apiHandler = buildApiHandler(apiConfiguration)
|
||||
|
||||
// Create a system prompt
|
||||
const systemPrompt =
|
||||
@@ -941,46 +1066,58 @@ Commit message:`
|
||||
const repo = api.repositories[0]
|
||||
repo.inputBox.value = commitMessage
|
||||
const message = "Commit message generated and applied"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
const message = "No Git repositories found"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const message = "Git extension not found"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const message = "Failed to generate commit message"
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (innerError) {
|
||||
const innerErrorMessage = innerError instanceof Error ? innerError.message : String(innerError)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${innerErrorMessage}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${innerErrorMessage}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${errorMessage}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to generate commit message: ${errorMessage}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { StringRequest } from "../../../shared/proto/common"
|
||||
import { McpDownloadResponse } from "../../../shared/proto/mcp"
|
||||
import { McpServer } from "@shared/mcp"
|
||||
import axios from "axios"
|
||||
import * as vscode from "vscode"
|
||||
import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
|
||||
import { clineEnvConfig } from "@/config"
|
||||
|
||||
/**
|
||||
* Download an MCP server from the marketplace
|
||||
@@ -31,7 +31,7 @@ export async function downloadMcp(controller: Controller, request: StringRequest
|
||||
|
||||
// Fetch server details from marketplace
|
||||
const response = await axios.post<McpDownloadResponse>(
|
||||
`${clineEnvConfig.mcpBaseUrl}/download`,
|
||||
"https://api.cline.bot/v1/mcp/download",
|
||||
{ mcpId },
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Controller } from ".."
|
||||
import { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { OpenRouterCompatibleModelInfo, OpenRouterModelInfo } from "../../../shared/proto/models"
|
||||
import axios from "axios"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { huggingFaceModels } from "@shared/api"
|
||||
|
||||
/**
|
||||
* Ensures the cache directory exists and returns its path
|
||||
*/
|
||||
async function ensureCacheDirectoryExists(controller: Controller): Promise<string> {
|
||||
const cacheDir = path.join(controller.context.globalStorageUri.fsPath, "cache")
|
||||
try {
|
||||
await fs.mkdir(cacheDir, { recursive: true })
|
||||
} catch (error) {
|
||||
// Directory might already exist
|
||||
}
|
||||
return cacheDir
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the Hugging Face models and returns the updated model list
|
||||
* @param controller The controller instance
|
||||
* @param request Empty request object
|
||||
* @returns Response containing the Hugging Face models
|
||||
*/
|
||||
export async function refreshHuggingFaceModels(
|
||||
controller: Controller,
|
||||
_request: EmptyRequest,
|
||||
): Promise<OpenRouterCompatibleModelInfo> {
|
||||
const huggingFaceModelsFilePath = path.join(await ensureCacheDirectoryExists(controller), "huggingface_models.json")
|
||||
|
||||
let models: Record<string, OpenRouterModelInfo> = {}
|
||||
|
||||
try {
|
||||
// Fetch models from Hugging Face API
|
||||
const response = await axios.get("https://router.huggingface.co/v1/models", {
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
if (response.data?.data) {
|
||||
const rawModels = response.data.data
|
||||
|
||||
// Transform HF models to OpenRouter-compatible format
|
||||
for (const rawModel of rawModels) {
|
||||
const modelInfo = OpenRouterModelInfo.create({
|
||||
maxTokens: 8192, // HF doesn't provide max_tokens, use default
|
||||
contextWindow: 128_000, // FIXME: HF doesn't provide context window, use default
|
||||
supportsImages: false, // Most models don't support images
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0, // Will be set based on providers
|
||||
outputPrice: 0, // Will be set based on providers
|
||||
cacheWritesPrice: 0,
|
||||
cacheReadsPrice: 0,
|
||||
description: `Available on providers: ${rawModel.providers?.join(", ") || "unknown"}`,
|
||||
})
|
||||
|
||||
// Add model-specific configurations if we have them in our static models
|
||||
if (rawModel.id in huggingFaceModels) {
|
||||
const staticModel = huggingFaceModels[rawModel.id as keyof typeof huggingFaceModels]
|
||||
modelInfo.maxTokens = staticModel.maxTokens
|
||||
modelInfo.contextWindow = staticModel.contextWindow
|
||||
modelInfo.supportsImages = staticModel.supportsImages
|
||||
modelInfo.supportsPromptCache = staticModel.supportsPromptCache
|
||||
modelInfo.inputPrice = staticModel.inputPrice
|
||||
modelInfo.outputPrice = staticModel.outputPrice
|
||||
modelInfo.description = staticModel.description || modelInfo.description
|
||||
}
|
||||
|
||||
models[rawModel.id] = modelInfo
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
await fs.writeFile(huggingFaceModelsFilePath, JSON.stringify(models, null, 2))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching Hugging Face models:", error)
|
||||
|
||||
// Try to load from cache
|
||||
try {
|
||||
if (await fileExistsAtPath(huggingFaceModelsFilePath)) {
|
||||
const cachedModels = await fs.readFile(huggingFaceModelsFilePath, "utf-8")
|
||||
const parsedModels = JSON.parse(cachedModels)
|
||||
models = parsedModels
|
||||
}
|
||||
} catch (cacheError) {
|
||||
console.error("Error loading cached Hugging Face models:", cacheError)
|
||||
}
|
||||
|
||||
// If no cache available, use static models as fallback
|
||||
if (Object.keys(models).length === 0) {
|
||||
for (const [modelId, modelInfo] of Object.entries(huggingFaceModels)) {
|
||||
models[modelId] = OpenRouterModelInfo.create({
|
||||
maxTokens: modelInfo.maxTokens,
|
||||
contextWindow: modelInfo.contextWindow,
|
||||
supportsImages: modelInfo.supportsImages,
|
||||
supportsPromptCache: modelInfo.supportsPromptCache,
|
||||
inputPrice: modelInfo.inputPrice,
|
||||
outputPrice: modelInfo.outputPrice,
|
||||
cacheWritesPrice: (modelInfo as any).cacheWritesPrice || 0,
|
||||
cacheReadsPrice: (modelInfo as any).cacheReadsPrice || 0,
|
||||
description: modelInfo.description || "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OpenRouterCompatibleModelInfo.create({ models })
|
||||
}
|
||||
@@ -29,8 +29,7 @@ export async function updateApiConfigurationProto(
|
||||
|
||||
// Update the task's API handler if there's an active task
|
||||
if (controller.task) {
|
||||
const currentMode = await controller.getCurrentMode()
|
||||
controller.task.api = buildApiHandler(appApiConfiguration, currentMode)
|
||||
controller.task.api = buildApiHandler(appApiConfiguration)
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
|
||||
@@ -15,16 +15,20 @@ import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
export async function resetState(controller: Controller, request: ResetStateRequest): Promise<Empty> {
|
||||
try {
|
||||
if (request.global) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting global state...",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting global state...",
|
||||
}),
|
||||
)
|
||||
await resetGlobalState(controller.context)
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting workspace state...",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Resetting workspace state...",
|
||||
}),
|
||||
)
|
||||
await resetWorkspaceState(controller.context)
|
||||
}
|
||||
|
||||
@@ -33,10 +37,12 @@ export async function resetState(controller: Controller, request: ResetStateRequ
|
||||
controller.task = undefined
|
||||
}
|
||||
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "State reset",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "State reset",
|
||||
}),
|
||||
)
|
||||
await controller.postStateToWebview()
|
||||
|
||||
await sendChatButtonClickedEvent(controller.id)
|
||||
@@ -44,10 +50,12 @@ export async function resetState(controller: Controller, request: ResetStateRequ
|
||||
return Empty.create()
|
||||
} catch (error) {
|
||||
console.error("Error resetting state:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function subscribeToState(
|
||||
const initialState = await controller.getStateToPostToWebview()
|
||||
const initialStateJson = JSON.stringify(initialState)
|
||||
|
||||
//console.log(`[DEBUG] set up state subscription for controller ${controllerId}`)
|
||||
console.log(`[DEBUG] set up state subscription for controller ${controllerId}`)
|
||||
|
||||
await responseStream({
|
||||
stateJson: initialStateJson,
|
||||
@@ -37,7 +37,7 @@ export async function subscribeToState(
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activeStateSubscriptions.delete(controllerId)
|
||||
//console.log(`[DEBUG] Cleaned up state subscription for controller ${controllerId}`)
|
||||
console.log(`[DEBUG] Cleaned up state subscription for controller ${controllerId}`)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
@@ -68,7 +68,7 @@ export async function sendStateUpdate(controllerId: string, state: any): Promise
|
||||
},
|
||||
false, // Not the last message
|
||||
)
|
||||
//console.log(`[DEBUG] sending followup state to controller ${controllerId}`, stateJson.length, "chars")
|
||||
console.log(`[DEBUG] sending followup state to controller ${controllerId}`, stateJson.length, "chars")
|
||||
} catch (error) {
|
||||
console.error(`Error sending state update to controller ${controllerId}:`, error)
|
||||
// Remove the subscription if there was an error
|
||||
|
||||
@@ -27,10 +27,12 @@ export async function updateDefaultTerminalProfile(
|
||||
// Show information message if terminals were closed
|
||||
if (closedCount > 0) {
|
||||
const message = `Closed ${closedCount} ${closedCount === 1 ? "terminal" : "terminals"} with different profile.`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Show warning if there are busy terminals that couldn't be closed
|
||||
@@ -38,10 +40,12 @@ export async function updateDefaultTerminalProfile(
|
||||
const message =
|
||||
`${busyTerminals.length} busy ${busyTerminals.length === 1 ? "terminal has" : "terminals have"} a different profile. ` +
|
||||
`Close ${busyTerminals.length === 1 ? "it" : "them"} to use the new profile for all commands.`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
await updateApiConfiguration(controller.context, apiConfiguration)
|
||||
|
||||
if (controller.task) {
|
||||
const currentMode = await controller.getCurrentMode()
|
||||
controller.task.api = buildApiHandler(apiConfiguration, currentMode)
|
||||
controller.task.api = buildApiHandler(apiConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
},
|
||||
}),
|
||||
)
|
||||
).selectedOption
|
||||
)?.selectedOption
|
||||
|
||||
// Default VS Code Cancel button returns `undefined` - don't delete anything
|
||||
if (userChoice === undefined) {
|
||||
@@ -67,15 +67,17 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
} else {
|
||||
// No favorited tasks found - show warning and ask user what to do
|
||||
const answer = (
|
||||
await getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message: "No favorited tasks found. Would you like to delete all tasks anyway?",
|
||||
options: {
|
||||
modal: true,
|
||||
items: ["Delete All Tasks"],
|
||||
},
|
||||
})
|
||||
).selectedOption
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message: "No favorited tasks found. Would you like to delete all tasks anyway?",
|
||||
options: {
|
||||
modal: true,
|
||||
items: ["Delete All Tasks"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
|
||||
// User cancelled - don't delete anything
|
||||
if (answer === undefined) {
|
||||
@@ -103,10 +105,12 @@ export async function deleteAllTaskHistory(controller: Controller): Promise<Dele
|
||||
await fs.rm(checkpointsDirPath, { recursive: true, force: true })
|
||||
}
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Encountered error while deleting task history, there may be some files left behind. Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Update webview
|
||||
|
||||
@@ -28,11 +28,13 @@ export const deleteTasksWithIds: TaskMethodHandler = async (
|
||||
? "Are you sure you want to delete this task? This action cannot be undone."
|
||||
: `Are you sure you want to delete these ${taskCount} tasks? This action cannot be undone.`
|
||||
|
||||
const userChoice = await getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
options: { modal: true, items: ["Delete"] },
|
||||
})
|
||||
const userChoice = await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.WARNING,
|
||||
message,
|
||||
options: { modal: true, items: ["Delete"] },
|
||||
}),
|
||||
)
|
||||
|
||||
if (userChoice === undefined) {
|
||||
return Empty.create()
|
||||
|
||||
@@ -30,77 +30,25 @@ export async function initializeWebview(controller: Controller, request: EmptyRe
|
||||
handleModelsServiceRequest(controller, "refreshOpenRouterModels", EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
// 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, planActSeparateModelsSetting } = await getAllExtensionState(controller.context)
|
||||
const currentMode = await controller.getCurrentMode()
|
||||
|
||||
if (planActSeparateModelsSetting) {
|
||||
// Separate models: update only current mode
|
||||
const modelIdField = currentMode === "plan" ? "planModeOpenRouterModelId" : "actModeOpenRouterModelId"
|
||||
const modelInfoField = currentMode === "plan" ? "planModeOpenRouterModelInfo" : "actModeOpenRouterModelInfo"
|
||||
const modelId = apiConfiguration[modelIdField]
|
||||
|
||||
if (modelId && response.models[modelId]) {
|
||||
await updateGlobalState(controller.context, modelInfoField, response.models[modelId])
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
} else {
|
||||
// Shared models: update both plan and act modes
|
||||
const planModelId = apiConfiguration.planModeOpenRouterModelId
|
||||
const actModelId = apiConfiguration.actModeOpenRouterModelId
|
||||
|
||||
// Update plan mode model info if we have a model ID
|
||||
if (planModelId && response.models[planModelId]) {
|
||||
await updateGlobalState(controller.context, "planModeOpenRouterModelInfo", response.models[planModelId])
|
||||
}
|
||||
|
||||
// Update act mode model info if we have a model ID
|
||||
if (actModelId && response.models[actModelId]) {
|
||||
await updateGlobalState(controller.context, "actModeOpenRouterModelInfo", response.models[actModelId])
|
||||
}
|
||||
|
||||
// Post state update if we updated any model info
|
||||
if ((planModelId && response.models[planModelId]) || (actModelId && response.models[actModelId])) {
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
const { apiConfiguration } = await getAllExtensionState(controller.context)
|
||||
if (apiConfiguration.openRouterModelId && response.models[apiConfiguration.openRouterModelId]) {
|
||||
await updateGlobalState(
|
||||
controller.context,
|
||||
"openRouterModelInfo",
|
||||
response.models[apiConfiguration.openRouterModelId],
|
||||
)
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
handleModelsServiceRequest(controller, "refreshGroqModels", EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
// Update model info in state for Groq (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, planActSeparateModelsSetting } = await getAllExtensionState(controller.context)
|
||||
const currentMode = await controller.getCurrentMode()
|
||||
|
||||
if (planActSeparateModelsSetting) {
|
||||
// Separate models: update only current mode
|
||||
const modelIdField = currentMode === "plan" ? "planModeGroqModelId" : "actModeGroqModelId"
|
||||
const modelInfoField = currentMode === "plan" ? "planModeGroqModelInfo" : "actModeGroqModelInfo"
|
||||
const modelId = apiConfiguration[modelIdField]
|
||||
|
||||
if (modelId && response.models[modelId]) {
|
||||
await updateGlobalState(controller.context, modelInfoField, response.models[modelId])
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
} else {
|
||||
// Shared models: update both plan and act modes
|
||||
const planModelId = apiConfiguration.planModeGroqModelId
|
||||
const actModelId = apiConfiguration.actModeGroqModelId
|
||||
|
||||
// Update plan mode model info if we have a model ID
|
||||
if (planModelId && response.models[planModelId]) {
|
||||
await updateGlobalState(controller.context, "planModeGroqModelInfo", response.models[planModelId])
|
||||
}
|
||||
|
||||
// Update act mode model info if we have a model ID
|
||||
if (actModelId && response.models[actModelId]) {
|
||||
await updateGlobalState(controller.context, "actModeGroqModelInfo", response.models[actModelId])
|
||||
}
|
||||
|
||||
// Post state update if we updated any model info
|
||||
if ((planModelId && response.models[planModelId]) || (actModelId && response.models[actModelId])) {
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
// update model info in state for Groq
|
||||
const { apiConfiguration } = await getAllExtensionState(controller.context)
|
||||
if (apiConfiguration.groqModelId && response.models[apiConfiguration.groqModelId]) {
|
||||
await updateGlobalState(controller.context, "groqModelInfo", response.models[apiConfiguration.groqModelId])
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as vscode from "vscode"
|
||||
import type { Controller } from "../index"
|
||||
import type { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { Empty } from "../../../shared/proto/common"
|
||||
import { telemetryService } from "../../../services/posthog/telemetry/TelemetryService"
|
||||
|
||||
/**
|
||||
* Opens the Cline walkthrough in VSCode
|
||||
* @param controller The controller instance
|
||||
* @param request Empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function openWalkthrough(controller: Controller, request: EmptyRequest): Promise<Empty> {
|
||||
try {
|
||||
await vscode.commands.executeCommand("workbench.action.openWalkthrough", "saoudrizwan.claude-dev#ClineWalkthrough")
|
||||
telemetryService.captureButtonClick("webview_openWalkthrough")
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
console.error(`Failed to open walkthrough: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -78,10 +78,12 @@ export async function parseMentions(
|
||||
await urlContentFetcher.launchBrowser()
|
||||
} catch (error) {
|
||||
launchBrowserError = error
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${urlMention}: ${error.message}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${urlMention}: ${error.message}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +100,12 @@ export async function parseMentions(
|
||||
const markdown = await urlContentFetcher.urlToMarkdown(mention)
|
||||
result = markdown
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${mention}: ${error.message}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error fetching content for ${mention}: ${error.message}`,
|
||||
}),
|
||||
)
|
||||
result = `Error fetching content: ${error.message}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as diff from "diff"
|
||||
import * as path from "path"
|
||||
import { ClineIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/ClineIgnoreController"
|
||||
import { Mode } from "@/shared/ChatSettings"
|
||||
|
||||
export const formatResponse = {
|
||||
duplicateFileReadNotice: () =>
|
||||
@@ -148,7 +147,7 @@ Otherwise, if you have not completed the task and do not need additional informa
|
||||
},
|
||||
|
||||
taskResumption: (
|
||||
mode: Mode,
|
||||
mode: "plan" | "act",
|
||||
agoText: string,
|
||||
cwd: string,
|
||||
wasRecent: boolean | 0 | undefined,
|
||||
|
||||
@@ -21,7 +21,6 @@ export type SecretKey =
|
||||
| "asksageApiKey"
|
||||
| "xaiApiKey"
|
||||
| "moonshotApiKey"
|
||||
| "huggingFaceApiKey"
|
||||
| "nebiusApiKey"
|
||||
| "sambanovaApiKey"
|
||||
| "cerebrasApiKey"
|
||||
@@ -43,9 +42,12 @@ export type GlobalStateKey =
|
||||
| "lastShownAnnouncementId"
|
||||
| "taskHistory"
|
||||
| "openAiBaseUrl"
|
||||
| "openAiModelId"
|
||||
| "openAiModelInfo"
|
||||
| "openAiHeaders"
|
||||
| "ollamaBaseUrl"
|
||||
| "ollamaApiOptionsCtxNum"
|
||||
| "lmStudioModelId"
|
||||
| "lmStudioBaseUrl"
|
||||
| "anthropicBaseUrl"
|
||||
| "geminiBaseUrl"
|
||||
@@ -85,55 +87,38 @@ export type GlobalStateKey =
|
||||
// Settings around plan/act and ephemeral model configuration
|
||||
| "chatSettings"
|
||||
| "mode"
|
||||
// Plan mode configurations
|
||||
| "planModeApiProvider"
|
||||
| "planModeApiModelId"
|
||||
| "planModeThinkingBudgetTokens"
|
||||
| "planModeReasoningEffort"
|
||||
| "planModeVsCodeLmModelSelector"
|
||||
| "planModeAwsBedrockCustomSelected"
|
||||
| "planModeAwsBedrockCustomModelBaseId"
|
||||
| "planModeOpenRouterModelId"
|
||||
| "planModeOpenRouterModelInfo"
|
||||
| "planModeOpenAiModelId"
|
||||
| "planModeOpenAiModelInfo"
|
||||
| "planModeOllamaModelId"
|
||||
| "planModeLmStudioModelId"
|
||||
| "planModeLiteLlmModelId"
|
||||
| "planModeLiteLlmModelInfo"
|
||||
| "planModeRequestyModelId"
|
||||
| "planModeRequestyModelInfo"
|
||||
| "planModeTogetherModelId"
|
||||
| "planModeFireworksModelId"
|
||||
| "planModeSapAiCoreModelId"
|
||||
| "planModeGroqModelId"
|
||||
| "planModeGroqModelInfo"
|
||||
| "planModeHuggingFaceModelId"
|
||||
| "planModeHuggingFaceModelInfo"
|
||||
// Act mode configurations
|
||||
| "actModeApiProvider"
|
||||
| "actModeApiModelId"
|
||||
| "actModeThinkingBudgetTokens"
|
||||
| "actModeReasoningEffort"
|
||||
| "actModeVsCodeLmModelSelector"
|
||||
| "actModeAwsBedrockCustomSelected"
|
||||
| "actModeAwsBedrockCustomModelBaseId"
|
||||
| "actModeOpenRouterModelId"
|
||||
| "actModeOpenRouterModelInfo"
|
||||
| "actModeOpenAiModelId"
|
||||
| "actModeOpenAiModelInfo"
|
||||
| "actModeOllamaModelId"
|
||||
| "actModeLmStudioModelId"
|
||||
| "actModeLiteLlmModelId"
|
||||
| "actModeLiteLlmModelInfo"
|
||||
| "actModeRequestyModelId"
|
||||
| "actModeRequestyModelInfo"
|
||||
| "actModeTogetherModelId"
|
||||
| "actModeFireworksModelId"
|
||||
| "actModeSapAiCoreModelId"
|
||||
| "actModeGroqModelId"
|
||||
| "actModeGroqModelInfo"
|
||||
| "actModeHuggingFaceModelId"
|
||||
| "actModeHuggingFaceModelInfo"
|
||||
// Current active model configuration (per workspace)
|
||||
| "apiProvider"
|
||||
| "apiModelId"
|
||||
| "thinkingBudgetTokens"
|
||||
| "reasoningEffort"
|
||||
| "vsCodeLmModelSelector"
|
||||
| "awsBedrockCustomSelected"
|
||||
| "awsBedrockCustomModelBaseId"
|
||||
| "openRouterModelId"
|
||||
| "openRouterModelInfo"
|
||||
| "openAiModelId"
|
||||
| "openAiModelInfo"
|
||||
| "ollamaModelId"
|
||||
| "lmStudioModelId"
|
||||
| "liteLlmModelId"
|
||||
| "liteLlmModelInfo"
|
||||
| "requestyModelId"
|
||||
| "requestyModelInfo"
|
||||
| "togetherModelId"
|
||||
| "fireworksModelId"
|
||||
| "sapAiCoreModelId"
|
||||
// Previous mode saved configurations (per workspace)
|
||||
| "previousModeApiProvider"
|
||||
| "previousModeModelId"
|
||||
| "previousModeModelInfo"
|
||||
| "previousModeVsCodeLmModelSelector"
|
||||
| "previousModeThinkingBudgetTokens"
|
||||
| "previousModeReasoningEffort"
|
||||
| "previousModeAwsBedrockCustomSelected"
|
||||
| "previousModeAwsBedrockCustomModelBaseId"
|
||||
| "previousModeSapAiCoreModelId"
|
||||
| "groqModelId"
|
||||
| "groqModelInfo"
|
||||
|
||||
export type LocalStateKey = "localClineRulesToggles" | "localCursorRulesToggles" | "localWindsurfRulesToggles" | "workflowToggles"
|
||||
|
||||
@@ -32,10 +32,6 @@ export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionC
|
||||
"togetherModelId",
|
||||
"fireworksModelId",
|
||||
"sapAiCoreModelId",
|
||||
"groqModelId",
|
||||
"groqModelInfo",
|
||||
"huggingFaceModelId",
|
||||
"huggingFaceModelInfo",
|
||||
|
||||
// Previous mode settings
|
||||
"previousModeApiProvider",
|
||||
@@ -57,8 +53,8 @@ export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionC
|
||||
if (workspaceValue !== undefined && globalValue === undefined) {
|
||||
console.log(`[Storage Migration] migrating key: ${key} to global storage. Current value: ${workspaceValue}`)
|
||||
|
||||
// Move to global storage using raw VSCode method to avoid type errors
|
||||
await context.globalState.update(key, workspaceValue)
|
||||
// Move to global storage
|
||||
await updateGlobalState(context, key as GlobalStateKey, workspaceValue)
|
||||
// Remove from workspace storage
|
||||
await context.workspaceState.update(key, undefined)
|
||||
const newWorkspaceValue = await context.workspaceState.get(key)
|
||||
@@ -173,375 +169,6 @@ export async function migrateModeFromWorkspaceStorageToControllerState(context:
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateLegacyApiConfigurationToModeSpecific(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
// Check if migration is needed - if planModeApiProvider already exists, skip migration
|
||||
const planModeApiProvider = await context.globalState.get("planModeApiProvider")
|
||||
if (planModeApiProvider !== undefined) {
|
||||
console.log("Legacy API configuration migration already completed, skipping...")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Starting legacy API configuration migration to mode-specific keys...")
|
||||
|
||||
// Get the planActSeparateModelsSetting to determine migration strategy
|
||||
const planActSeparateModelsSetting = (await context.globalState.get("planActSeparateModelsSetting")) as
|
||||
| boolean
|
||||
| undefined
|
||||
|
||||
// Read legacy values directly
|
||||
const apiProvider = await context.globalState.get("apiProvider")
|
||||
const apiModelId = await context.globalState.get("apiModelId")
|
||||
const thinkingBudgetTokens = await context.globalState.get("thinkingBudgetTokens")
|
||||
const reasoningEffort = await context.globalState.get("reasoningEffort")
|
||||
const vsCodeLmModelSelector = await context.globalState.get("vsCodeLmModelSelector")
|
||||
const awsBedrockCustomSelected = await context.globalState.get("awsBedrockCustomSelected")
|
||||
const awsBedrockCustomModelBaseId = await context.globalState.get("awsBedrockCustomModelBaseId")
|
||||
const openRouterModelId = await context.globalState.get("openRouterModelId")
|
||||
const openRouterModelInfo = await context.globalState.get("openRouterModelInfo")
|
||||
const openAiModelId = await context.globalState.get("openAiModelId")
|
||||
const openAiModelInfo = await context.globalState.get("openAiModelInfo")
|
||||
const ollamaModelId = await context.globalState.get("ollamaModelId")
|
||||
const lmStudioModelId = await context.globalState.get("lmStudioModelId")
|
||||
const liteLlmModelId = await context.globalState.get("liteLlmModelId")
|
||||
const liteLlmModelInfo = await context.globalState.get("liteLlmModelInfo")
|
||||
const requestyModelId = await context.globalState.get("requestyModelId")
|
||||
const requestyModelInfo = await context.globalState.get("requestyModelInfo")
|
||||
const togetherModelId = await context.globalState.get("togetherModelId")
|
||||
const fireworksModelId = await context.globalState.get("fireworksModelId")
|
||||
const sapAiCoreModelId = await context.globalState.get("sapAiCoreModelId")
|
||||
const groqModelId = await context.globalState.get("groqModelId")
|
||||
const groqModelInfo = await context.globalState.get("groqModelInfo")
|
||||
const huggingFaceModelId = await context.globalState.get("huggingFaceModelId")
|
||||
const huggingFaceModelInfo = await context.globalState.get("huggingFaceModelInfo")
|
||||
|
||||
// Read previous mode values
|
||||
const previousModeApiProvider = await context.globalState.get("previousModeApiProvider")
|
||||
const previousModeModelId = await context.globalState.get("previousModeModelId")
|
||||
const previousModeModelInfo = await context.globalState.get("previousModeModelInfo")
|
||||
const previousModeVsCodeLmModelSelector = await context.globalState.get("previousModeVsCodeLmModelSelector")
|
||||
const previousModeThinkingBudgetTokens = await context.globalState.get("previousModeThinkingBudgetTokens")
|
||||
const previousModeReasoningEffort = await context.globalState.get("previousModeReasoningEffort")
|
||||
const previousModeAwsBedrockCustomSelected = await context.globalState.get("previousModeAwsBedrockCustomSelected")
|
||||
const previousModeAwsBedrockCustomModelBaseId = await context.globalState.get("previousModeAwsBedrockCustomModelBaseId")
|
||||
const previousModeSapAiCoreModelId = await context.globalState.get("previousModeSapAiCoreModelId")
|
||||
|
||||
// Migrate based on planActSeparateModelsSetting
|
||||
if (planActSeparateModelsSetting === false) {
|
||||
console.log("Migrating with separate models DISABLED - using current values for both modes")
|
||||
|
||||
// Use current values for both plan and act modes
|
||||
if (apiProvider !== undefined) {
|
||||
await context.globalState.update("planModeApiProvider", apiProvider)
|
||||
await context.globalState.update("actModeApiProvider", apiProvider)
|
||||
}
|
||||
if (apiModelId !== undefined) {
|
||||
await context.globalState.update("planModeApiModelId", apiModelId)
|
||||
await context.globalState.update("actModeApiModelId", apiModelId)
|
||||
}
|
||||
if (thinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("planModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
await context.globalState.update("actModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
}
|
||||
if (reasoningEffort !== undefined) {
|
||||
await context.globalState.update("planModeReasoningEffort", reasoningEffort)
|
||||
await context.globalState.update("actModeReasoningEffort", reasoningEffort)
|
||||
}
|
||||
if (vsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("planModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
await context.globalState.update("actModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
}
|
||||
if (awsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await context.globalState.update("actModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
}
|
||||
if (awsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
}
|
||||
if (openRouterModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelId", openRouterModelId)
|
||||
await context.globalState.update("actModeOpenRouterModelId", openRouterModelId)
|
||||
}
|
||||
if (openRouterModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
await context.globalState.update("actModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
}
|
||||
if (openAiModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelId", openAiModelId)
|
||||
await context.globalState.update("actModeOpenAiModelId", openAiModelId)
|
||||
}
|
||||
if (openAiModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelInfo", openAiModelInfo)
|
||||
await context.globalState.update("actModeOpenAiModelInfo", openAiModelInfo)
|
||||
}
|
||||
if (ollamaModelId !== undefined) {
|
||||
await context.globalState.update("planModeOllamaModelId", ollamaModelId)
|
||||
await context.globalState.update("actModeOllamaModelId", ollamaModelId)
|
||||
}
|
||||
if (lmStudioModelId !== undefined) {
|
||||
await context.globalState.update("planModeLmStudioModelId", lmStudioModelId)
|
||||
await context.globalState.update("actModeLmStudioModelId", lmStudioModelId)
|
||||
}
|
||||
if (liteLlmModelId !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelId", liteLlmModelId)
|
||||
await context.globalState.update("actModeLiteLlmModelId", liteLlmModelId)
|
||||
}
|
||||
if (liteLlmModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
await context.globalState.update("actModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
}
|
||||
if (requestyModelId !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelId", requestyModelId)
|
||||
await context.globalState.update("actModeRequestyModelId", requestyModelId)
|
||||
}
|
||||
if (requestyModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelInfo", requestyModelInfo)
|
||||
await context.globalState.update("actModeRequestyModelInfo", requestyModelInfo)
|
||||
}
|
||||
if (togetherModelId !== undefined) {
|
||||
await context.globalState.update("planModeTogetherModelId", togetherModelId)
|
||||
await context.globalState.update("actModeTogetherModelId", togetherModelId)
|
||||
}
|
||||
if (fireworksModelId !== undefined) {
|
||||
await context.globalState.update("planModeFireworksModelId", fireworksModelId)
|
||||
await context.globalState.update("actModeFireworksModelId", fireworksModelId)
|
||||
}
|
||||
if (sapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("planModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
await context.globalState.update("actModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
}
|
||||
if (groqModelId !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelId", groqModelId)
|
||||
await context.globalState.update("actModeGroqModelId", groqModelId)
|
||||
}
|
||||
if (groqModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelInfo", groqModelInfo)
|
||||
await context.globalState.update("actModeGroqModelInfo", groqModelInfo)
|
||||
}
|
||||
if (huggingFaceModelId !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelId", huggingFaceModelId)
|
||||
await context.globalState.update("actModeHuggingFaceModelId", huggingFaceModelId)
|
||||
}
|
||||
if (huggingFaceModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
await context.globalState.update("actModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
}
|
||||
} else {
|
||||
console.log("Migrating with separate models ENABLED - using current->plan, previous->act")
|
||||
|
||||
// Use current values for plan mode
|
||||
if (apiProvider !== undefined) {
|
||||
await context.globalState.update("planModeApiProvider", apiProvider)
|
||||
}
|
||||
if (apiModelId !== undefined) {
|
||||
await context.globalState.update("planModeApiModelId", apiModelId)
|
||||
}
|
||||
if (thinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("planModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
}
|
||||
if (reasoningEffort !== undefined) {
|
||||
await context.globalState.update("planModeReasoningEffort", reasoningEffort)
|
||||
}
|
||||
if (vsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("planModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
}
|
||||
if (awsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
}
|
||||
if (awsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("planModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
}
|
||||
if (openRouterModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelId", openRouterModelId)
|
||||
}
|
||||
if (openRouterModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
}
|
||||
if (openAiModelId !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelId", openAiModelId)
|
||||
}
|
||||
if (openAiModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeOpenAiModelInfo", openAiModelInfo)
|
||||
}
|
||||
if (ollamaModelId !== undefined) {
|
||||
await context.globalState.update("planModeOllamaModelId", ollamaModelId)
|
||||
}
|
||||
if (lmStudioModelId !== undefined) {
|
||||
await context.globalState.update("planModeLmStudioModelId", lmStudioModelId)
|
||||
}
|
||||
if (liteLlmModelId !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelId", liteLlmModelId)
|
||||
}
|
||||
if (liteLlmModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
}
|
||||
if (requestyModelId !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelId", requestyModelId)
|
||||
}
|
||||
if (requestyModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeRequestyModelInfo", requestyModelInfo)
|
||||
}
|
||||
if (togetherModelId !== undefined) {
|
||||
await context.globalState.update("planModeTogetherModelId", togetherModelId)
|
||||
}
|
||||
if (fireworksModelId !== undefined) {
|
||||
await context.globalState.update("planModeFireworksModelId", fireworksModelId)
|
||||
}
|
||||
if (sapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("planModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
}
|
||||
if (groqModelId !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelId", groqModelId)
|
||||
}
|
||||
if (groqModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeGroqModelInfo", groqModelInfo)
|
||||
}
|
||||
if (huggingFaceModelId !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelId", huggingFaceModelId)
|
||||
}
|
||||
if (huggingFaceModelInfo !== undefined) {
|
||||
await context.globalState.update("planModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
}
|
||||
|
||||
// Use previous values for act mode (with fallback to current values)
|
||||
if (previousModeApiProvider !== undefined) {
|
||||
await context.globalState.update("actModeApiProvider", previousModeApiProvider)
|
||||
} else if (apiProvider !== undefined) {
|
||||
await context.globalState.update("actModeApiProvider", apiProvider)
|
||||
}
|
||||
if (previousModeModelId !== undefined) {
|
||||
await context.globalState.update("actModeApiModelId", previousModeModelId)
|
||||
} else if (apiModelId !== undefined) {
|
||||
await context.globalState.update("actModeApiModelId", apiModelId)
|
||||
}
|
||||
if (previousModeThinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("actModeThinkingBudgetTokens", previousModeThinkingBudgetTokens)
|
||||
} else if (thinkingBudgetTokens !== undefined) {
|
||||
await context.globalState.update("actModeThinkingBudgetTokens", thinkingBudgetTokens)
|
||||
}
|
||||
if (previousModeReasoningEffort !== undefined) {
|
||||
await context.globalState.update("actModeReasoningEffort", previousModeReasoningEffort)
|
||||
} else if (reasoningEffort !== undefined) {
|
||||
await context.globalState.update("actModeReasoningEffort", reasoningEffort)
|
||||
}
|
||||
if (previousModeVsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("actModeVsCodeLmModelSelector", previousModeVsCodeLmModelSelector)
|
||||
} else if (vsCodeLmModelSelector !== undefined) {
|
||||
await context.globalState.update("actModeVsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
}
|
||||
if (previousModeAwsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomSelected", previousModeAwsBedrockCustomSelected)
|
||||
} else if (awsBedrockCustomSelected !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
}
|
||||
if (previousModeAwsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", previousModeAwsBedrockCustomModelBaseId)
|
||||
} else if (awsBedrockCustomModelBaseId !== undefined) {
|
||||
await context.globalState.update("actModeAwsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
}
|
||||
if (previousModeSapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("actModeSapAiCoreModelId", previousModeSapAiCoreModelId)
|
||||
} else if (sapAiCoreModelId !== undefined) {
|
||||
await context.globalState.update("actModeSapAiCoreModelId", sapAiCoreModelId)
|
||||
}
|
||||
|
||||
// For fields without previous variants, use current values for act mode
|
||||
if (previousModeModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeOpenRouterModelInfo", previousModeModelInfo)
|
||||
} else if (openRouterModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeOpenRouterModelInfo", openRouterModelInfo)
|
||||
}
|
||||
if (openRouterModelId !== undefined) {
|
||||
await context.globalState.update("actModeOpenRouterModelId", openRouterModelId)
|
||||
}
|
||||
if (openAiModelId !== undefined) {
|
||||
await context.globalState.update("actModeOpenAiModelId", openAiModelId)
|
||||
}
|
||||
if (openAiModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeOpenAiModelInfo", openAiModelInfo)
|
||||
}
|
||||
if (ollamaModelId !== undefined) {
|
||||
await context.globalState.update("actModeOllamaModelId", ollamaModelId)
|
||||
}
|
||||
if (lmStudioModelId !== undefined) {
|
||||
await context.globalState.update("actModeLmStudioModelId", lmStudioModelId)
|
||||
}
|
||||
if (liteLlmModelId !== undefined) {
|
||||
await context.globalState.update("actModeLiteLlmModelId", liteLlmModelId)
|
||||
}
|
||||
if (liteLlmModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeLiteLlmModelInfo", liteLlmModelInfo)
|
||||
}
|
||||
if (requestyModelId !== undefined) {
|
||||
await context.globalState.update("actModeRequestyModelId", requestyModelId)
|
||||
}
|
||||
if (requestyModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeRequestyModelInfo", requestyModelInfo)
|
||||
}
|
||||
if (togetherModelId !== undefined) {
|
||||
await context.globalState.update("actModeTogetherModelId", togetherModelId)
|
||||
}
|
||||
if (fireworksModelId !== undefined) {
|
||||
await context.globalState.update("actModeFireworksModelId", fireworksModelId)
|
||||
}
|
||||
if (groqModelId !== undefined) {
|
||||
await context.globalState.update("actModeGroqModelId", groqModelId)
|
||||
}
|
||||
if (groqModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeGroqModelInfo", groqModelInfo)
|
||||
}
|
||||
if (huggingFaceModelId !== undefined) {
|
||||
await context.globalState.update("actModeHuggingFaceModelId", huggingFaceModelId)
|
||||
}
|
||||
if (huggingFaceModelInfo !== undefined) {
|
||||
await context.globalState.update("actModeHuggingFaceModelInfo", huggingFaceModelInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up legacy keys after successful migration
|
||||
console.log("Cleaning up legacy keys...")
|
||||
await context.globalState.update("apiProvider", undefined)
|
||||
await context.globalState.update("apiModelId", undefined)
|
||||
await context.globalState.update("thinkingBudgetTokens", undefined)
|
||||
await context.globalState.update("reasoningEffort", undefined)
|
||||
await context.globalState.update("vsCodeLmModelSelector", undefined)
|
||||
await context.globalState.update("awsBedrockCustomSelected", undefined)
|
||||
await context.globalState.update("awsBedrockCustomModelBaseId", undefined)
|
||||
await context.globalState.update("openRouterModelId", undefined)
|
||||
await context.globalState.update("openRouterModelInfo", undefined)
|
||||
await context.globalState.update("openAiModelId", undefined)
|
||||
await context.globalState.update("openAiModelInfo", undefined)
|
||||
await context.globalState.update("ollamaModelId", undefined)
|
||||
await context.globalState.update("lmStudioModelId", undefined)
|
||||
await context.globalState.update("liteLlmModelId", undefined)
|
||||
await context.globalState.update("liteLlmModelInfo", undefined)
|
||||
await context.globalState.update("requestyModelId", undefined)
|
||||
await context.globalState.update("requestyModelInfo", undefined)
|
||||
await context.globalState.update("togetherModelId", undefined)
|
||||
await context.globalState.update("fireworksModelId", undefined)
|
||||
await context.globalState.update("sapAiCoreModelId", undefined)
|
||||
await context.globalState.update("groqModelId", undefined)
|
||||
await context.globalState.update("groqModelInfo", undefined)
|
||||
await context.globalState.update("huggingFaceModelId", undefined)
|
||||
await context.globalState.update("huggingFaceModelInfo", undefined)
|
||||
await context.globalState.update("previousModeApiProvider", undefined)
|
||||
await context.globalState.update("previousModeModelId", undefined)
|
||||
await context.globalState.update("previousModeModelInfo", undefined)
|
||||
await context.globalState.update("previousModeVsCodeLmModelSelector", undefined)
|
||||
await context.globalState.update("previousModeThinkingBudgetTokens", undefined)
|
||||
await context.globalState.update("previousModeReasoningEffort", undefined)
|
||||
await context.globalState.update("previousModeAwsBedrockCustomSelected", undefined)
|
||||
await context.globalState.update("previousModeAwsBedrockCustomModelBaseId", undefined)
|
||||
await context.globalState.update("previousModeSapAiCoreModelId", undefined)
|
||||
|
||||
console.log("Successfully migrated legacy API configuration to mode-specific keys")
|
||||
} catch (error) {
|
||||
console.error("Failed to migrate legacy API configuration to mode-specific keys:", error)
|
||||
// Continue execution - migration failure shouldn't break extension startup
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateWelcomeViewCompleted(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
// Check if welcomeViewCompleted is already set
|
||||
@@ -563,10 +190,8 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
|
||||
config.awsRegion,
|
||||
config.vertexProjectId,
|
||||
config.openAiApiKey,
|
||||
config.planModeOllamaModelId,
|
||||
config.planModeLmStudioModelId,
|
||||
config.actModeOllamaModelId,
|
||||
config.actModeLmStudioModelId,
|
||||
config.ollamaModelId,
|
||||
config.lmStudioModelId,
|
||||
config.liteLlmApiKey,
|
||||
config.geminiApiKey,
|
||||
config.openAiNativeApiKey,
|
||||
@@ -576,8 +201,7 @@ export async function migrateWelcomeViewCompleted(context: vscode.ExtensionConte
|
||||
config.qwenApiKey,
|
||||
config.doubaoApiKey,
|
||||
config.mistralApiKey,
|
||||
config.planModeVsCodeLmModelSelector,
|
||||
config.actModeVsCodeLmModelSelector,
|
||||
config.vsCodeLmModelSelector,
|
||||
config.clineAccountId,
|
||||
config.asksageApiKey,
|
||||
config.xaiApiKey,
|
||||
|
||||
+144
-260
@@ -1,5 +1,5 @@
|
||||
import * as vscode from "vscode"
|
||||
import { DEFAULT_CHAT_SETTINGS, Mode } from "@shared/ChatSettings"
|
||||
import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { GlobalStateKey, LocalStateKey, SecretKey } from "./state-keys"
|
||||
@@ -170,7 +170,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
groqApiKey,
|
||||
moonshotApiKey,
|
||||
nebiusApiKey,
|
||||
huggingFaceApiKey,
|
||||
planActSeparateModelsSettingRaw,
|
||||
favoritedModelIds,
|
||||
globalClineRulesToggles,
|
||||
@@ -190,6 +189,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
claudeCodePath,
|
||||
groqModelId,
|
||||
groqModelInfo,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "isNewUser") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "welcomeViewCompleted") as Promise<boolean | undefined>,
|
||||
@@ -249,7 +250,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getSecret(context, "groqApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "moonshotApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "nebiusApiKey") as Promise<string | undefined>,
|
||||
getSecret(context, "huggingFaceApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
|
||||
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
|
||||
@@ -269,6 +269,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "sapAiCoreTokenUrl") as Promise<string | undefined>,
|
||||
getGlobalState(context, "sapAiResourceGroup") as Promise<string | undefined>,
|
||||
getGlobalState(context, "claudeCodePath") as Promise<string | undefined>,
|
||||
getGlobalState(context, "groqModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "groqModelInfo") as Promise<ModelInfo | undefined>,
|
||||
])
|
||||
|
||||
const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
|
||||
@@ -277,115 +279,74 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
const [
|
||||
chatSettings,
|
||||
currentMode,
|
||||
// Plan mode configurations
|
||||
planModeApiProvider,
|
||||
planModeApiModelId,
|
||||
planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId,
|
||||
planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo,
|
||||
planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo,
|
||||
planModeOllamaModelId,
|
||||
planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo,
|
||||
planModeRequestyModelId,
|
||||
planModeRequestyModelInfo,
|
||||
planModeTogetherModelId,
|
||||
planModeFireworksModelId,
|
||||
planModeSapAiCoreModelId,
|
||||
planModeGroqModelId,
|
||||
planModeGroqModelInfo,
|
||||
planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo,
|
||||
// Act mode configurations
|
||||
actModeApiProvider,
|
||||
actModeApiModelId,
|
||||
actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId,
|
||||
actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo,
|
||||
actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo,
|
||||
actModeOllamaModelId,
|
||||
actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo,
|
||||
actModeRequestyModelId,
|
||||
actModeRequestyModelInfo,
|
||||
actModeTogetherModelId,
|
||||
actModeFireworksModelId,
|
||||
actModeSapAiCoreModelId,
|
||||
actModeGroqModelId,
|
||||
actModeGroqModelInfo,
|
||||
actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo,
|
||||
storedApiProvider,
|
||||
apiModelId,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
vsCodeLmModelSelector,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
ollamaModelId,
|
||||
lmStudioModelId,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherModelId,
|
||||
fireworksModelId,
|
||||
previousModeApiProvider,
|
||||
previousModeModelId,
|
||||
previousModeModelInfo,
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
previousModeSapAiCoreModelId,
|
||||
sapAiCoreModelId,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "chatSettings") as Promise<StoredChatSettings | undefined>,
|
||||
getGlobalState(context, "mode") as Promise<Mode | undefined>,
|
||||
// Plan mode configurations
|
||||
getGlobalState(context, "planModeApiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "planModeApiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "planModeReasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "planModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "planModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "planModeOpenRouterModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeOpenRouterModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "planModeOpenAiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeOpenAiModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "planModeOllamaModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeLmStudioModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeLiteLlmModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeLiteLlmModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "planModeRequestyModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeRequestyModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "planModeTogetherModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeFireworksModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeSapAiCoreModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeGroqModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeGroqModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "planModeHuggingFaceModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "planModeHuggingFaceModelInfo") as Promise<ModelInfo | undefined>,
|
||||
// Act mode configurations
|
||||
getGlobalState(context, "actModeApiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "actModeApiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "actModeReasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "actModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "actModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "actModeOpenRouterModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeOpenRouterModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "actModeOpenAiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeOpenAiModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "actModeOllamaModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeLmStudioModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeLiteLlmModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeLiteLlmModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "actModeRequestyModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeRequestyModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "actModeTogetherModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeFireworksModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeSapAiCoreModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeGroqModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeGroqModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "actModeHuggingFaceModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "actModeHuggingFaceModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "mode") as Promise<"plan" | "act" | undefined>,
|
||||
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "reasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "openRouterModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openRouterModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "openAiModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openAiModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "ollamaModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "lmStudioModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "liteLlmModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "liteLlmModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "requestyModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "requestyModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "togetherModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "fireworksModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeApiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "previousModeModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeModelInfo") as Promise<ModelInfo | undefined>,
|
||||
getGlobalState(context, "previousModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "previousModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "previousModeReasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "previousModeSapAiCoreModelId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "sapAiCoreModelId") as Promise<string | undefined>,
|
||||
])
|
||||
|
||||
const processingStart = performance.now()
|
||||
let apiProvider: ApiProvider
|
||||
if (planModeApiProvider) {
|
||||
apiProvider = planModeApiProvider
|
||||
if (storedApiProvider) {
|
||||
// Use the explicitly stored provider - this respects user's selection
|
||||
apiProvider = storedApiProvider
|
||||
} else {
|
||||
// Either new user or legacy user that doesn't have the apiProvider stored in state
|
||||
// (If they're using OpenRouter or Bedrock, then apiProvider state will exist)
|
||||
@@ -408,7 +369,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
planActSeparateModelsSetting = planActSeparateModelsSettingRaw
|
||||
} else {
|
||||
// default to true for existing users
|
||||
if (planModeApiProvider) {
|
||||
if (storedApiProvider) {
|
||||
planActSeparateModelsSetting = true
|
||||
} else {
|
||||
// default to false for new users
|
||||
@@ -421,6 +382,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
return {
|
||||
apiConfiguration: {
|
||||
apiProvider,
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
clineAccountId,
|
||||
@@ -436,13 +399,19 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
awsBedrockApiKey,
|
||||
awsUseProfile,
|
||||
awsAuthentication,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
openAiApiKey,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
openAiHeaders: openAiHeaders || {},
|
||||
ollamaModelId,
|
||||
ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId,
|
||||
lmStudioBaseUrl,
|
||||
anthropicBaseUrl,
|
||||
geminiApiKey,
|
||||
@@ -450,18 +419,29 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherApiKey,
|
||||
togetherModelId,
|
||||
qwenApiKey,
|
||||
qwenApiLine,
|
||||
moonshotApiLine,
|
||||
doubaoApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openRouterProviderSorting,
|
||||
vsCodeLmModelSelector,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
liteLlmBaseUrl,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
liteLlmApiKey,
|
||||
liteLlmUsePromptCache,
|
||||
fireworksApiKey,
|
||||
fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens,
|
||||
asksageApiKey,
|
||||
@@ -470,6 +450,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
sambanovaApiKey,
|
||||
cerebrasApiKey,
|
||||
groqApiKey,
|
||||
groqModelId,
|
||||
groqModelInfo,
|
||||
moonshotApiKey,
|
||||
nebiusApiKey,
|
||||
favoritedModelIds,
|
||||
@@ -479,55 +461,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
huggingFaceApiKey,
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: planModeApiProvider || apiProvider,
|
||||
planModeApiModelId,
|
||||
planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId,
|
||||
planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo,
|
||||
planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo,
|
||||
planModeOllamaModelId,
|
||||
planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo,
|
||||
planModeRequestyModelId,
|
||||
planModeRequestyModelInfo,
|
||||
planModeTogetherModelId,
|
||||
planModeFireworksModelId,
|
||||
planModeSapAiCoreModelId,
|
||||
planModeGroqModelId,
|
||||
planModeGroqModelInfo,
|
||||
planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo,
|
||||
// Act mode configurations
|
||||
actModeApiProvider: actModeApiProvider || apiProvider,
|
||||
actModeApiModelId,
|
||||
actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId,
|
||||
actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo,
|
||||
actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo,
|
||||
actModeOllamaModelId,
|
||||
actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo,
|
||||
actModeRequestyModelId,
|
||||
actModeRequestyModelInfo,
|
||||
actModeTogetherModelId,
|
||||
actModeFireworksModelId,
|
||||
actModeSapAiCoreModelId,
|
||||
actModeGroqModelId,
|
||||
actModeGroqModelInfo,
|
||||
sapAiCoreModelId,
|
||||
},
|
||||
isNewUser: isNewUser ?? true,
|
||||
welcomeViewCompleted,
|
||||
@@ -543,6 +477,15 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
mode: currentMode || "act", // Merge mode from global state
|
||||
},
|
||||
userInfo,
|
||||
previousModeApiProvider,
|
||||
previousModeModelId,
|
||||
previousModeModelInfo,
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
previousModeSapAiCoreModelId,
|
||||
mcpMarketplaceEnabled: mcpMarketplaceEnabled,
|
||||
mcpDisplayMode: mcpDisplayMode ?? DEFAULT_MCP_DISPLAY_MODE,
|
||||
mcpResponsesCollapsed: mcpResponsesCollapsed,
|
||||
@@ -559,6 +502,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
export async function updateApiConfiguration(context: vscode.ExtensionContext, apiConfiguration: ApiConfiguration) {
|
||||
const {
|
||||
apiProvider,
|
||||
apiModelId,
|
||||
apiKey,
|
||||
openRouterApiKey,
|
||||
awsAccessKey,
|
||||
@@ -572,13 +517,19 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsAuthentication,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
openAiApiKey,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
openAiHeaders,
|
||||
ollamaModelId,
|
||||
ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId,
|
||||
lmStudioBaseUrl,
|
||||
anthropicBaseUrl,
|
||||
geminiApiKey,
|
||||
@@ -586,13 +537,21 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherApiKey,
|
||||
togetherModelId,
|
||||
qwenApiKey,
|
||||
doubaoApiKey,
|
||||
mistralApiKey,
|
||||
azureApiVersion,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openRouterProviderSorting,
|
||||
vsCodeLmModelSelector,
|
||||
liteLlmBaseUrl,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
liteLlmApiKey,
|
||||
liteLlmUsePromptCache,
|
||||
qwenApiLine,
|
||||
@@ -600,14 +559,19 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
asksageApiKey,
|
||||
asksageApiUrl,
|
||||
xaiApiKey,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
clineAccountId,
|
||||
sambanovaApiKey,
|
||||
cerebrasApiKey,
|
||||
groqApiKey,
|
||||
groqModelId,
|
||||
groqModelInfo,
|
||||
moonshotApiKey,
|
||||
nebiusApiKey,
|
||||
favoritedModelIds,
|
||||
fireworksApiKey,
|
||||
fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens,
|
||||
sapAiCoreClientId,
|
||||
@@ -615,113 +579,35 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
sapAiCoreModelId,
|
||||
claudeCodePath,
|
||||
huggingFaceApiKey,
|
||||
// Plan mode configurations
|
||||
planModeApiProvider,
|
||||
planModeApiModelId,
|
||||
planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId,
|
||||
planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo,
|
||||
planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo,
|
||||
planModeOllamaModelId,
|
||||
planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo,
|
||||
planModeRequestyModelId,
|
||||
planModeRequestyModelInfo,
|
||||
planModeTogetherModelId,
|
||||
planModeFireworksModelId,
|
||||
planModeSapAiCoreModelId,
|
||||
planModeGroqModelId,
|
||||
planModeGroqModelInfo,
|
||||
planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo,
|
||||
// Act mode configurations
|
||||
actModeApiProvider,
|
||||
actModeApiModelId,
|
||||
actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId,
|
||||
actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo,
|
||||
actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo,
|
||||
actModeOllamaModelId,
|
||||
actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo,
|
||||
actModeRequestyModelId,
|
||||
actModeRequestyModelInfo,
|
||||
actModeTogetherModelId,
|
||||
actModeFireworksModelId,
|
||||
actModeSapAiCoreModelId,
|
||||
actModeGroqModelId,
|
||||
actModeGroqModelInfo,
|
||||
actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo,
|
||||
} = apiConfiguration
|
||||
|
||||
// OPTIMIZED: Batch all global state updates into 2 operations instead of 47
|
||||
const batchedGlobalUpdates = {
|
||||
// Plan mode configuration updates
|
||||
planModeApiProvider,
|
||||
planModeApiModelId,
|
||||
planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId,
|
||||
planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo,
|
||||
planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo,
|
||||
planModeOllamaModelId,
|
||||
planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo,
|
||||
planModeRequestyModelId,
|
||||
planModeRequestyModelInfo,
|
||||
planModeTogetherModelId,
|
||||
planModeFireworksModelId,
|
||||
planModeSapAiCoreModelId,
|
||||
planModeGroqModelId,
|
||||
planModeGroqModelInfo,
|
||||
planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo,
|
||||
|
||||
// Act mode configuration updates
|
||||
actModeApiProvider,
|
||||
actModeApiModelId,
|
||||
actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId,
|
||||
actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo,
|
||||
actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo,
|
||||
actModeOllamaModelId,
|
||||
actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo,
|
||||
actModeRequestyModelId,
|
||||
actModeRequestyModelInfo,
|
||||
actModeTogetherModelId,
|
||||
actModeFireworksModelId,
|
||||
actModeSapAiCoreModelId,
|
||||
actModeGroqModelId,
|
||||
actModeGroqModelInfo,
|
||||
actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo,
|
||||
// Ephemeral model config updates (20 keys)
|
||||
apiProvider,
|
||||
apiModelId,
|
||||
thinkingBudgetTokens,
|
||||
reasoningEffort,
|
||||
vsCodeLmModelSelector,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
openRouterModelId,
|
||||
openRouterModelInfo,
|
||||
openAiModelId,
|
||||
openAiModelInfo,
|
||||
ollamaModelId,
|
||||
lmStudioModelId,
|
||||
liteLlmModelId,
|
||||
liteLlmModelInfo,
|
||||
requestyModelId,
|
||||
requestyModelInfo,
|
||||
togetherModelId,
|
||||
fireworksModelId,
|
||||
groqModelId,
|
||||
groqModelInfo,
|
||||
sapAiCoreModelId,
|
||||
|
||||
// Global state updates (27 keys)
|
||||
awsRegion,
|
||||
@@ -786,7 +672,6 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
nebiusApiKey,
|
||||
sapAiCoreClientId,
|
||||
sapAiCoreClientSecret,
|
||||
huggingFaceApiKey,
|
||||
}
|
||||
|
||||
// Execute batched operations in parallel for maximum performance
|
||||
@@ -830,7 +715,6 @@ export async function resetGlobalState(context: vscode.ExtensionContext) {
|
||||
"groqApiKey",
|
||||
"moonshotApiKey",
|
||||
"nebiusApiKey",
|
||||
"huggingFaceApiKey",
|
||||
]
|
||||
for (const key of secretKeys) {
|
||||
await storeSecret(context, key, undefined)
|
||||
|
||||
@@ -54,7 +54,6 @@ import { TaskState } from "./TaskState"
|
||||
import { MessageStateHandler } from "./message-state"
|
||||
import { AutoApprove } from "./tools/autoApprove"
|
||||
import { showNotificationForApprovalIfAutoApprovalEnabled } from "./utils"
|
||||
import { ChatSettings } from "@/shared/ChatSettings"
|
||||
|
||||
export class ToolExecutor {
|
||||
private autoApprover: AutoApprove
|
||||
@@ -91,7 +90,6 @@ export class ToolExecutor {
|
||||
private browserSettings: BrowserSettings,
|
||||
private cwd: string,
|
||||
private taskId: string,
|
||||
private chatSettings: ChatSettings,
|
||||
|
||||
// Callbacks to the Task (Entity)
|
||||
private say: (
|
||||
@@ -636,7 +634,7 @@ export class ToolExecutor {
|
||||
}
|
||||
await this.diffViewProvider.update(newContent, true)
|
||||
await setTimeoutPromise(300) // wait for diff view to update
|
||||
await this.diffViewProvider.scrollToFirstDiff()
|
||||
this.diffViewProvider.scrollToFirstDiff()
|
||||
// showOmissionWarning(this.diffViewProvider.originalContent || "", newContent)
|
||||
|
||||
const completeMessage = JSON.stringify({
|
||||
@@ -1919,12 +1917,7 @@ export class ToolExecutor {
|
||||
const clineVersion =
|
||||
vscode.extensions.getExtension("saoudrizwan.claude-dev")?.packageJSON.version || "Unknown"
|
||||
const systemInfo = `VSCode: ${vscode.version}, Node.js: ${process.version}, Architecture: ${os.arch()}`
|
||||
const currentMode = this.chatSettings.mode
|
||||
const apiProvider =
|
||||
currentMode === "plan"
|
||||
? await getGlobalState(this.context, "planModeApiProvider")
|
||||
: await getGlobalState(this.context, "actModeApiProvider")
|
||||
const providerAndModel = `${apiProvider} / ${this.api.getModel().id}`
|
||||
const providerAndModel = `${await getGlobalState(this.context, "apiProvider")} / ${this.api.getModel().id}`
|
||||
|
||||
// Ask user for confirmation
|
||||
const bugReportData = JSON.stringify({
|
||||
|
||||
+51
-87
@@ -81,10 +81,9 @@ import { refreshWorkflowToggles } from "../context/instructions/user-instruction
|
||||
import { MessageStateHandler } from "./message-state"
|
||||
import { TaskState } from "./TaskState"
|
||||
import { ToolExecutor } from "./ToolExecutor"
|
||||
import { updateApiReqMsg } from "./utils"
|
||||
import { createDiffViewProvider } from "@/hosts/host-providers"
|
||||
import { ErrorService } from "@/services/error/ErrorService"
|
||||
import { ClineErrorType } from "@/services/error/ClineError"
|
||||
import { extractErrorDetails, formatErrorWithStatusCode, updateApiReqMsg } from "./utils"
|
||||
import { createDiffViewProvider, getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
|
||||
export const USE_EXPERIMENTAL_CLAUDE4_FEATURES = false
|
||||
|
||||
export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
|
||||
@@ -166,19 +165,8 @@ export class Task {
|
||||
this.reinitExistingTaskFromId = reinitExistingTaskFromId
|
||||
this.cancelTask = cancelTask
|
||||
this.clineIgnoreController = new ClineIgnoreController(cwd)
|
||||
|
||||
// TODO(ae) this is a hack to replace the terminal manager for standalone,
|
||||
// until we have proper host bridge support for terminal execution. The
|
||||
// standaloneTerminalManager is defined in the vscode-impls and injected
|
||||
// during compilation of the standalone manager only, so this variable only
|
||||
// exists in that case
|
||||
if ((global as any).standaloneTerminalManager) {
|
||||
console.log("[DEBUG] Using vscode-impls.js terminal manager")
|
||||
this.terminalManager = (global as any).standaloneTerminalManager
|
||||
} else {
|
||||
console.log("[DEBUG] Using built in terminal manager")
|
||||
this.terminalManager = new TerminalManager()
|
||||
}
|
||||
// Initialization moved to startTask/resumeTaskFromHistory
|
||||
this.terminalManager = new TerminalManager()
|
||||
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
|
||||
this.terminalManager.setTerminalReuseEnabled(terminalReuseEnabled ?? true)
|
||||
this.terminalManager.setTerminalOutputLineLimit(terminalOutputLineLimit)
|
||||
@@ -261,19 +249,12 @@ export class Task {
|
||||
},
|
||||
}
|
||||
|
||||
const currentProvider =
|
||||
chatSettings.mode === "plan" ? apiConfiguration.planModeApiProvider : apiConfiguration.actModeApiProvider
|
||||
|
||||
if (currentProvider === "openai" || currentProvider === "openai-native") {
|
||||
if (chatSettings.mode === "plan") {
|
||||
effectiveApiConfiguration.planModeReasoningEffort = chatSettings.openAIReasoningEffort
|
||||
} else {
|
||||
effectiveApiConfiguration.actModeReasoningEffort = chatSettings.openAIReasoningEffort
|
||||
}
|
||||
if (apiConfiguration.apiProvider === "openai" || apiConfiguration.apiProvider === "openai-native") {
|
||||
effectiveApiConfiguration.reasoningEffort = chatSettings.openAIReasoningEffort
|
||||
}
|
||||
|
||||
// Now that taskId is initialized, we can build the API handler
|
||||
this.api = buildApiHandler(effectiveApiConfiguration, chatSettings.mode)
|
||||
this.api = buildApiHandler(effectiveApiConfiguration)
|
||||
|
||||
// Set taskId on browserSession for telemetry tracking
|
||||
this.browserSession.setTaskId(this.taskId)
|
||||
@@ -288,10 +269,10 @@ export class Task {
|
||||
// initialize telemetry
|
||||
if (historyItem) {
|
||||
// Open task from history
|
||||
telemetryService.captureTaskRestarted(this.taskId, currentProvider)
|
||||
telemetryService.captureTaskRestarted(this.taskId, apiConfiguration.apiProvider)
|
||||
} else {
|
||||
// New task started
|
||||
telemetryService.captureTaskCreated(this.taskId, currentProvider)
|
||||
telemetryService.captureTaskCreated(this.taskId, apiConfiguration.apiProvider)
|
||||
}
|
||||
|
||||
this.toolExecutor = new ToolExecutor(
|
||||
@@ -311,7 +292,6 @@ export class Task {
|
||||
this.browserSettings,
|
||||
cwd,
|
||||
this.taskId,
|
||||
this.chatSettings,
|
||||
this.say.bind(this),
|
||||
this.ask.bind(this),
|
||||
this.saveCheckpoint.bind(this),
|
||||
@@ -336,17 +316,7 @@ export class Task {
|
||||
* Updates the auto approval settings for this task
|
||||
*/
|
||||
public updateAutoApprovalSettings(settings: AutoApprovalSettings): void {
|
||||
// Check if maxRequests changed
|
||||
const maxRequestsChanged = this.autoApprovalSettings.maxRequests !== settings.maxRequests
|
||||
|
||||
// Update the settings
|
||||
this.autoApprovalSettings = settings
|
||||
this.toolExecutor.updateAutoApprovalSettings(settings)
|
||||
|
||||
// Reset counter if max requests limit changed
|
||||
if (maxRequestsChanged) {
|
||||
this.taskState.consecutiveAutoApprovedRequestsCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
async restoreCheckpoint(messageTs: number, restoreType: ClineCheckpointRestore, offset?: number) {
|
||||
@@ -1597,15 +1567,6 @@ export class Task {
|
||||
}
|
||||
}
|
||||
|
||||
private async getCurrentProviderInfo(): Promise<{ modelId: string; providerId: string }> {
|
||||
const modelId = this.api.getModel()?.id
|
||||
const providerId =
|
||||
this.chatSettings.mode === "plan"
|
||||
? ((await getGlobalState(this.getContext(), "planModeApiProvider")) as string)
|
||||
: ((await getGlobalState(this.getContext(), "actModeApiProvider")) as string)
|
||||
return { modelId, providerId }
|
||||
}
|
||||
|
||||
async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
|
||||
// Wait for MCP servers to be connected before generating system prompt
|
||||
await pWaitFor(() => this.mcpHub.isConnecting !== true, { timeout: 10_000 }).catch(() => {
|
||||
@@ -1700,17 +1661,16 @@ export class Task {
|
||||
const isAnthropic = this.api instanceof AnthropicHandler
|
||||
const isOpenRouterContextWindowError = checkIsOpenRouterContextWindowError(error) && isOpenRouter
|
||||
const isAnthropicContextWindowError = checkIsAnthropicContextWindowError(error) && isAnthropic
|
||||
const { modelId, providerId } = await this.getCurrentProviderInfo()
|
||||
const clineError = ErrorService.toClineError(error, modelId, providerId)
|
||||
|
||||
// Capture provider failure telemetry using clineError
|
||||
// TODO: Move into ErrorService
|
||||
const { statusCode, message, requestId } = extractErrorDetails(error)
|
||||
|
||||
// Capture provider failure telemetry
|
||||
telemetryService.captureProviderApiError({
|
||||
taskId: this.taskId,
|
||||
model: modelInfo.id,
|
||||
errorMessage: clineError.message,
|
||||
errorStatus: clineError._error?.status,
|
||||
requestId: clineError._error?.request_id,
|
||||
errorMessage: message,
|
||||
errorStatus: statusCode,
|
||||
requestId,
|
||||
})
|
||||
|
||||
if (isAnthropic && isAnthropicContextWindowError && !this.taskState.didAutomaticallyRetryFailedApiRequest) {
|
||||
@@ -1756,12 +1716,12 @@ export class Task {
|
||||
// If the conversation has more than 3 messages, we can truncate again. If not, then the conversation is bricked.
|
||||
// ToDo: Allow the user to change their input if this is the case.
|
||||
if (truncatedConversationHistory.length > 3) {
|
||||
clineError.message = "Context window exceeded. Click retry to truncate the conversation and try again."
|
||||
error = new Error("Context window exceeded. Click retry to truncate the conversation and try again.")
|
||||
this.taskState.didAutomaticallyRetryFailedApiRequest = false
|
||||
}
|
||||
}
|
||||
|
||||
const streamingFailedMessage = clineError.serialize()
|
||||
const errorMessage = formatErrorWithStatusCode(error)
|
||||
|
||||
// Update the 'api_req_started' message to reflect final failure before asking user to manually retry
|
||||
const lastApiReqStartedIndex = findLastIndex(
|
||||
@@ -1777,24 +1737,19 @@ export class Task {
|
||||
text: JSON.stringify({
|
||||
...currentApiReqInfo, // Spread the modified info (with retryStatus removed)
|
||||
// cancelReason: "retries_exhausted", // Indicate that automatic retries failed
|
||||
streamingFailedMessage,
|
||||
streamingFailedMessage: errorMessage,
|
||||
} satisfies ClineApiReqInfo),
|
||||
})
|
||||
// this.ask will trigger postStateToWebview, so this change should be picked up.
|
||||
}
|
||||
|
||||
const { response } = await this.ask("api_req_failed", streamingFailedMessage)
|
||||
const { response } = await this.ask("api_req_failed", errorMessage)
|
||||
|
||||
if (response !== "yesButtonClicked") {
|
||||
// this will never happen since if noButtonClicked, we will clear current task, aborting this instance
|
||||
throw new Error("API request failed")
|
||||
}
|
||||
|
||||
// Do not retry automatically again if currently unauthenticated
|
||||
if (clineError.isErrorType(ClineErrorType.Auth)) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.say("api_req_retried")
|
||||
}
|
||||
// delegate generator output from the recursive call
|
||||
@@ -1930,10 +1885,10 @@ export class Task {
|
||||
}
|
||||
|
||||
// Used to know what models were used in the task if user wants to export metadata for error reporting purposes
|
||||
const { modelId, providerId } = await this.getCurrentProviderInfo()
|
||||
if (providerId && modelId) {
|
||||
const currentProviderId = (await getGlobalState(this.getContext(), "apiProvider")) as string
|
||||
if (currentProviderId && this.api.getModel().id) {
|
||||
try {
|
||||
await this.modelContextTracker.recordModelUsage(providerId, modelId, this.chatSettings.mode)
|
||||
await this.modelContextTracker.recordModelUsage(currentProviderId, this.api.getModel().id, this.chatSettings.mode)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -2111,7 +2066,7 @@ export class Task {
|
||||
content: userContent,
|
||||
})
|
||||
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, providerId, modelId, "user")
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, currentProviderId, this.api.getModel().id, "user")
|
||||
|
||||
// since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message
|
||||
const lastApiReqIndex = findLastIndex(this.messageStateHandler.getClineMessages(), (m) => m.say === "api_req_started")
|
||||
@@ -2176,13 +2131,19 @@ export class Task {
|
||||
})
|
||||
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
|
||||
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, providerId, this.api.getModel().id, "assistant", {
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
})
|
||||
telemetryService.captureConversationTurnEvent(
|
||||
this.taskId,
|
||||
currentProviderId,
|
||||
this.api.getModel().id,
|
||||
"assistant",
|
||||
{
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
// signals to provider that it can retrieve the saved messages from disk, as abortTask can not be awaited on in nature
|
||||
this.taskState.didFinishAbortingStream = true
|
||||
@@ -2279,8 +2240,7 @@ export class Task {
|
||||
// abandoned happens when extension is no longer waiting for the cline instance to finish aborting (error is thrown here when any function in the for loop throws due to this.abort)
|
||||
if (!this.taskState.abandoned) {
|
||||
this.abortTask() // if the stream failed, there's various states the task could be in (i.e. could have streamed some tools the user may have executed), so we just resort to replicating a cancel task
|
||||
const clineError = ErrorService.toClineError(error, this.api.getModel().id)
|
||||
const errorMessage = clineError.serialize()
|
||||
const errorMessage = formatErrorWithStatusCode(error)
|
||||
|
||||
await abortStream("streaming_failed", errorMessage)
|
||||
await this.reinitExistingTaskFromId(this.taskId)
|
||||
@@ -2350,13 +2310,19 @@ export class Task {
|
||||
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
|
||||
let didEndLoop = false
|
||||
if (assistantMessage.length > 0) {
|
||||
telemetryService.captureConversationTurnEvent(this.taskId, providerId, modelId, "assistant", {
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
})
|
||||
telemetryService.captureConversationTurnEvent(
|
||||
this.taskId,
|
||||
currentProviderId,
|
||||
this.api.getModel().id,
|
||||
"assistant",
|
||||
{
|
||||
tokensIn: inputTokens,
|
||||
tokensOut: outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
totalCost,
|
||||
},
|
||||
)
|
||||
|
||||
await this.messageStateHandler.addToApiConversationHistory({
|
||||
role: "assistant",
|
||||
@@ -2402,8 +2368,6 @@ export class Task {
|
||||
},
|
||||
],
|
||||
})
|
||||
// Returns early to avoid retry since no assistant message was received
|
||||
return true
|
||||
}
|
||||
|
||||
return didEndLoop // will always be false for now
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import { showSystemNotification } from "@/integrations/notifications"
|
||||
import { ClineApiReqCancelReason, ClineApiReqInfo } from "@/shared/ExtensionMessage"
|
||||
import { serializeError } from "serialize-error"
|
||||
import { MessageStateHandler } from "./message-state"
|
||||
import { calculateApiCostAnthropic } from "@/utils/cost"
|
||||
import { ApiHandler } from "@/api"
|
||||
|
||||
export function formatErrorWithStatusCode(error: any): string {
|
||||
const { statusCode, message } = extractErrorDetails(error)
|
||||
|
||||
// Only prepend the statusCode if it's not already part of the message
|
||||
return statusCode && !message.includes(statusCode.toString()) ? `${statusCode} - ${message}` : message
|
||||
}
|
||||
|
||||
export function extractErrorDetails(error: any): { message: string; statusCode?: number; requestId?: string } {
|
||||
const statusCode = error.status || error.statusCode || (error.response && error.response?.status)
|
||||
const message = error.message ?? JSON.stringify(serializeError(error), null, 2)
|
||||
const requestId = error.request_id || error.response?.request_id || undefined
|
||||
|
||||
return { message, statusCode, requestId }
|
||||
}
|
||||
|
||||
export const showNotificationForApprovalIfAutoApprovalEnabled = (
|
||||
message: string,
|
||||
autoApprovalSettingsEnabled: boolean,
|
||||
|
||||
@@ -264,11 +264,13 @@ export abstract class WebviewProvider {
|
||||
} catch (error) {
|
||||
// Only show the error message if not in development mode.
|
||||
if (!process.env.IS_DEV) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message:
|
||||
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message:
|
||||
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return this.getHtmlContent()
|
||||
|
||||
@@ -99,10 +99,12 @@ export function registerTaskCommands(context: vscode.ExtensionContext, controlle
|
||||
await controller.postStateToWebview()
|
||||
|
||||
const message = `Created ${tasksCount} test tasks`
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
},
|
||||
)
|
||||
}),
|
||||
|
||||
+32
-35
@@ -27,7 +27,6 @@ import {
|
||||
migrateCustomInstructionsToGlobalRules,
|
||||
migrateModeFromWorkspaceStorageToControllerState,
|
||||
migrateWelcomeViewCompleted,
|
||||
migrateLegacyApiConfigurationToModeSpecific,
|
||||
} from "./core/storage/state-migrations"
|
||||
|
||||
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
|
||||
@@ -76,9 +75,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Migrate workspace storage values back to global storage (reverting previous migration)
|
||||
await migrateWorkspaceToGlobalStorage(context)
|
||||
|
||||
// Migrate legacy API configuration to mode-specific keys (one-time migration)
|
||||
await migrateLegacyApiConfigurationToModeSpecific(context)
|
||||
|
||||
// Clean up orphaned file context warnings (startup cleanup)
|
||||
await FileContextTracker.cleanupOrphanedWarnings(context)
|
||||
|
||||
@@ -110,10 +106,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const message = `Cline has been updated to v${currentVersion}`
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
}),
|
||||
)
|
||||
// Record that we've shown the popup for this version.
|
||||
await context.globalState.update("clineLastPopupNotificationVersion", currentVersion)
|
||||
}
|
||||
@@ -415,10 +413,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Ensure clipboard is restored even if an error occurs
|
||||
await writeTextToClipboard(tempCopyBuffer)
|
||||
console.error("Error getting terminal contents:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to get terminal contents",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to get terminal contents",
|
||||
}),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -554,10 +554,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
@@ -579,10 +581,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
@@ -647,10 +651,12 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
sendFocusChatInputEvent(clientId)
|
||||
} else {
|
||||
console.error("FocusChatInput: Could not find or activate a Cline webview to focus.")
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Could not activate Cline view. Please try opening it manually from the Activity Bar.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Could not activate Cline view. Please try opening it manually from the Activity Bar.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
telemetryService.captureButtonClick("command_focusChatInput", activeWebviewProvider?.controller.task?.taskId)
|
||||
}),
|
||||
@@ -685,18 +691,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
context.secrets.onDidChange(async (event) => {
|
||||
context.secrets.onDidChange((event) => {
|
||||
if (event.key === "clineAccountId") {
|
||||
// Check if the secret was removed (logout) or added/updated (login)
|
||||
const secretValue = await context.secrets.get("clineAccountId")
|
||||
const authService = AuthService.getInstance(context)
|
||||
if (secretValue) {
|
||||
// Secret was added or updated - restore auth info (login from another window)
|
||||
authService?.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
} else {
|
||||
// Secret was removed - handle logout for all windows
|
||||
authService?.handleDeauth()
|
||||
}
|
||||
AuthService.getInstance(context)?.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { DiffViewProvider } from "@/integrations/editor/DiffViewProvider"
|
||||
|
||||
export class ExternalDiffViewProvider extends DiffViewProvider {
|
||||
private activeDiffEditorId: string | undefined
|
||||
|
||||
override async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
return
|
||||
}
|
||||
const response = await getHostBridgeProvider().diffClient.openDiff({
|
||||
path: this.absolutePath,
|
||||
content: this.originalContent ?? "",
|
||||
})
|
||||
this.activeDiffEditorId = response.diffId
|
||||
}
|
||||
|
||||
override async replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
_currentLine: number | undefined,
|
||||
): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.replaceText({
|
||||
diffId: this.activeDiffEditorId,
|
||||
content: content,
|
||||
startLine: rangeToReplace.startLine,
|
||||
endLine: rangeToReplace.endLine,
|
||||
})
|
||||
}
|
||||
|
||||
protected override async truncateDocument(lineNumber: number): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.truncateDocument({
|
||||
diffId: this.activeDiffEditorId,
|
||||
endLine: lineNumber,
|
||||
})
|
||||
}
|
||||
|
||||
protected async saveDocument(): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.saveDocument({ diffId: this.activeDiffEditorId })
|
||||
}
|
||||
|
||||
protected override async scrollEditorToLine(line: number): Promise<void> {
|
||||
console.log(`Called ExternalDiffViewProvider.scrollEditorToLine(${line}) stub`)
|
||||
}
|
||||
|
||||
override async scrollAnimation(startLine: number, endLine: number): Promise<void> {
|
||||
console.log(`Called ExternalDiffViewProvider.scrollAnimation(${startLine}, ${endLine}) stub`)
|
||||
}
|
||||
|
||||
protected override async getDocumentText(): Promise<string | undefined> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return undefined
|
||||
}
|
||||
return (await getHostBridgeProvider().diffClient.getDocumentText({ diffId: this.activeDiffEditorId })).content
|
||||
}
|
||||
|
||||
protected override async getNewDiagnosticProblems(): Promise<string> {
|
||||
console.log(`Called ExternalDiffViewProvider.getNewDiagnosticProblems() stub`)
|
||||
return ""
|
||||
}
|
||||
|
||||
protected override async closeDiffView(): Promise<void> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return
|
||||
}
|
||||
await getHostBridgeProvider().diffClient.closeDiff({ diffId: this.activeDiffEditorId })
|
||||
this.activeDiffEditorId = undefined
|
||||
}
|
||||
|
||||
protected override async resetDiffView(): Promise<void> {
|
||||
this.activeDiffEditorId = undefined
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
import { arePathsEqual } from "@/utils/path"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { DecorationController } from "@/hosts/vscode/DecorationController"
|
||||
import { DecorationController } from "@integrations/editor/DecorationController"
|
||||
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "@integrations/editor/DiffViewProvider"
|
||||
import { diagnosticsToProblemsString, getNewDiagnostics } from "@/integrations/diagnostics"
|
||||
|
||||
export class VscodeDiffViewProvider extends DiffViewProvider {
|
||||
private activeDiffEditor?: vscode.TextEditor
|
||||
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
|
||||
private fadedOverlayController?: DecorationController
|
||||
private activeLineController?: DecorationController
|
||||
|
||||
override async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
throw new Error("No file path set")
|
||||
@@ -88,110 +81,20 @@ export class VscodeDiffViewProvider extends DiffViewProvider {
|
||||
override async replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
currentLine: number | undefined,
|
||||
currentLine: number,
|
||||
): Promise<void> {
|
||||
if (!this.activeDiffEditor || !this.activeDiffEditor.document) {
|
||||
const document = this.activeDiffEditor?.document
|
||||
if (!document) {
|
||||
throw new Error("User closed text editor, unable to edit file...")
|
||||
}
|
||||
// Place cursor at the beginning of the diff editor to keep it out of the way of the stream animation
|
||||
const beginningOfDocument = new vscode.Position(0, 0)
|
||||
this.activeDiffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
|
||||
|
||||
// Replace the text in the diff editor document.
|
||||
const document = this.activeDiffEditor?.document
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
const range = new vscode.Range(rangeToReplace.startLine, 0, rangeToReplace.endLine, 0)
|
||||
edit.replace(document.uri, range, content)
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
|
||||
if (currentLine !== undefined) {
|
||||
// Update decorations for the entire changed section
|
||||
this.activeLineController?.setActiveLine(currentLine)
|
||||
this.fadedOverlayController?.updateOverlayAfterLine(currentLine, document.lineCount)
|
||||
}
|
||||
}
|
||||
|
||||
override async scrollEditorToLine(line: number): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const scrollLine = line + 4
|
||||
this.activeDiffEditor.revealRange(new vscode.Range(scrollLine, 0, scrollLine, 0), vscode.TextEditorRevealType.InCenter)
|
||||
}
|
||||
|
||||
override async scrollAnimation(startLine: number, endLine: number): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor.revealRange(new vscode.Range(line, 0, line, 0), vscode.TextEditorRevealType.InCenter)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
}
|
||||
}
|
||||
|
||||
override async truncateDocument(lineNumber: number): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const document = this.activeDiffEditor.document
|
||||
if (lineNumber < document.lineCount) {
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
edit.delete(document.uri, new vscode.Range(lineNumber, 0, document.lineCount, 0))
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
}
|
||||
// Clear all decorations at the end (before applying final edit)
|
||||
this.fadedOverlayController?.clear()
|
||||
this.activeLineController?.clear()
|
||||
}
|
||||
|
||||
protected override async getDocumentText(): Promise<string | undefined> {
|
||||
if (!this.activeDiffEditor || !this.activeDiffEditor.document) {
|
||||
return undefined
|
||||
}
|
||||
return this.activeDiffEditor.document.getText()
|
||||
}
|
||||
|
||||
protected override async getNewDiagnosticProblems(): Promise<string> {
|
||||
// Get the diagnostics after changing the document.
|
||||
const postDiagnostics = vscode.languages.getDiagnostics()
|
||||
const newProblems = getNewDiagnostics(this.preDiagnostics, postDiagnostics)
|
||||
// Only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
|
||||
// will be empty string if no errors
|
||||
const problems = await diagnosticsToProblemsString(newProblems, [vscode.DiagnosticSeverity.Error])
|
||||
return problems
|
||||
}
|
||||
|
||||
protected override async saveDocument(): Promise<void> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
if (this.activeDiffEditor.document.isDirty) {
|
||||
await this.activeDiffEditor.document.save()
|
||||
}
|
||||
}
|
||||
|
||||
protected async closeDiffView(): Promise<void> {
|
||||
// Close all the cline diff views.
|
||||
const tabs = vscode.window.tabGroups.all
|
||||
.flatMap((tg) => tg.tabs)
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME)
|
||||
for (const tab of tabs) {
|
||||
// trying to close dirty views results in save popup
|
||||
if (!tab.isDirty) {
|
||||
await vscode.window.tabGroups.close(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async resetDiffView(): Promise<void> {
|
||||
this.activeDiffEditor = undefined
|
||||
this.fadedOverlayController = undefined
|
||||
this.activeLineController = undefined
|
||||
this.preDiagnostics = []
|
||||
// Update decorations for the entire changed section
|
||||
this.activeLineController?.setActiveLine(currentLine)
|
||||
this.fadedOverlayController?.updateOverlayAfterLine(currentLine, document.lineCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StreamingCallbacks } from "@/hosts/host-provider-types"
|
||||
import { hostServiceHandlers } from "@generated/hosts/vscode/hostbridge-grpc-service-config"
|
||||
import { HostServiceHandlerConfig, hostServiceHandlers } from "@generated/hosts/vscode/hostbridge-grpc-service-config"
|
||||
import { GrpcRequestRegistry } from "@core/controller/grpc-request-registry"
|
||||
|
||||
/**
|
||||
@@ -161,19 +161,6 @@ export class GrpcHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for a host service handler
|
||||
*/
|
||||
export interface HostServiceHandlerConfig {
|
||||
requestHandler: (method: string, message: any) => Promise<any>
|
||||
streamingHandler: (
|
||||
method: string,
|
||||
message: any,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request registry instance
|
||||
* This allows other parts of the code to access the registry
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { CloseDiffRequest, CloseDiffResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function closeDiff(_request: CloseDiffRequest): Promise<CloseDiffResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { GetDocumentTextRequest, GetDocumentTextResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function getDocumentText(_request: GetDocumentTextRequest): Promise<GetDocumentTextResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpenDiffRequest, OpenDiffResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function openDiff(_request: OpenDiffRequest): Promise<OpenDiffResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
throw new Error("diffService.openDiff is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReplaceTextRequest, ReplaceTextResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function replaceText(_request: ReplaceTextRequest): Promise<ReplaceTextResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
throw new Error("diffService.replaceText is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { SaveDocumentRequest, SaveDocumentResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function saveDocument(_request: SaveDocumentRequest): Promise<SaveDocumentResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { TruncateDocumentRequest, TruncateDocumentResponse } from "@/shared/proto/index.host"
|
||||
|
||||
export async function truncateDocument(_request: TruncateDocumentRequest): Promise<TruncateDocumentResponse> {
|
||||
throw new Error("diffService is not supported. Use the VscodeDiffViewProvider.")
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { SelectedResponse, ShowMessageRequest, ShowMessageType } from "@/shared/
|
||||
|
||||
const DEFAULT_OPTIONS = { modal: false, items: [] } as const
|
||||
|
||||
export async function showMessage(request: ShowMessageRequest): Promise<SelectedResponse> {
|
||||
export async function showMessage(request: ShowMessageRequest): Promise<SelectedResponse | undefined> {
|
||||
const { message, type, options } = request
|
||||
const { modal, detail, items } = { ...DEFAULT_OPTIONS, ...options }
|
||||
const option = { modal, detail }
|
||||
|
||||
@@ -76,39 +76,10 @@ export async function* runClaudeCode(options: ClaudeCodeOptions): AsyncGenerator
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
// When the command fails, execa throws an error with the arguments, which include the whole system prompt.
|
||||
// We want to log that, but not show it to the user.
|
||||
console.error(`Error during Claude Code execution:`, err)
|
||||
|
||||
if (err instanceof Error) {
|
||||
if (err.message.includes("ENOENT")) {
|
||||
throw new Error(
|
||||
`Failed to find the Claude Code executable.
|
||||
Make sure it's installed and available in your PATH or properly set in your provider settings.`,
|
||||
{ cause: err },
|
||||
)
|
||||
}
|
||||
|
||||
if (err.message.includes("E2BIG")) {
|
||||
throw new Error(
|
||||
`Executing Claude Code failed due to a long system prompt. The maximum argument length is 131072 bytes.
|
||||
Rules and workflows contribute to a longer system prompt, consider disabling some of them temporarily to reduce the length.
|
||||
Anthropic is aware of this issue and is considering a fix: https://github.com/anthropics/claude-code/issues/3411.
|
||||
`,
|
||||
{ cause: err },
|
||||
)
|
||||
}
|
||||
|
||||
if (err.message.includes("ENAMETOOLONG")) {
|
||||
throw new Error(
|
||||
`Executing Claude Code failed due to a long system prompt. Windows has a limit of 8191 characters, which makes the integration with Cline not work properly.
|
||||
Please check our docs on how to integrate Claude Code with Cline on Windows: https://docs.cline.bot/provider-config/claude-code#windows-setup.
|
||||
Anthropic is aware of this issue and is considering a fix: https://github.com/anthropics/claude-code/issues/3411.
|
||||
`,
|
||||
{ cause: err },
|
||||
)
|
||||
}
|
||||
|
||||
// When the command fails, execa throws an error with the arguments, which include the whole system prompt.
|
||||
// We want to log that, but not show it to the user.
|
||||
const startOfCommand = err.message.indexOf(": ")
|
||||
if (startOfCommand !== -1) {
|
||||
const messageWithoutCommand = err.message.slice(0, startOfCommand).trim()
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as vscode from "vscode"
|
||||
|
||||
interface DebugSession {
|
||||
id: string
|
||||
name: string
|
||||
output: string[]
|
||||
lastRetrievedIndex: number
|
||||
}
|
||||
|
||||
export class DebugConsoleManager {
|
||||
private sessions: Map<string, DebugSession> = new Map()
|
||||
private disposables: vscode.Disposable[] = []
|
||||
|
||||
constructor() {
|
||||
// Listen for debug session start events
|
||||
this.disposables.push(
|
||||
vscode.debug.onDidStartDebugSession((session) => {
|
||||
this.sessions.set(session.id, {
|
||||
id: session.id,
|
||||
name: session.name,
|
||||
output: [],
|
||||
lastRetrievedIndex: -1,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
// Listen for debug session end events
|
||||
this.disposables.push(
|
||||
vscode.debug.onDidTerminateDebugSession((session) => {
|
||||
this.sessions.delete(session.id)
|
||||
}),
|
||||
)
|
||||
|
||||
// Listen for debug console output
|
||||
this.disposables.push(
|
||||
vscode.debug.onDidReceiveDebugSessionCustomEvent((e: vscode.DebugSessionCustomEvent) => {
|
||||
if (e.event === "output" && e.body?.output) {
|
||||
const session = this.sessions.get(e.session.id)
|
||||
if (session) {
|
||||
session.output.push(e.body.output)
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active debug sessions
|
||||
*/
|
||||
getActiveSessions(): { id: string; name: string }[] {
|
||||
return Array.from(this.sessions.values()).map(({ id, name }) => ({ id, name }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any new output since the last retrieval for a specific debug session
|
||||
*/
|
||||
getUnretrievedOutput(sessionId: string): string | undefined {
|
||||
const session = this.sessions.get(sessionId)
|
||||
if (!session) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const newOutput = session.output.slice(session.lastRetrievedIndex + 1).join("")
|
||||
session.lastRetrievedIndex = session.output.length - 1
|
||||
return newOutput || undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
dispose() {
|
||||
this.disposables.forEach((d) => d.dispose())
|
||||
this.sessions.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
import * as vscode from "vscode"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
|
||||
type FileDiagnostics = [vscode.Uri, vscode.Diagnostic[]][]
|
||||
|
||||
|
||||
About Diagnostics:
|
||||
The Problems tab shows diagnostics that have been reported for your project. These diagnostics are categorized into:
|
||||
Errors: Critical issues that usually prevent your code from compiling or running correctly.
|
||||
Warnings: Potential problems in the code that may not prevent it from running but could cause issues (e.g., bad practices, unused variables).
|
||||
Information: Non-critical suggestions or tips (e.g., formatting issues or notes from linters).
|
||||
The Problems tab displays diagnostics from various sources:
|
||||
1. Language Servers:
|
||||
- TypeScript: Type errors, missing imports, syntax issues
|
||||
- Python: Syntax errors, invalid type hints, undefined variables
|
||||
- JavaScript/Node.js: Parsing and execution errors
|
||||
2. Linters:
|
||||
- ESLint: Code style, best practices, potential bugs
|
||||
- Pylint: Unused imports, naming conventions
|
||||
- TSLint: Style and correctness issues in TypeScript
|
||||
3. Build Tools:
|
||||
- Webpack: Module resolution failures, build errors
|
||||
- Gulp: Build errors during task execution
|
||||
4. Custom Validators:
|
||||
- Extensions can generate custom diagnostics for specific languages or tools
|
||||
Each problem typically indicates its source (e.g., language server, linter, build tool).
|
||||
Diagnostics update in real-time as you edit code, helping identify issues quickly. For example, if you introduce a syntax error in a TypeScript file, the Problems tab will immediately display the new error.
|
||||
|
||||
Notes on diagnostics:
|
||||
- linter diagnostics are only captured for open editors
|
||||
- this works great for us since when cline edits/creates files its through vscode's textedit api's and we get those diagnostics for free
|
||||
- some tools might require you to save the file or manually refresh to clear the problem from the list.
|
||||
|
||||
System Prompt
|
||||
- You will automatically receive workspace error diagnostics in environment_details. Be mindful that this may include issues beyond the scope of your task or the user's request. Only address errors relevant to your work, and avoid fixing pre-existing or unrelated issues unless the user specifically instructs you to do so.
|
||||
- If you are unable to resolve errors provided in environment_details after two attempts, consider using ask_followup_question to ask the user for additional information, such as the latest documentation related to a problematic framework, to help you make progress on the task. If the error remains unresolved after this step, proceed with your task while disregarding the error.
|
||||
|
||||
class DiagnosticsMonitor {
|
||||
private diagnosticsChangeEmitter: vscode.EventEmitter<void> = new vscode.EventEmitter<void>()
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private lastDiagnostics: FileDiagnostics = []
|
||||
|
||||
constructor() {
|
||||
this.disposables.push(
|
||||
vscode.languages.onDidChangeDiagnostics(() => {
|
||||
this.diagnosticsChangeEmitter.fire()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
public async getCurrentDiagnostics(shouldWaitForChanges: boolean): Promise<FileDiagnostics> {
|
||||
const currentDiagnostics = this.getDiagnostics()
|
||||
if (!shouldWaitForChanges) {
|
||||
this.lastDiagnostics = currentDiagnostics
|
||||
return currentDiagnostics
|
||||
}
|
||||
|
||||
if (!deepEqual(this.lastDiagnostics, currentDiagnostics)) {
|
||||
this.lastDiagnostics = currentDiagnostics
|
||||
return currentDiagnostics
|
||||
}
|
||||
|
||||
let timeout = 300 // only way this happens is if there's no errors
|
||||
|
||||
// if diagnostics contain existing errors (since the check above didn't trigger) then it's likely cline just did something that should have fixed the error, so we'll give a longer grace period for diagnostics to catch up
|
||||
const hasErrors = currentDiagnostics.some(([_, diagnostics]) =>
|
||||
diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error)
|
||||
)
|
||||
if (hasErrors) {
|
||||
console.log("Existing errors detected, extending timeout", currentDiagnostics)
|
||||
timeout = 10_000
|
||||
}
|
||||
|
||||
return this.waitForUpdatedDiagnostics(timeout)
|
||||
}
|
||||
|
||||
private async waitForUpdatedDiagnostics(timeout: number): Promise<FileDiagnostics> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
cleanup()
|
||||
const finalDiagnostics = this.getDiagnostics()
|
||||
this.lastDiagnostics = finalDiagnostics
|
||||
resolve(finalDiagnostics)
|
||||
}, timeout)
|
||||
|
||||
const disposable = this.diagnosticsChangeEmitter.event(() => {
|
||||
const updatedDiagnostics = this.getDiagnostics() // I thought this would only trigger when diagnostics changed, but that's not the case.
|
||||
if (deepEqual(this.lastDiagnostics, updatedDiagnostics)) {
|
||||
// diagnostics have not changed, ignoring...
|
||||
return
|
||||
}
|
||||
cleanup()
|
||||
this.lastDiagnostics = updatedDiagnostics
|
||||
resolve(updatedDiagnostics)
|
||||
})
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer)
|
||||
disposable.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private getDiagnostics(): FileDiagnostics {
|
||||
const allDiagnostics = vscode.languages.getDiagnostics()
|
||||
return allDiagnostics
|
||||
.filter(([_, diagnostics]) => diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error))
|
||||
.map(([uri, diagnostics]) => [
|
||||
uri,
|
||||
diagnostics.filter((d) => d.severity === vscode.DiagnosticSeverity.Error),
|
||||
])
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach((d) => d.dispose())
|
||||
this.disposables = []
|
||||
this.diagnosticsChangeEmitter.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
export default DiagnosticsMonitor
|
||||
*/
|
||||
@@ -4,10 +4,13 @@ import * as fs from "fs/promises"
|
||||
import { createDirectoriesForFile } from "@utils/fs"
|
||||
import { arePathsEqual, getCwd } from "@utils/path"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { DecorationController } from "./DecorationController"
|
||||
import * as diff from "diff"
|
||||
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
|
||||
import { detectEncoding } from "../misc/extract-text"
|
||||
import * as iconv from "iconv-lite"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowTextDocumentRequest, ShowTextDocumentOptions } from "@/shared/proto/host/window"
|
||||
|
||||
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
|
||||
|
||||
@@ -23,6 +26,11 @@ export abstract class DiffViewProvider {
|
||||
private streamedLines: string[] = []
|
||||
private newContent?: string
|
||||
|
||||
protected activeDiffEditor?: vscode.TextEditor
|
||||
protected fadedOverlayController?: DecorationController
|
||||
protected activeLineController?: DecorationController
|
||||
protected preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
|
||||
|
||||
constructor() {}
|
||||
|
||||
public async open(relPath: string): Promise<void> {
|
||||
@@ -54,13 +62,17 @@ export abstract class DiffViewProvider {
|
||||
await fs.writeFile(this.absolutePath, "")
|
||||
}
|
||||
await this.openDiffEditor()
|
||||
await this.scrollEditorToLine(0)
|
||||
this.scrollEditorToLine(0) // will this crash for new files?
|
||||
this.streamedLines = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a diff editor or viewer for the current file.
|
||||
*
|
||||
* This abstract method must be implemented by subclasses to create and display
|
||||
* a diff editor or viewer that shows the difference between the original and
|
||||
* modified content.
|
||||
*
|
||||
* Called automatically by the `open` method after ensuring the file exists and
|
||||
* creating any necessary directories.
|
||||
*
|
||||
@@ -68,82 +80,13 @@ export abstract class DiffViewProvider {
|
||||
*/
|
||||
protected abstract openDiffEditor(): Promise<void>
|
||||
|
||||
/**
|
||||
* Scrolls the diff editor to reveal a specific line.
|
||||
*
|
||||
* It's used during streaming updates to keep the user's view focused on the changing content.
|
||||
*
|
||||
* @param line The 0-based line number to scroll to
|
||||
*/
|
||||
protected abstract scrollEditorToLine(line: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Creates a smooth scrolling animation between two lines in the diff editor.
|
||||
*
|
||||
* It's typically used when updates contain many lines, to help the user visually track the flow
|
||||
* of significant changes in the document.
|
||||
*
|
||||
* @param startLine The 0-based line number to begin the animation from
|
||||
* @param endLine The 0-based line number to animate to
|
||||
*/
|
||||
protected abstract scrollAnimation(startLine: number, endLine: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Removes content from the specified line to the end of the document.
|
||||
* Called after the final update is received.
|
||||
*/
|
||||
protected abstract truncateDocument(lineNumber: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Get the contents of the diff editor document.
|
||||
*
|
||||
* Returns undefined if the diff editor was closed.
|
||||
*/
|
||||
protected abstract getDocumentText(): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Get any new diagnostic problems that appeared after applying the diff.
|
||||
*
|
||||
* Getting diagnostics before and after the file edit is a better approach than
|
||||
* automatically tracking problems in real-time. This method ensures we only
|
||||
* report new problems that are a direct result of this specific edit.
|
||||
* Since these are new problems resulting from Cline's edit, we know they're
|
||||
* directly related to the work he's doing. This eliminates the risk of Cline
|
||||
* going off-task or getting distracted by unrelated issues, which was a problem
|
||||
* with the previous auto-debug approach. Some users' machines may be slow to
|
||||
* update diagnostics, so this approach provides a good balance between automation
|
||||
* and avoiding potential issues where Cline might get stuck in loops due to
|
||||
* outdated problem information. If no new problems show up by the time the user
|
||||
* accepts the changes, they can always debug later using the '@problems' mention.
|
||||
* This way, Cline only becomes aware of new problems resulting from his edits
|
||||
* and can address them accordingly. If problems don't change immediately after
|
||||
* applying a fix, Cline won't be notified, which is generally fine since the
|
||||
* initial fix is usually correct and it may just take time for linters to catch up.
|
||||
*/
|
||||
protected abstract getNewDiagnosticProblems(): Promise<string>
|
||||
|
||||
/**
|
||||
* Save the contents of the diff editor UI to the file.
|
||||
*/
|
||||
protected abstract saveDocument(): Promise<void>
|
||||
|
||||
/**
|
||||
* Closes the diff editor tab or window.
|
||||
*/
|
||||
protected abstract closeDiffView(): Promise<void>
|
||||
|
||||
/**
|
||||
* Cleans up the diff view resources and resets internal state.
|
||||
*/
|
||||
protected abstract resetDiffView(): Promise<void>
|
||||
|
||||
async update(
|
||||
accumulatedContent: string,
|
||||
isFinal: boolean,
|
||||
changeLocation?: { startLine: number; endLine: number; startChar: number; endChar: number },
|
||||
) {
|
||||
if (!this.isEditing) {
|
||||
throw new Error("Not editing any file")
|
||||
if (!this.relPath || !this.activeLineController || !this.fadedOverlayController) {
|
||||
throw new Error("Required values not set")
|
||||
}
|
||||
|
||||
// --- Fix to prevent duplicate BOM ---
|
||||
@@ -161,6 +104,16 @@ export abstract class DiffViewProvider {
|
||||
}
|
||||
const diffLines = accumulatedLines.slice(this.streamedLines.length)
|
||||
|
||||
const diffEditor = this.activeDiffEditor
|
||||
const document = diffEditor?.document
|
||||
if (!diffEditor || !document) {
|
||||
throw new Error("User closed text editor, unable to edit file...")
|
||||
}
|
||||
|
||||
// Place cursor at the beginning of the diff editor to keep it out of the way of the stream animation
|
||||
const beginningOfDocument = new vscode.Position(0, 0)
|
||||
diffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
|
||||
|
||||
// Instead of animating each line, we'll update in larger chunks
|
||||
const currentLine = this.streamedLines.length + diffLines.length - 1
|
||||
if (currentLine >= 0) {
|
||||
@@ -176,19 +129,30 @@ export abstract class DiffViewProvider {
|
||||
if (changeLocation) {
|
||||
// We have the actual location of the change, scroll to it
|
||||
const targetLine = changeLocation.startLine
|
||||
await this.scrollEditorToLine(targetLine)
|
||||
this.scrollEditorToLine(targetLine)
|
||||
} else {
|
||||
// Fallback to the old logic for non-replacement updates
|
||||
if (diffLines.length <= 5) {
|
||||
// For small changes, just jump directly to the line
|
||||
await this.scrollEditorToLine(currentLine)
|
||||
this.scrollEditorToLine(currentLine)
|
||||
} else {
|
||||
// For larger changes, create a quick scrolling animation
|
||||
const startLine = this.streamedLines.length
|
||||
const endLine = currentLine
|
||||
await this.scrollAnimation(startLine, endLine)
|
||||
const totalLines = endLine - startLine
|
||||
const numSteps = 10 // Adjust this number to control animation speed
|
||||
const stepSize = Math.max(1, Math.floor(totalLines / numSteps))
|
||||
|
||||
// Create and await the smooth scrolling animation
|
||||
for (let line = startLine; line <= endLine; line += stepSize) {
|
||||
this.activeDiffEditor?.revealRange(
|
||||
new vscode.Range(line, 0, line, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, 16)) // ~60fps
|
||||
}
|
||||
// Ensure we end at the final line
|
||||
await this.scrollEditorToLine(currentLine)
|
||||
this.scrollEditorToLine(currentLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,8 +161,11 @@ export abstract class DiffViewProvider {
|
||||
this.streamedLines = accumulatedLines
|
||||
if (isFinal) {
|
||||
// Handle any remaining lines if the new content is shorter than the original
|
||||
await this.truncateDocument(this.streamedLines.length)
|
||||
|
||||
if (this.streamedLines.length < document.lineCount) {
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
edit.delete(document.uri, new vscode.Range(this.streamedLines.length, 0, document.lineCount, 0))
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
}
|
||||
// Add empty last line if original content had one
|
||||
const hasEmptyLastLine = this.originalContent?.endsWith("\n")
|
||||
if (hasEmptyLastLine) {
|
||||
@@ -207,6 +174,9 @@ export abstract class DiffViewProvider {
|
||||
accumulatedContent += "\n"
|
||||
}
|
||||
}
|
||||
// Clear all decorations at the end (before applying final edit)
|
||||
this.fadedOverlayController.clear()
|
||||
this.activeLineController.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +195,7 @@ export abstract class DiffViewProvider {
|
||||
abstract replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
currentLine: number | undefined,
|
||||
currentLine: number,
|
||||
): Promise<void>
|
||||
|
||||
async saveChanges(): Promise<{
|
||||
@@ -234,10 +204,7 @@ export abstract class DiffViewProvider {
|
||||
autoFormattingEdits: string | undefined
|
||||
finalContent: string | undefined
|
||||
}> {
|
||||
// get the contents before save operation which may do auto-formatting
|
||||
const preSaveContent = await this.getDocumentText()
|
||||
|
||||
if (!this.relPath || !this.absolutePath || !this.newContent || preSaveContent === undefined) {
|
||||
if (!this.relPath || !this.newContent || !this.activeDiffEditor) {
|
||||
return {
|
||||
newProblemsMessage: undefined,
|
||||
userEdits: undefined,
|
||||
@@ -245,21 +212,50 @@ export abstract class DiffViewProvider {
|
||||
finalContent: undefined,
|
||||
}
|
||||
}
|
||||
const updatedDocument = this.activeDiffEditor.document
|
||||
|
||||
// get the contents before save operation which may do auto-formatting
|
||||
const preSaveContent = updatedDocument.getText()
|
||||
|
||||
if (updatedDocument.isDirty) {
|
||||
await updatedDocument.save()
|
||||
}
|
||||
|
||||
await this.saveDocument()
|
||||
// get text after save in case there is any auto-formatting done by the editor
|
||||
const postSaveContent = (await this.getDocumentText()) || ""
|
||||
const postSaveContent = updatedDocument.getText()
|
||||
|
||||
await getHostBridgeProvider().windowClient.showTextDocument({
|
||||
path: this.absolutePath,
|
||||
options: {
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
},
|
||||
})
|
||||
await this.closeDiffView()
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: this.absolutePath,
|
||||
options: ShowTextDocumentOptions.create({
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await this.closeAllDiffViews()
|
||||
|
||||
const newProblems = await this.getNewDiagnosticProblems()
|
||||
/*
|
||||
Getting diagnostics before and after the file edit is a better approach than
|
||||
automatically tracking problems in real-time. This method ensures we only
|
||||
report new problems that are a direct result of this specific edit.
|
||||
Since these are new problems resulting from Cline's edit, we know they're
|
||||
directly related to the work he's doing. This eliminates the risk of Cline
|
||||
going off-task or getting distracted by unrelated issues, which was a problem
|
||||
with the previous auto-debug approach. Some users' machines may be slow to
|
||||
update diagnostics, so this approach provides a good balance between automation
|
||||
and avoiding potential issues where Cline might get stuck in loops due to
|
||||
outdated problem information. If no new problems show up by the time the user
|
||||
accepts the changes, they can always debug later using the '@problems' mention.
|
||||
This way, Cline only becomes aware of new problems resulting from his edits
|
||||
and can address them accordingly. If problems don't change immediately after
|
||||
applying a fix, Cline won't be notified, which is generally fine since the
|
||||
initial fix is usually correct and it may just take time for linters to catch up.
|
||||
*/
|
||||
const postDiagnostics = vscode.languages.getDiagnostics()
|
||||
const newProblems = await diagnosticsToProblemsString(getNewDiagnostics(this.preDiagnostics, postDiagnostics), [
|
||||
vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
|
||||
]) // will be empty string if no errors
|
||||
const newProblemsMessage =
|
||||
newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
|
||||
|
||||
@@ -299,14 +295,16 @@ export abstract class DiffViewProvider {
|
||||
}
|
||||
|
||||
async revertChanges(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
if (!this.absolutePath || !this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const fileExists = this.editType === "modify"
|
||||
|
||||
const updatedDocument = this.activeDiffEditor.document
|
||||
if (!fileExists) {
|
||||
await this.saveDocument()
|
||||
await this.closeDiffView()
|
||||
if (updatedDocument.isDirty) {
|
||||
await updatedDocument.save()
|
||||
}
|
||||
await this.closeAllDiffViews()
|
||||
await fs.unlink(this.absolutePath)
|
||||
// Remove only the directories we created, in reverse order
|
||||
for (let i = this.createdDirs.length - 1; i >= 0; i--) {
|
||||
@@ -316,41 +314,70 @@ export abstract class DiffViewProvider {
|
||||
console.log(`File ${this.absolutePath} has been deleted.`)
|
||||
} else {
|
||||
// revert document
|
||||
// Apply the edit and save, since contents shouldn't have changed this won't show in local history unless of
|
||||
// course the user made changes and saved during the edit.
|
||||
const contents = (await this.getDocumentText()) || ""
|
||||
const lineCount = (contents.match(/\n/g) || []).length + 1
|
||||
await this.replaceText(this.originalContent ?? "", { startLine: 0, endLine: lineCount }, undefined)
|
||||
|
||||
await this.saveDocument()
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
const fullRange = new vscode.Range(
|
||||
updatedDocument.positionAt(0),
|
||||
updatedDocument.positionAt(updatedDocument.getText().length),
|
||||
)
|
||||
edit.replace(updatedDocument.uri, fullRange, this.originalContent ?? "")
|
||||
// Apply the edit and save, since contents shouldn't have changed this won't show in local history unless of course the user made changes and saved during the edit
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
await updatedDocument.save()
|
||||
console.log(`File ${this.absolutePath} has been reverted to its original content.`)
|
||||
if (this.documentWasOpen) {
|
||||
await getHostBridgeProvider().windowClient.showTextDocument({
|
||||
path: this.absolutePath,
|
||||
options: {
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
},
|
||||
})
|
||||
await getHostBridgeProvider().windowClient.showTextDocument(
|
||||
ShowTextDocumentRequest.create({
|
||||
path: this.absolutePath,
|
||||
options: ShowTextDocumentOptions.create({
|
||||
preview: false,
|
||||
preserveFocus: true,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
await this.closeDiffView()
|
||||
await this.closeAllDiffViews()
|
||||
}
|
||||
|
||||
// edit is done
|
||||
await this.reset()
|
||||
}
|
||||
|
||||
async scrollToFirstDiff() {
|
||||
if (!this.isEditing) {
|
||||
private async closeAllDiffViews() {
|
||||
const tabs = vscode.window.tabGroups.all
|
||||
.flatMap((tg) => tg.tabs)
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME)
|
||||
for (const tab of tabs) {
|
||||
// trying to close dirty views results in save popup
|
||||
if (!tab.isDirty) {
|
||||
await vscode.window.tabGroups.close(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private scrollEditorToLine(line: number) {
|
||||
if (this.activeDiffEditor) {
|
||||
const scrollLine = line + 4
|
||||
this.activeDiffEditor.revealRange(
|
||||
new vscode.Range(scrollLine, 0, scrollLine, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
scrollToFirstDiff() {
|
||||
if (!this.activeDiffEditor) {
|
||||
return
|
||||
}
|
||||
const currentContent = (await this.getDocumentText()) || ""
|
||||
const currentContent = this.activeDiffEditor.document.getText()
|
||||
const diffs = diff.diffLines(this.originalContent || "", currentContent)
|
||||
let lineCount = 0
|
||||
for (const part of diffs) {
|
||||
if (part.added || part.removed) {
|
||||
// Found the first diff, scroll to it
|
||||
this.scrollEditorToLine(lineCount)
|
||||
this.activeDiffEditor.revealRange(
|
||||
new vscode.Range(lineCount, 0, lineCount, 0),
|
||||
vscode.TextEditorRevealType.InCenter,
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!part.removed) {
|
||||
@@ -366,8 +393,10 @@ export abstract class DiffViewProvider {
|
||||
this.originalContent = undefined
|
||||
this.createdDirs = []
|
||||
this.documentWasOpen = false
|
||||
this.activeDiffEditor = undefined
|
||||
this.fadedOverlayController = undefined
|
||||
this.activeLineController = undefined
|
||||
this.streamedLines = []
|
||||
|
||||
await this.resetDiffView()
|
||||
this.preDiagnostics = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,12 @@ export function extractCommitMessage(aiResponse: string): string {
|
||||
*/
|
||||
export async function copyCommitMessageToClipboard(message: string): Promise<void> {
|
||||
await writeTextToClipboard(message)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message copied to clipboard",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message copied to clipboard",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,16 +77,18 @@ export async function showCommitMessageOptions(message: string): Promise<void> {
|
||||
const editAction = "Edit Message"
|
||||
|
||||
const selectedAction = (
|
||||
await getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message generated",
|
||||
options: {
|
||||
modal: false,
|
||||
detail: message,
|
||||
items: [copyAction, applyAction, editAction],
|
||||
},
|
||||
})
|
||||
).selectedOption
|
||||
await getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message generated",
|
||||
options: {
|
||||
modal: false,
|
||||
detail: message,
|
||||
items: [copyAction, applyAction, editAction],
|
||||
},
|
||||
}),
|
||||
)
|
||||
)?.selectedOption
|
||||
|
||||
// Handle user dismissing the dialog (selectedAction is undefined)
|
||||
if (!selectedAction) {
|
||||
@@ -116,22 +120,28 @@ async function applyCommitMessageToGitInput(message: string): Promise<void> {
|
||||
if (api && api.repositories.length > 0) {
|
||||
const repo = api.repositories[0]
|
||||
repo.inputBox.value = message
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message applied to Git input",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Commit message applied to Git input",
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No Git repositories found",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "No Git repositories found",
|
||||
}),
|
||||
)
|
||||
await copyCommitMessageToClipboard(message)
|
||||
}
|
||||
} else {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Git extension not found",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Git extension not found",
|
||||
}),
|
||||
)
|
||||
await copyCommitMessageToClipboard(message)
|
||||
}
|
||||
}
|
||||
@@ -151,8 +161,10 @@ async function editCommitMessage(message: string): Promise<void> {
|
||||
path: document.uri.fsPath,
|
||||
}),
|
||||
)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Edit the commit message and copy when ready",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Edit the commit message and copy when ready",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@ export async function downloadTask(dateTs: number, conversationHistory: Anthropi
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to save markdown file: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to save markdown file: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ import * as os from "os"
|
||||
import * as vscode from "vscode"
|
||||
import { arePathsEqual } from "@utils/path"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { ShowTextDocumentRequest, ShowTextDocumentOptions, ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { writeFile } from "@utils/fs"
|
||||
|
||||
export async function openImage(dataUri: string) {
|
||||
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
|
||||
if (!matches) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid data URI format",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid data URI format",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const [, format, base64Data] = matches
|
||||
@@ -22,10 +24,12 @@ export async function openImage(dataUri: string) {
|
||||
await writeFile(tempFilePath, new Uint8Array(imageBuffer))
|
||||
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error opening image: ${error}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error opening image: ${error}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,18 +46,22 @@ export async function selectFiles(imagesAllowed: boolean): Promise<{ images: str
|
||||
const dimensions = sizeOf(uint8Array) // Get dimensions from Uint8Array
|
||||
if (dimensions.width! > 7500 || dimensions.height! > 7500) {
|
||||
console.warn(`Image dimensions exceed 7500px, skipping: ${filePath}`)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Image too large: ${path.basename(filePath)} was skipped (dimensions exceed 7500px).`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Image too large: ${path.basename(filePath)} was skipped (dimensions exceed 7500px).`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file or getting dimensions for ${filePath}:`, error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not read dimensions for ${path.basename(filePath)}, skipping.`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not read dimensions for ${path.basename(filePath)}, skipping.`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -72,18 +76,22 @@ export async function selectFiles(imagesAllowed: boolean): Promise<{ images: str
|
||||
const stats = await fs.stat(filePath)
|
||||
if (stats.size > 20 * 1000 * 1024) {
|
||||
console.warn(`File too large, skipping: ${filePath}`)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `File too large: ${path.basename(filePath)} was skipped (size exceeds 20MB).`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `File too large: ${path.basename(filePath)} was skipped (size exceeds 20MB).`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error checking file size for ${filePath}:`, error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not check file size for ${path.basename(filePath)}, skipping.`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Could not check file size for ${path.basename(filePath)}, skipping.`,
|
||||
}),
|
||||
)
|
||||
return null
|
||||
}
|
||||
return { type: "file", data: filePath }
|
||||
|
||||
@@ -31,35 +31,20 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
// constructor() {
|
||||
// super()
|
||||
|
||||
private async emitCurrentTerminalContents(): Promise<void> {
|
||||
try {
|
||||
const terminalSnapshot = await getLatestTerminalOutput()
|
||||
if (terminalSnapshot && terminalSnapshot.trim()) {
|
||||
const fallbackMessage = `The command's output could not be captured due to some technical issue, however it has been executed successfully. Here's the current terminal's content to help you get the command's output:\n\n${terminalSnapshot}`
|
||||
this.emit("line", fallbackMessage)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error capturing terminal output:", error)
|
||||
}
|
||||
}
|
||||
|
||||
async run(terminal: vscode.Terminal, command: string) {
|
||||
// Initialize state for new command
|
||||
await this.initializeForNewCommand(command)
|
||||
|
||||
console.log(`[TerminalProcess] Starting command: "${command}"`)
|
||||
console.log(`[TerminalProcess] Shell integration available: ${!!terminal.shellIntegration?.executeCommand}`)
|
||||
console.log(`[TerminalProcess] Terminal ID: ${terminal.name}`)
|
||||
|
||||
if (terminal.shellIntegration?.executeCommand) {
|
||||
await this.runWithShellIntegration(terminal, command)
|
||||
} else {
|
||||
// no shell integration detected, we'll fallback to running the command and capturing the terminal's output after some time
|
||||
await this.runWithoutShellIntegration(terminal, command)
|
||||
// When command does not produce any output, we can assume the shell integration API failed and as a fallback return the current terminal contents
|
||||
const emitCurrentTerminalContents = async () => {
|
||||
try {
|
||||
const terminalSnapshot = await getLatestTerminalOutput()
|
||||
if (terminalSnapshot && terminalSnapshot.trim()) {
|
||||
const fallbackMessage = `The command's output could not be captured due to some technical issue, however it has been executed successfully. Here's the current terminal's content to help you get the command's output:\n\n${terminalSnapshot}`
|
||||
this.emit("line", fallbackMessage)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error capturing terminal output:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeForNewCommand(command: string): Promise<void> {
|
||||
// Clear any existing grace period timer from previous commands
|
||||
if (this.gracePeriodTimer) {
|
||||
clearTimeout(this.gracePeriodTimer)
|
||||
@@ -80,372 +65,291 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
this.lastRetrievedIndex = 0
|
||||
this.isListening = true
|
||||
this.isHot = false
|
||||
|
||||
this.command = command
|
||||
}
|
||||
console.log(`[TerminalProcess] Starting command: "${command}"`)
|
||||
console.log(`[TerminalProcess] Shell integration available: ${!!terminal.shellIntegration?.executeCommand}`)
|
||||
console.log(`[TerminalProcess] Terminal ID: ${terminal.name}`)
|
||||
if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) {
|
||||
let execution
|
||||
let stream
|
||||
|
||||
private async runWithShellIntegration(terminal: vscode.Terminal, command: string): Promise<void> {
|
||||
// Execute command and get stream
|
||||
const stream = await this.executeCommandWithShellIntegration(terminal, command)
|
||||
if (!stream) {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize state for stream processing
|
||||
const streamState = this.initializeStreamState()
|
||||
|
||||
// Set up timeout for commands with no output
|
||||
const firstChunkTimeout = this.setupNoOutputTimeout(command, streamState)
|
||||
|
||||
// Process the output stream
|
||||
await this.processOutputStream(stream, command, streamState, firstChunkTimeout)
|
||||
|
||||
// Handle post-stream cleanup
|
||||
this.cleanupAfterStream(streamState, firstChunkTimeout)
|
||||
|
||||
// Determine command completion behavior
|
||||
await this.handleCommandCompletion(command, streamState)
|
||||
}
|
||||
|
||||
private async executeCommandWithShellIntegration(terminal: vscode.Terminal, command: string): Promise<any> {
|
||||
try {
|
||||
if (!terminal.shellIntegration || !terminal.shellIntegration.executeCommand) {
|
||||
throw new Error("Shell integration not available")
|
||||
try {
|
||||
execution = terminal.shellIntegration.executeCommand(command)
|
||||
stream = execution.read()
|
||||
} catch (error) {
|
||||
console.error(`[TerminalProcess] Failed to execute command: ${error}`)
|
||||
this.emit("error", error as Error)
|
||||
return
|
||||
}
|
||||
const execution = terminal.shellIntegration.executeCommand(command)
|
||||
return execution.read()
|
||||
} catch (error) {
|
||||
console.error(`[TerminalProcess] Failed to execute command: ${error}`)
|
||||
this.emit("error", error as Error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private initializeStreamState() {
|
||||
return {
|
||||
isFirstChunk: true,
|
||||
didOutputNonCommand: false,
|
||||
didEmitEmptyLine: false,
|
||||
receivedFirstChunk: false,
|
||||
}
|
||||
}
|
||||
// todo: need to handle errors
|
||||
let isFirstChunk = true
|
||||
let didOutputNonCommand = false
|
||||
let didEmitEmptyLine = false
|
||||
let receivedFirstChunk = false
|
||||
|
||||
private setupNoOutputTimeout(command: string, streamState: any): NodeJS.Timeout {
|
||||
// Set up a 3-second timeout to handle commands with no/delayed output.
|
||||
// This ensures the UI remains responsive by:
|
||||
// 1. Showing the "proceed while running" button after 3 seconds
|
||||
// 2. Capturing current terminal contents in case shell integration missed output
|
||||
// 3. Informing the user that the command is still running
|
||||
return setTimeout(async () => {
|
||||
if (!streamState.receivedFirstChunk && !streamState.didEmitEmptyLine) {
|
||||
console.log(`[TerminalProcess] First chunk timeout fired - no output received within 3s for: "${command}"`)
|
||||
this.emit("line", "") // empty line to show proceed button
|
||||
streamState.didEmitEmptyLine = true
|
||||
// Set up a timeout to emit empty line if no output is received within 3 seconds
|
||||
// This ensures the "proceed while running" button appears even for commands with no/delayed output
|
||||
const firstChunkTimeout = setTimeout(() => {
|
||||
if (!receivedFirstChunk && !didEmitEmptyLine) {
|
||||
console.log(`[TerminalProcess] First chunk timeout fired - no output received within 3s for: "${command}"`)
|
||||
this.emit("line", "") // empty line to show proceed button
|
||||
didEmitEmptyLine = true
|
||||
|
||||
// Capture terminal contents as fallback for shell integration issues
|
||||
await this.emitCurrentTerminalContents()
|
||||
// Also emit a message indicating the command might be running without output
|
||||
this.emit("line", "[Command is running but producing no output]")
|
||||
}
|
||||
}, 3000) // 3 second timeout
|
||||
|
||||
for await (let data of stream) {
|
||||
// Clear the timeout since we received output
|
||||
if (!receivedFirstChunk) {
|
||||
clearTimeout(firstChunkTimeout)
|
||||
receivedFirstChunk = true
|
||||
console.log(`[TerminalProcess] First chunk received for command: "${command}"`)
|
||||
}
|
||||
|
||||
// Log raw data length
|
||||
console.log(`[TerminalProcess] Raw data chunk received: ${data.length} chars`)
|
||||
if (!data || data.trim() === "") {
|
||||
console.log(`[TerminalProcess] WARNING: Received empty or whitespace-only chunk`)
|
||||
}
|
||||
// 1. Process chunk and remove artifacts
|
||||
if (isFirstChunk) {
|
||||
/*
|
||||
The first chunk we get from this stream needs to be processed to be more human readable, ie remove vscode's custom escape sequences and identifiers, removing duplicate first char bug, etc.
|
||||
*/
|
||||
|
||||
// bug where sometimes the command output makes its way into vscode shell integration metadata
|
||||
/*
|
||||
]633 is a custom sequence number used by VSCode shell integration:
|
||||
- OSC 633 ; A ST - Mark prompt start
|
||||
- OSC 633 ; B ST - Mark prompt end
|
||||
- OSC 633 ; C ST - Mark pre-execution (start of command output)
|
||||
- OSC 633 ; D [; <exitcode>] ST - Mark execution finished with optional exit code
|
||||
- OSC 633 ; E ; <commandline> [; <nonce>] ST - Explicitly set command line with optional nonce
|
||||
*/
|
||||
// if you print this data you might see something like "eecho hello worldo hello world;5ba85d14-e92a-40c4-b2fd-71525581eeb0]633;C" but this is actually just a bunch of escape sequences, ignore up to the first ;C
|
||||
/* ddateb15026-6a64-40db-b21f-2a621a9830f0]633;CTue Sep 17 06:37:04 EDT 2024 % ]633;D;0]633;P;Cwd=/Users/saoud/Repositories/test */
|
||||
// Gets output between ]633;C (command start) and ]633;D (command end)
|
||||
const outputBetweenSequences = this.removeLastLineArtifacts(
|
||||
data.match(/\]633;C([\s\S]*?)\]633;D/)?.[1] || "",
|
||||
).trim()
|
||||
|
||||
// Once we've retrieved any potential output between sequences, we can remove everything up to end of the last sequence
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||
const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g
|
||||
const lastMatch = [...data.matchAll(vscodeSequenceRegex)].pop()
|
||||
if (lastMatch && lastMatch.index !== undefined) {
|
||||
data = data.slice(lastMatch.index + lastMatch[0].length)
|
||||
}
|
||||
// Place output back after removing vscode sequences
|
||||
if (outputBetweenSequences) {
|
||||
data = outputBetweenSequences + "\n" + data
|
||||
}
|
||||
// remove ansi
|
||||
data = stripAnsi(data)
|
||||
// Split data by newlines
|
||||
const lines = data ? data.split("\n") : []
|
||||
// Remove non-human readable characters from the first line
|
||||
if (lines.length > 0) {
|
||||
lines[0] = lines[0].replace(/[^\x20-\x7E]/g, "")
|
||||
}
|
||||
// Check for duplicated first character that might be a terminal artifact
|
||||
// But skip this check for known syntax characters like {, [, ", etc.
|
||||
if (
|
||||
lines.length > 0 &&
|
||||
lines[0].length >= 2 &&
|
||||
lines[0][0] === lines[0][1] &&
|
||||
!["[", "{", '"', "'", "<", "("].includes(lines[0][0])
|
||||
) {
|
||||
lines[0] = lines[0].slice(1)
|
||||
}
|
||||
// Only remove specific terminal artifacts from line beginnings while preserving JSON syntax
|
||||
if (lines.length > 0) {
|
||||
// This regex only removes common terminal artifacts (%, $, >, #) and invisible control chars
|
||||
// but preserves important syntax chars like {, [, ", etc.
|
||||
lines[0] = lines[0].replace(/^[\x00-\x1F%$>#\s]*/, "")
|
||||
}
|
||||
if (lines.length > 1) {
|
||||
lines[1] = lines[1].replace(/^[\x00-\x1F%$>#\s]*/, "")
|
||||
}
|
||||
// Join lines back
|
||||
data = lines.join("\n")
|
||||
isFirstChunk = false
|
||||
} else {
|
||||
data = stripAnsi(data)
|
||||
}
|
||||
|
||||
// Ctrl+C detection: if user presses Ctrl+C, treat as command terminated
|
||||
if (data.includes("^C") || data.includes("\u0003")) {
|
||||
if (this.hotTimer) {
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
this.isHot = false
|
||||
break
|
||||
}
|
||||
|
||||
// first few chunks could be the command being echoed back, so we must ignore
|
||||
// note this means that 'echo' commands won't work
|
||||
if (!didOutputNonCommand) {
|
||||
const lines = data.split("\n")
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (command.includes(lines[i].trim())) {
|
||||
lines.splice(i, 1)
|
||||
i-- // Adjust index after removal
|
||||
} else {
|
||||
didOutputNonCommand = true
|
||||
break
|
||||
}
|
||||
}
|
||||
data = lines.join("\n")
|
||||
}
|
||||
|
||||
// 2. Set isHot depending on the command
|
||||
// Set to hot to stall API requests until terminal is cool again
|
||||
this.isHot = true
|
||||
if (this.hotTimer) {
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
// these markers indicate the command is some kind of local dev server recompiling the app, which we want to wait for output of before sending request to cline
|
||||
const compilingMarkers = ["compiling", "building", "bundling", "transpiling", "generating", "starting"]
|
||||
const markerNullifiers = [
|
||||
"compiled",
|
||||
"success",
|
||||
"finish",
|
||||
"complete",
|
||||
"succeed",
|
||||
"done",
|
||||
"end",
|
||||
"stop",
|
||||
"exit",
|
||||
"terminate",
|
||||
"error",
|
||||
"fail",
|
||||
]
|
||||
const isCompiling =
|
||||
compilingMarkers.some((marker) => data.toLowerCase().includes(marker.toLowerCase())) &&
|
||||
!markerNullifiers.some((nullifier) => data.toLowerCase().includes(nullifier.toLowerCase()))
|
||||
this.hotTimer = setTimeout(
|
||||
() => {
|
||||
this.isHot = false
|
||||
},
|
||||
isCompiling ? PROCESS_HOT_TIMEOUT_COMPILING : PROCESS_HOT_TIMEOUT_NORMAL,
|
||||
)
|
||||
|
||||
// For non-immediately returning commands we want to show loading spinner right away but this wouldn't happen until it emits a line break, so as soon as we get any output we emit "" to let webview know to show spinner
|
||||
if (!didEmitEmptyLine && !this.fullOutput && data) {
|
||||
this.emit("line", "") // empty line to indicate start of command output stream
|
||||
didEmitEmptyLine = true
|
||||
}
|
||||
|
||||
this.fullOutput += data
|
||||
if (this.isListening) {
|
||||
this.emitIfEol(data)
|
||||
this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length
|
||||
}
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
private async processOutputStream(
|
||||
stream: any,
|
||||
command: string,
|
||||
streamState: any,
|
||||
firstChunkTimeout: NodeJS.Timeout,
|
||||
): Promise<void> {
|
||||
for await (let data of stream) {
|
||||
// Handle first chunk received
|
||||
if (!streamState.receivedFirstChunk) {
|
||||
this.emitRemainingBufferIfListening()
|
||||
|
||||
// Clean up the first chunk timeout if it's still active
|
||||
if (!receivedFirstChunk) {
|
||||
clearTimeout(firstChunkTimeout)
|
||||
streamState.receivedFirstChunk = true
|
||||
console.log(`[TerminalProcess] First chunk received for command: "${command}"`)
|
||||
console.log(`[TerminalProcess] WARNING: Stream ended without receiving any chunks for command: "${command}"`)
|
||||
|
||||
// If we never received any chunks and haven't emitted anything yet, emit now
|
||||
if (!didEmitEmptyLine) {
|
||||
console.log(`[TerminalProcess] Emitting fallback empty line for no-output command`)
|
||||
this.emit("line", "") // empty line to show proceed button
|
||||
// this.emit("line", "[Command completed with no output]")
|
||||
await emitCurrentTerminalContents()
|
||||
didEmitEmptyLine = true
|
||||
}
|
||||
}
|
||||
|
||||
// Log chunk info
|
||||
console.log(`[TerminalProcess] Raw data chunk received: ${data.length} chars`)
|
||||
if (!data || data.trim() === "") {
|
||||
console.log(`[TerminalProcess] WARNING: Received empty or whitespace-only chunk`)
|
||||
// for now we don't want this delaying requests since we don't send diagnostics automatically anymore (previous: "even though the command is finished, we still want to consider it 'hot' in case so that api request stalls to let diagnostics catch up")
|
||||
if (this.hotTimer) {
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
|
||||
// Process the chunk
|
||||
data = await this.processChunk(data, command, streamState)
|
||||
|
||||
// Check for Ctrl+C interruption
|
||||
if (this.isCommandInterrupted(data)) {
|
||||
this.handleInterruption()
|
||||
break
|
||||
}
|
||||
|
||||
// Update hot/cool state based on output
|
||||
this.updateHotState(data)
|
||||
|
||||
// Handle output emission
|
||||
this.handleChunkOutput(data, streamState)
|
||||
}
|
||||
|
||||
// Emit any remaining buffer content
|
||||
this.emitRemainingBufferIfListening()
|
||||
}
|
||||
|
||||
private async processChunk(data: string, command: string, streamState: any): Promise<string> {
|
||||
if (streamState.isFirstChunk) {
|
||||
data = this.cleanFirstChunk(data)
|
||||
streamState.isFirstChunk = false
|
||||
} else {
|
||||
data = stripAnsi(data)
|
||||
}
|
||||
|
||||
// Remove command echo if not yet seen real output
|
||||
if (!streamState.didOutputNonCommand) {
|
||||
data = this.removeCommandEcho(data, command, streamState)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
private cleanFirstChunk(data: string): string {
|
||||
/*
|
||||
The first chunk we get from this stream needs to be processed to be more human readable,
|
||||
ie remove vscode's custom escape sequences and identifiers, removing duplicate first char bug, etc.
|
||||
*/
|
||||
|
||||
// bug where sometimes the command output makes its way into vscode shell integration metadata
|
||||
/*
|
||||
]633 is a custom sequence number used by VSCode shell integration:
|
||||
- OSC 633 ; A ST - Mark prompt start
|
||||
- OSC 633 ; B ST - Mark prompt end
|
||||
- OSC 633 ; C ST - Mark pre-execution (start of command output)
|
||||
- OSC 633 ; D [; <exitcode>] ST - Mark execution finished with optional exit code
|
||||
- OSC 633 ; E ; <commandline> [; <nonce>] ST - Explicitly set command line with optional nonce
|
||||
*/
|
||||
// Gets output between ]633;C (command start) and ]633;D (command end)
|
||||
const outputBetweenSequences = this.removeLastLineArtifacts(data.match(/\]633;C([\s\S]*?)\]633;D/)?.[1] || "").trim()
|
||||
|
||||
// Once we've retrieved any potential output between sequences, we can remove everything up to end of the last sequence
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||
const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g
|
||||
const lastMatch = [...data.matchAll(vscodeSequenceRegex)].pop()
|
||||
if (lastMatch && lastMatch.index !== undefined) {
|
||||
data = data.slice(lastMatch.index + lastMatch[0].length)
|
||||
}
|
||||
// Place output back after removing vscode sequences
|
||||
if (outputBetweenSequences) {
|
||||
data = outputBetweenSequences + "\n" + data
|
||||
}
|
||||
|
||||
// remove ansi codes
|
||||
data = stripAnsi(data)
|
||||
|
||||
// Split data by newlines for line-by-line processing
|
||||
const lines = data ? data.split("\n") : []
|
||||
|
||||
// Remove non-human readable characters from the first line
|
||||
if (lines.length > 0) {
|
||||
lines[0] = lines[0].replace(/[^\x20-\x7E]/g, "")
|
||||
}
|
||||
|
||||
// Check for duplicated first character that might be a terminal artifact
|
||||
// But skip this check for known syntax characters like {, [, ", etc.
|
||||
if (
|
||||
lines.length > 0 &&
|
||||
lines[0].length >= 2 &&
|
||||
lines[0][0] === lines[0][1] &&
|
||||
!["[", "{", '"', "'", "<", "("].includes(lines[0][0])
|
||||
) {
|
||||
lines[0] = lines[0].slice(1)
|
||||
}
|
||||
|
||||
// Remove specific terminal artifacts from line beginnings while preserving JSON syntax
|
||||
if (lines.length > 0) {
|
||||
// This regex only removes common terminal artifacts (%, $, >, #) and invisible control chars
|
||||
// but preserves important syntax chars like {, [, ", etc.
|
||||
lines[0] = lines[0].replace(/^[\x00-\x1F%$>#\s]*/, "")
|
||||
}
|
||||
if (lines.length > 1) {
|
||||
lines[1] = lines[1].replace(/^[\x00-\x1F%$>#\s]*/, "")
|
||||
}
|
||||
|
||||
// Join lines back
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
private removeCommandEcho(data: string, command: string, streamState: any): string {
|
||||
// first few chunks could be the command being echoed back, so we must ignore
|
||||
// note this means that 'echo' commands won't work properly
|
||||
const lines = data.split("\n")
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const trimmedLine = lines[i].trim()
|
||||
// Check if the line is the command being echoed back
|
||||
if (trimmedLine && trimmedLine === command.trim()) {
|
||||
lines.splice(i, 1)
|
||||
i-- // Adjust index after removal
|
||||
} else if (trimmedLine) {
|
||||
// We've hit actual output, not just the command echo
|
||||
streamState.didOutputNonCommand = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
private isCommandInterrupted(data: string): boolean {
|
||||
// Ctrl+C detection: if user presses Ctrl+C, treat as command terminated
|
||||
return data.includes("^C") || data.includes("\u0003")
|
||||
}
|
||||
|
||||
private handleInterruption(): void {
|
||||
if (this.hotTimer) {
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
this.isHot = false
|
||||
}
|
||||
|
||||
private updateHotState(data: string): void {
|
||||
// Set to hot to stall API requests until terminal is cool again
|
||||
this.isHot = true
|
||||
if (this.hotTimer) {
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
|
||||
const isCompiling = this.detectCompilationStatus(data)
|
||||
const timeout = isCompiling ? PROCESS_HOT_TIMEOUT_COMPILING : PROCESS_HOT_TIMEOUT_NORMAL
|
||||
|
||||
this.hotTimer = setTimeout(() => {
|
||||
this.isHot = false
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
private detectCompilationStatus(data: string): boolean {
|
||||
// these markers indicate the command is some kind of local dev server recompiling the app
|
||||
const compilingMarkers = ["compiling", "building", "bundling", "transpiling", "generating", "starting"]
|
||||
const markerNullifiers = [
|
||||
"compiled",
|
||||
"success",
|
||||
"finish",
|
||||
"complete",
|
||||
"succeed",
|
||||
"done",
|
||||
"end",
|
||||
"stop",
|
||||
"exit",
|
||||
"terminate",
|
||||
"error",
|
||||
"fail",
|
||||
]
|
||||
console.log(`[TerminalProcess] Stream ended for command: "${command}"`)
|
||||
console.log(`[TerminalProcess] Final output length: ${this.fullOutput.length} characters`)
|
||||
|
||||
return (
|
||||
compilingMarkers.some((marker) => data.toLowerCase().includes(marker.toLowerCase())) &&
|
||||
!markerNullifiers.some((nullifier) => data.toLowerCase().includes(nullifier.toLowerCase()))
|
||||
)
|
||||
}
|
||||
// Check if this looks like a command that completed vs one that's still running
|
||||
const quickCommands = ["cd ", "pwd", "ls ", "echo ", "mkdir ", "touch ", "rm ", "cp ", "mv "]
|
||||
const isQuickCommand = quickCommands.some((cmd) => command.startsWith(cmd) || command.includes(" && " + cmd))
|
||||
|
||||
private handleChunkOutput(data: string, streamState: any): void {
|
||||
// For non-immediately returning commands we want to show loading spinner right away
|
||||
if (!streamState.didEmitEmptyLine && !this.fullOutput && data) {
|
||||
this.emit("line", "") // empty line to indicate start of command output stream
|
||||
streamState.didEmitEmptyLine = true
|
||||
}
|
||||
// Check if output suggests a long-running process
|
||||
const longRunningIndicators = [
|
||||
"listening on",
|
||||
"server running",
|
||||
"started on",
|
||||
"watching for",
|
||||
"compiled successfully",
|
||||
"webpack",
|
||||
"vite",
|
||||
"nodemon",
|
||||
"dev server",
|
||||
"press ctrl",
|
||||
"to quit",
|
||||
"to exit",
|
||||
"to stop",
|
||||
]
|
||||
const hasLongRunningOutput = longRunningIndicators.some((indicator) =>
|
||||
this.fullOutput.toLowerCase().includes(indicator),
|
||||
)
|
||||
|
||||
this.fullOutput += data
|
||||
if (this.isListening) {
|
||||
this.emitIfEol(data)
|
||||
this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length
|
||||
}
|
||||
}
|
||||
// Check if this is likely a command that starts a server or long-running process
|
||||
const longRunningCommands = ["npm run", "npm start", "yarn", "node ", "python ", "serve", "dev", "watch"]
|
||||
const isLongRunningCommand = longRunningCommands.some((cmd) => command.includes(cmd))
|
||||
|
||||
private cleanupAfterStream(streamState: any, firstChunkTimeout: NodeJS.Timeout): void {
|
||||
// Clean up the first chunk timeout if it's still active
|
||||
if (!streamState.receivedFirstChunk) {
|
||||
clearTimeout(firstChunkTimeout)
|
||||
console.log(`[TerminalProcess] WARNING: Stream ended without receiving any chunks for command: "${this.command}"`)
|
||||
}
|
||||
|
||||
// Clear hot timer
|
||||
if (this.hotTimer) {
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
this.isHot = false
|
||||
|
||||
console.log(`[TerminalProcess] Stream ended for command: "${this.command}"`)
|
||||
console.log(`[TerminalProcess] Final output length: ${this.fullOutput.length} characters`)
|
||||
}
|
||||
|
||||
private async handleCommandCompletion(command: string, streamState: any): Promise<void> {
|
||||
const commandType = this.analyzeCommandType(command)
|
||||
|
||||
// Handle commands with no output
|
||||
if (this.fullOutput.length === 0) {
|
||||
console.log(`[TerminalProcess] WARNING: Process completed but no output was captured`)
|
||||
if (!streamState.didEmitEmptyLine) {
|
||||
await this.emitCurrentTerminalContents()
|
||||
if (this.fullOutput.length === 0) {
|
||||
console.log(`[TerminalProcess] WARNING: Process completed but no output was captured`)
|
||||
// Ensure we emit at least one line for UI feedback
|
||||
if (!didEmitEmptyLine) {
|
||||
// this.emit("line", "[Command completed silently]")
|
||||
await emitCurrentTerminalContents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if we should skip grace period
|
||||
if (commandType.isQuickCommand && !commandType.isLongRunning) {
|
||||
console.log(`[TerminalProcess] Command appears to have completed immediately, skipping grace period`)
|
||||
// Only skip grace period for truly quick commands that have no output or are known to complete instantly
|
||||
if ((this.fullOutput.length === 0 || isQuickCommand) && !isLongRunningCommand && !hasLongRunningOutput) {
|
||||
console.log(`[TerminalProcess] Command appears to have completed immediately, skipping grace period`)
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
} else {
|
||||
console.log(
|
||||
`[TerminalProcess] Command may still be running (longRunningCommand: ${isLongRunningCommand}, longRunningOutput: ${hasLongRunningOutput})`,
|
||||
)
|
||||
console.log(`[TerminalProcess] Starting grace period to detect true completion...`)
|
||||
// Start grace period - wait 2.5 seconds to see if more output comes
|
||||
// This prevents premature "proceed while running" for commands that clearly finished
|
||||
this.startGracePeriod()
|
||||
}
|
||||
} else {
|
||||
// no shell integration detected, we'll fallback to running the command and capturing the terminal's output after some time
|
||||
terminal.sendText(command, true)
|
||||
|
||||
// wait 3 seconds for the command to run
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
|
||||
// For terminals without shell integration, also try to capture terminal content
|
||||
await emitCurrentTerminalContents()
|
||||
// For terminals without shell integration, we can't know when the command completes
|
||||
// So we'll just emit the continue event after a delay
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
} else {
|
||||
console.log(`[TerminalProcess] Command may still be running (longRunningCommand: ${commandType.isLongRunning})`)
|
||||
console.log(`[TerminalProcess] Starting grace period to detect true completion...`)
|
||||
this.startGracePeriod()
|
||||
this.emit("no_shell_integration")
|
||||
// setTimeout(() => {
|
||||
// console.log(`Emitting continue after delay for terminal`)
|
||||
// // can't emit completed since we don't if the command actually completed, it could still be running server
|
||||
// }, 500) // Adjust this delay as needed
|
||||
}
|
||||
}
|
||||
|
||||
private analyzeCommandType(command: string): { isQuickCommand: boolean; isLongRunning: boolean } {
|
||||
// Check if this looks like a command that completed vs one that's still running
|
||||
const quickCommands = ["cd ", "pwd", "ls ", "echo ", "mkdir ", "touch ", "rm ", "cp ", "mv "]
|
||||
const isQuickCommand = quickCommands.some((cmd) => command.startsWith(cmd) || command.includes(" && " + cmd))
|
||||
|
||||
// Check if output suggests a long-running process
|
||||
const longRunningIndicators = [
|
||||
"listening on",
|
||||
"server running",
|
||||
"started on",
|
||||
"watching for",
|
||||
"compiled successfully",
|
||||
"webpack",
|
||||
"vite",
|
||||
"nodemon",
|
||||
"dev server",
|
||||
"press ctrl",
|
||||
"to quit",
|
||||
"to exit",
|
||||
"to stop",
|
||||
]
|
||||
const hasLongRunningOutput = longRunningIndicators.some((indicator) => this.fullOutput.toLowerCase().includes(indicator))
|
||||
|
||||
// Check if this is likely a command that starts a server or long-running process
|
||||
const longRunningCommands = ["npm run", "npm start", "yarn", "node ", "python ", "serve", "dev", "watch"]
|
||||
const isLongRunningCommand = longRunningCommands.some((cmd) => command.includes(cmd))
|
||||
|
||||
return {
|
||||
isQuickCommand: this.fullOutput.length === 0 || isQuickCommand,
|
||||
isLongRunning: isLongRunningCommand || hasLongRunningOutput,
|
||||
}
|
||||
}
|
||||
|
||||
private async runWithoutShellIntegration(terminal: vscode.Terminal, command: string): Promise<void> {
|
||||
// Send command to terminal
|
||||
terminal.sendText(command, true)
|
||||
|
||||
// wait 3 seconds for the command to run
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
|
||||
// For terminals without shell integration, also try to capture terminal content
|
||||
await this.emitCurrentTerminalContents()
|
||||
// For terminals without shell integration, we can't know when the command completes
|
||||
// So we'll just emit the continue event after a delay
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
this.emit("no_shell_integration")
|
||||
// setTimeout(() => {
|
||||
// console.log(`Emitting continue after delay for terminal`)
|
||||
// // can't emit completed since we don't if the command actually completed, it could still be running server
|
||||
// }, 500) // Adjust this delay as needed
|
||||
}
|
||||
|
||||
// Inspired by https://github.com/sindresorhus/execa/blob/main/lib/transform/split.js
|
||||
private emitIfEol(chunk: string) {
|
||||
this.buffer += chunk
|
||||
|
||||
@@ -8,12 +8,14 @@ import type {
|
||||
UserResponse,
|
||||
} from "@shared/ClineAccount"
|
||||
import { AuthService } from "../auth/AuthService"
|
||||
import { clineEnvConfig } from "@/config"
|
||||
|
||||
export class ClineAccountService {
|
||||
private static instance: ClineAccountService
|
||||
private _authService: AuthService
|
||||
private readonly _baseUrl = clineEnvConfig.apiBaseUrl
|
||||
// TODO: replace this with a global API Host
|
||||
private readonly _baseUrl = "http://localhost:7777"
|
||||
// private readonly _baseUrl = "https://core-api.staging.int.cline.bot"
|
||||
// private readonly _baseUrl = "http://localhost:7777"
|
||||
|
||||
constructor() {
|
||||
this._authService = AuthService.getInstance()
|
||||
|
||||
@@ -5,9 +5,10 @@ import { StreamingResponseHandler, getRequestRegistry } from "@/core/controller/
|
||||
import { FirebaseAuthProvider } from "./providers/FirebaseAuthProvider"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { storeSecret } from "@/core/storage/state"
|
||||
import { clineEnvConfig } from "@/config"
|
||||
|
||||
const DefaultClineAccountURI = `${clineEnvConfig.appBaseUrl}/auth`
|
||||
const DefaultClineAccountURI = "http://localhost:3000/auth"
|
||||
// const DefaultClineAccountURI = "https://staging-app.cline.bot/auth"
|
||||
// const DefaultClineAccountURI = "http://localhost:3000/auth"
|
||||
let authProviders: any[] = []
|
||||
|
||||
type ServiceConfig = {
|
||||
@@ -69,7 +70,38 @@ export class AuthService {
|
||||
const authProvidersConfigs = [
|
||||
{
|
||||
name: "firebase",
|
||||
config: clineEnvConfig.firebase,
|
||||
// config: {
|
||||
// apiKey: "AIzaSyC5rx59Xt8UgwdU3PCfzUF7vCwmp9-K2vk",
|
||||
// authDomain: "cline-prod.firebaseapp.com",
|
||||
// projectId: "cline-prod",
|
||||
// storageBucket: "cline-prod.firebasestorage.app",
|
||||
// messagingSenderId: "941048379330",
|
||||
// appId: "1:941048379330:web:45058eedeefc5cdfcc485b",
|
||||
// },
|
||||
// Uncomment for staging environment
|
||||
// config: {
|
||||
// apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
|
||||
// authDomain: "cline-staging.firebaseapp.com",
|
||||
// projectId: "cline-staging",
|
||||
// storageBucket: "cline-staging.firebasestorage.app",
|
||||
// messagingSenderId: "853479478430",
|
||||
// appId: "1:853479478430:web:2de0dba1c63c3262d4578f",
|
||||
// },
|
||||
// Uncomment for local development environment
|
||||
config: {
|
||||
apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
|
||||
authDomain: "cline-preview.firebaseapp.com",
|
||||
projectId: "cline-preview",
|
||||
storageBucket: "cline-preview.firebasestorage.app",
|
||||
messagingSenderId: "654681443338",
|
||||
appId: "1:654681443338:web:93bfe710626a573d9123f3",
|
||||
measurementId: "G-QL8CKK5TNJ",
|
||||
},
|
||||
// config: {
|
||||
// apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
|
||||
// authDomain: "cline-preview.firebaseapp.com",
|
||||
// projectId: "cline-preview",
|
||||
// }
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { GithubAuthProvider, GoogleAuthProvider, User, getAuth, signInWithCreden
|
||||
import { ExtensionContext } from "vscode"
|
||||
import { ClineAccountUserInfo, ClineAuthInfo } from "../AuthService"
|
||||
import { jwtDecode } from "jwt-decode"
|
||||
import { clineEnvConfig } from "@/config"
|
||||
|
||||
export class FirebaseAuthProvider {
|
||||
private _config: any
|
||||
@@ -70,7 +69,7 @@ export class FirebaseAuthProvider {
|
||||
// Now retrieve the user info from the backend (this was an easy solution to keep providing user profile details like name and email, but we should move to using the fetchMe() function instead)
|
||||
// Fetch user info from Cline API
|
||||
// TODO: consolidate with fetchMe() instead of making the call directly here
|
||||
const userResponse = await axios.get(`${clineEnvConfig.apiBaseUrl}/api/v1/users/me`, {
|
||||
const userResponse = await axios.get("https://api.cline.bot/api/v1/users/me", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${idToken}`,
|
||||
},
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "../../shared/ClineAccount"
|
||||
import { serializeError } from "serialize-error"
|
||||
|
||||
export enum ClineErrorType {
|
||||
Auth = "auth",
|
||||
Network = "network",
|
||||
RateLimit = "rateLimit",
|
||||
Balance = "balance",
|
||||
}
|
||||
|
||||
interface ErrorDetails {
|
||||
/**
|
||||
* The HTTP status code of the error, if applicable.
|
||||
*/
|
||||
status?: number
|
||||
/**
|
||||
* The request ID associated with the error, if available.
|
||||
* This can be useful for debugging and support.
|
||||
*/
|
||||
request_id?: string
|
||||
/**
|
||||
* Specific error code provided by the API or service.
|
||||
*/
|
||||
code?: string
|
||||
/**
|
||||
* The model ID associated with the error, if applicable.
|
||||
* This is useful for identifying which model the error relates to.
|
||||
*/
|
||||
modelId?: string
|
||||
/**
|
||||
* The provider ID associated with the error, if applicable.
|
||||
* This is useful for identifying which provider the error relates to.
|
||||
*/
|
||||
providerId?: string
|
||||
/**
|
||||
* The error message associated with the error, if applicable.
|
||||
*/
|
||||
message?: string
|
||||
// Additional details that might be present in the error
|
||||
// This can include things like current balance, error messages, etc.
|
||||
details?: any
|
||||
}
|
||||
|
||||
const RATE_LIMIT_PATTERNS = [/status code 429/i, /rate limit/i, /too many requests/i, /quota exceeded/i, /resource exhausted/i]
|
||||
|
||||
export class ClineError extends Error {
|
||||
readonly title = "ClineError"
|
||||
readonly _error: ErrorDetails
|
||||
|
||||
// Error details per providers:
|
||||
// Cline: error?.error
|
||||
// Ollama: error?.cause
|
||||
// tbc
|
||||
constructor(
|
||||
raw: any,
|
||||
public readonly modelId?: string,
|
||||
public readonly providerId?: string,
|
||||
) {
|
||||
const error = serializeError(raw)
|
||||
|
||||
const message = error.message || String(error) || error?.cause?.means
|
||||
super(message)
|
||||
|
||||
// Extract status from multiple possible locations
|
||||
const status = error.status || error.statusCode || error.response?.status
|
||||
|
||||
// Construct the error details object to includes relevant information
|
||||
// And ensure it has a consistent structure
|
||||
this._error = {
|
||||
message: raw.message || message,
|
||||
status,
|
||||
request_id: error.request_id || error.response?.request_id,
|
||||
code: error.code || error?.cause?.code,
|
||||
modelId,
|
||||
providerId,
|
||||
details: error.details || error.error, // Additional details provided by the server
|
||||
...error,
|
||||
stack: undefined, // Avoid serializing stack trace to keep the error object clean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the error to a JSON string that allows for easy transmission and storage.
|
||||
* This is useful for logging or sending error details to a webviews.
|
||||
*/
|
||||
public serialize(): string {
|
||||
return JSON.stringify({
|
||||
message: this.message,
|
||||
status: this._error.status,
|
||||
request_id: this._error.request_id,
|
||||
code: this._error.code,
|
||||
modelId: this.modelId,
|
||||
providerId: this.providerId,
|
||||
details: this._error.details,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a stringified error into a ClineError instance.
|
||||
*/
|
||||
static parse(errorStr?: string, modelId?: string): ClineError | undefined {
|
||||
if (!errorStr || typeof errorStr !== "string") {
|
||||
return undefined
|
||||
}
|
||||
return ClineError.transform(errorStr, modelId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms any object into a ClineError instance.
|
||||
* Always returns a ClineError, even if the input is not a valid error object.
|
||||
*/
|
||||
static transform(error: any, modelId?: string, providerId?: string): ClineError {
|
||||
try {
|
||||
return new ClineError(JSON.parse(error), modelId, providerId)
|
||||
} catch {
|
||||
return new ClineError(error, modelId, providerId)
|
||||
}
|
||||
}
|
||||
|
||||
public isErrorType(type: ClineErrorType): boolean {
|
||||
return ClineError.getErrorType(this) === type
|
||||
}
|
||||
|
||||
/**
|
||||
* Is known error type based on the error code, status, and details.
|
||||
* This is useful for determining how to handle the error in the UI or logic.
|
||||
*/
|
||||
static getErrorType(err: ClineError): ClineErrorType | undefined {
|
||||
const { code, status, details } = err._error
|
||||
|
||||
// Check balance error first (most specific)
|
||||
if (code === "insufficient_credits" && typeof details?.current_balance === "number") {
|
||||
return ClineErrorType.Balance
|
||||
}
|
||||
|
||||
// Check auth errors
|
||||
if (code === "ERR_BAD_REQUEST" || status === 401) {
|
||||
return ClineErrorType.Auth
|
||||
}
|
||||
|
||||
// Check for auth message (only if message exists)
|
||||
const message = err.message
|
||||
if (message?.includes(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE)) {
|
||||
return ClineErrorType.Auth
|
||||
}
|
||||
|
||||
// Check rate limit patterns
|
||||
if (message) {
|
||||
const lowerMessage = message.toLowerCase()
|
||||
if (RATE_LIMIT_PATTERNS.some((pattern) => pattern.test(lowerMessage))) {
|
||||
return ClineErrorType.RateLimit
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as Sentry from "@sentry/browser"
|
||||
import * as vscode from "vscode"
|
||||
import { telemetryService } from "../posthog/telemetry/TelemetryService"
|
||||
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
|
||||
import * as pkg from "../../../package.json"
|
||||
import { ClineError } from "./ClineError"
|
||||
|
||||
let telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
|
||||
let isTelemetryEnabled = ["all", "error"].includes(telemetryLevel)
|
||||
@@ -16,8 +15,6 @@ vscode.workspace.onDidChangeConfiguration(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const isDev = process.env.IS_DEV === "true"
|
||||
|
||||
export class ErrorService {
|
||||
private static serviceEnabled: boolean
|
||||
private static serviceLevel: string
|
||||
@@ -32,7 +29,7 @@ export class ErrorService {
|
||||
beforeSend(event) {
|
||||
// TelemetryService keeps track of whether the user has opted in to telemetry/error reporting
|
||||
const isUserManuallyOptedIn = telemetryService.isTelemetryEnabled()
|
||||
if (isUserManuallyOptedIn && ErrorService.isEnabled() && !isDev) {
|
||||
if (isUserManuallyOptedIn && ErrorService.isEnabled()) {
|
||||
return event
|
||||
}
|
||||
return null
|
||||
@@ -68,7 +65,7 @@ export class ErrorService {
|
||||
}
|
||||
}
|
||||
|
||||
static logException(error: Error | ClineError): void {
|
||||
static logException(error: Error): void {
|
||||
// Don't log if telemetry is off
|
||||
const isUserManuallyOptedIn = telemetryService.isTelemetryEnabled()
|
||||
if (!isUserManuallyOptedIn || !ErrorService.isEnabled()) {
|
||||
@@ -96,8 +93,4 @@ export class ErrorService {
|
||||
static isEnabled(): boolean {
|
||||
return ErrorService.serviceEnabled
|
||||
}
|
||||
|
||||
static toClineError(rawError: any, modelId?: string, providerId?: string): ClineError {
|
||||
return ClineError.transform(rawError, modelId, providerId)
|
||||
}
|
||||
}
|
||||
|
||||
+75
-94
@@ -35,10 +35,10 @@ import { secondsToMs } from "@utils/time"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import { DEFAULT_REQUEST_TIMEOUT_MS } from "./constants"
|
||||
import { McpConnection, McpServerConfig, Transport } from "./types"
|
||||
import { McpConnection, McpServerConfig } from "./types"
|
||||
import { BaseConfigSchema, ServerConfigSchema, McpSettingsSchema } from "./schemas"
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { ShowMessageRequest, ShowMessageType } from "@/shared/proto/host/window"
|
||||
export class McpHub {
|
||||
getMcpServersPath: () => Promise<string>
|
||||
private getSettingsDirectoryPath: () => Promise<string>
|
||||
@@ -109,20 +109,24 @@ export class McpHub {
|
||||
try {
|
||||
config = JSON.parse(content)
|
||||
} catch (error) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings format. Please ensure your settings follow the correct JSON format.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings format. Please ensure your settings follow the correct JSON format.",
|
||||
}),
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Validate against schema
|
||||
const result = McpSettingsSchema.safeParse(config)
|
||||
if (!result.success) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings schema.",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Invalid MCP settings schema.",
|
||||
}),
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -144,9 +148,9 @@ export class McpHub {
|
||||
}),
|
||||
{
|
||||
onResponse: async (response) => {
|
||||
// console.log(
|
||||
// `[DEBUG] MCP settings ${response.type === FileChangeEvent_ChangeType.CHANGED ? "changed" : "event"}`,
|
||||
// )
|
||||
console.log(
|
||||
`[DEBUG] MCP settings ${response.type === FileChangeEvent_ChangeType.CHANGED ? "changed" : "event"}`,
|
||||
)
|
||||
|
||||
// Only process the file if it was changed (not created or deleted)
|
||||
if (response.type === FileChangeEvent_ChangeType.CHANGED) {
|
||||
@@ -154,6 +158,12 @@ export class McpHub {
|
||||
if (settings) {
|
||||
try {
|
||||
await this.updateServerConnections(settings.mcpServers)
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "MCP servers updated",
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Failed to process MCP settings change:", error)
|
||||
}
|
||||
@@ -164,7 +174,7 @@ export class McpHub {
|
||||
console.error("Error watching MCP settings file:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
//console.log("[DEBUG] MCP settings file watch completed")
|
||||
console.log("[DEBUG] MCP settings file watch completed")
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -192,23 +202,6 @@ export class McpHub {
|
||||
// Remove existing connection if it exists (should never happen, the connection should be deleted beforehand)
|
||||
this.connections = this.connections.filter((conn) => conn.server.name !== name)
|
||||
|
||||
if (config.disabled) {
|
||||
console.log(`[MCP Debug] Creating disabled connection object for server "${name}"`)
|
||||
// Create a connection object for disabled server so it appears in UI
|
||||
const disabledConnection: McpConnection = {
|
||||
server: {
|
||||
name,
|
||||
config: JSON.stringify(config),
|
||||
status: "disconnected",
|
||||
disabled: true,
|
||||
},
|
||||
client: null as unknown as Client,
|
||||
transport: null as unknown as Transport,
|
||||
}
|
||||
this.connections.push(disabledConnection)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Each MCP server requires its own transport connection and has unique capabilities, configurations, and error handling. Having separate clients also allows proper scoping of resources/tools and independent server management like reconnection.
|
||||
const client = new Client(
|
||||
@@ -420,10 +413,12 @@ export class McpHub {
|
||||
console.log(`[MCP Fallback Notification] ${name}:`, JSON.stringify(notification, null, 2))
|
||||
|
||||
// Show in VS Code for visibility
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `MCP ${name}: ${notification.method || "unknown"} - ${JSON.stringify(notification.params || {})}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
console.log(`[MCP Debug] Successfully set fallback notification handler for ${name}`)
|
||||
} catch (error) {
|
||||
@@ -458,11 +453,6 @@ export class McpHub {
|
||||
throw new Error(`No connection found for server: ${serverName}`)
|
||||
}
|
||||
|
||||
// Disabled servers don't have clients, so return empty tools list
|
||||
if (connection.server.disabled || !connection.client) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await connection.client.request({ method: "tools/list" }, ListToolsResultSchema, {
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
})
|
||||
@@ -488,16 +478,9 @@ export class McpHub {
|
||||
|
||||
private async fetchResourcesList(serverName: string): Promise<McpResource[]> {
|
||||
try {
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
|
||||
// Disabled servers don't have clients, so return empty resources list
|
||||
if (!connection || connection.server.disabled || !connection.client) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await connection.client.request({ method: "resources/list" }, ListResourcesResultSchema, {
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
})
|
||||
const response = await this.connections
|
||||
.find((conn) => conn.server.name === serverName)
|
||||
?.client.request({ method: "resources/list" }, ListResourcesResultSchema, { timeout: DEFAULT_REQUEST_TIMEOUT_MS })
|
||||
return response?.resources || []
|
||||
} catch (error) {
|
||||
// console.error(`Failed to fetch resources for ${serverName}:`, error)
|
||||
@@ -507,20 +490,11 @@ export class McpHub {
|
||||
|
||||
private async fetchResourceTemplatesList(serverName: string): Promise<McpResourceTemplate[]> {
|
||||
try {
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
|
||||
// Disabled servers don't have clients, so return empty resource templates list
|
||||
if (!connection || connection.server.disabled || !connection.client) {
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await connection.client.request(
|
||||
{ method: "resources/templates/list" },
|
||||
ListResourceTemplatesResultSchema,
|
||||
{
|
||||
const response = await this.connections
|
||||
.find((conn) => conn.server.name === serverName)
|
||||
?.client.request({ method: "resources/templates/list" }, ListResourceTemplatesResultSchema, {
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
return response?.resourceTemplates || []
|
||||
} catch (error) {
|
||||
@@ -533,13 +507,8 @@ export class McpHub {
|
||||
const connection = this.connections.find((conn) => conn.server.name === name)
|
||||
if (connection) {
|
||||
try {
|
||||
// Only close transport and client if they exist (disabled servers don't have them)
|
||||
if (connection.transport) {
|
||||
await connection.transport.close()
|
||||
}
|
||||
if (connection.client) {
|
||||
await connection.client.close()
|
||||
}
|
||||
await connection.transport.close()
|
||||
await connection.client.close()
|
||||
} catch (error) {
|
||||
console.error(`Failed to close transport for ${name}:`, error)
|
||||
}
|
||||
@@ -702,10 +671,12 @@ export class McpHub {
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
const config = connection?.server.config
|
||||
if (config) {
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `Restarting ${serverName} MCP server...`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `Restarting ${serverName} MCP server...`,
|
||||
}),
|
||||
)
|
||||
connection.server.status = "connecting"
|
||||
connection.server.error = ""
|
||||
await this.notifyWebviewOfServerChanges()
|
||||
@@ -714,16 +685,20 @@ export class McpHub {
|
||||
await this.deleteConnection(serverName)
|
||||
// Try to connect again using existing config
|
||||
await this.connectToServer(serverName, JSON.parse(config), "internal")
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `${serverName} MCP server connected`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: `${serverName} MCP server connected`,
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Failed to restart connection for ${serverName}:`, error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to connect to ${serverName} MCP server`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to connect to ${serverName} MCP server`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,10 +784,12 @@ export class McpHub {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", error.message, error.stack)
|
||||
}
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -969,10 +946,12 @@ export class McpHub {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update autoApprove settings:", error)
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to update autoApprove settings",
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to update autoApprove settings",
|
||||
}),
|
||||
)
|
||||
throw error // Re-throw to ensure the error is properly handled
|
||||
}
|
||||
}
|
||||
@@ -1090,10 +1069,12 @@ export class McpHub {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", error.message, error.stack)
|
||||
}
|
||||
getHostBridgeProvider().windowClient.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`,
|
||||
})
|
||||
getHostBridgeProvider().windowClient.showMessage(
|
||||
ShowMessageRequest.create({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}),
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PostHog } from "posthog-node"
|
||||
import { posthogConfig } from "../../shared/services/config/posthog-config"
|
||||
import { posthogConfig } from "@/shared/services/config/posthog-config"
|
||||
|
||||
class PostHogClientProvider {
|
||||
private static instance: PostHogClientProvider
|
||||
|
||||
@@ -5,7 +5,6 @@ import { version as extensionVersion } from "../../../../package.json"
|
||||
import type { TaskFeedbackType } from "@shared/WebviewMessage"
|
||||
import type { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { posthogClientProvider } from "../PostHogClientProvider"
|
||||
import { Mode } from "@/shared/ChatSettings"
|
||||
|
||||
/**
|
||||
* TelemetryService handles telemetry event tracking for the Cline extension
|
||||
@@ -313,7 +312,7 @@ class TelemetryService {
|
||||
* @param taskId Unique identifier for the task
|
||||
* @param mode The mode being switched to (plan or act)
|
||||
*/
|
||||
public captureModeSwitch(taskId: string, mode: Mode) {
|
||||
public captureModeSwitch(taskId: string, mode: "plan" | "act") {
|
||||
this.capture({
|
||||
event: TelemetryService.EVENTS.TASK.MODE_SWITCH,
|
||||
properties: {
|
||||
@@ -535,7 +534,7 @@ class TelemetryService {
|
||||
* @param qty The quantity of options that were presented
|
||||
* @param mode The mode in which the option was selected ("plan" or "act")
|
||||
*/
|
||||
public captureOptionSelected(taskId: string, qty: number, mode: Mode) {
|
||||
public captureOptionSelected(taskId: string, qty: number, mode: "plan" | "act") {
|
||||
this.capture({
|
||||
event: TelemetryService.EVENTS.TASK.OPTION_SELECTED,
|
||||
properties: {
|
||||
@@ -552,7 +551,7 @@ class TelemetryService {
|
||||
* @param qty The quantity of options that were presented
|
||||
* @param mode The mode in which the custom response was provided ("plan" or "act")
|
||||
*/
|
||||
public captureOptionsIgnored(taskId: string, qty: number, mode: Mode) {
|
||||
public captureOptionsIgnored(taskId: string, qty: number, mode: "plan" | "act") {
|
||||
this.capture({
|
||||
event: TelemetryService.EVENTS.TASK.OPTIONS_IGNORED,
|
||||
properties: {
|
||||
|
||||
@@ -274,8 +274,7 @@ export function createTestServer(webviewProvider?: WebviewProvider): http.Server
|
||||
await updateApiConfiguration(visibleWebview.controller.context, updatedConfig)
|
||||
|
||||
// Update global state to use cline provider
|
||||
await updateGlobalState(visibleWebview.controller.context, "planModeApiProvider", "cline")
|
||||
await updateGlobalState(visibleWebview.controller.context, "actModeApiProvider", "cline")
|
||||
await updateGlobalState(visibleWebview.controller.context, "apiProvider", "cline" as ApiProvider)
|
||||
|
||||
// Post state to webview to reflect changes
|
||||
await visibleWebview.controller.postStateToWebview()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
export type OpenAIReasoningEffort = "low" | "medium" | "high"
|
||||
|
||||
export type Mode = "plan" | "act"
|
||||
|
||||
export interface ChatSettings {
|
||||
mode: Mode
|
||||
mode: "plan" | "act"
|
||||
preferredLanguage?: string
|
||||
openAIReasoningEffort?: OpenAIReasoningEffort
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { HistoryItem } from "./HistoryItem"
|
||||
import { TelemetrySetting } from "./TelemetrySetting"
|
||||
import { ClineRulesToggles } from "./cline-rules"
|
||||
import { UserInfo } from "./UserInfo"
|
||||
import { McpDisplayMode } from "./McpDisplayMode"
|
||||
import { McpDisplayMode, DEFAULT_MCP_DISPLAY_MODE } from "./McpDisplayMode"
|
||||
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
|
||||
+22
-97
@@ -29,19 +29,22 @@ export type ApiProvider =
|
||||
| "cerebras"
|
||||
| "sapaicore"
|
||||
| "groq"
|
||||
| "huggingface"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
// Global configuration (not mode-specific)
|
||||
apiModelId?: string
|
||||
apiKey?: string // anthropic
|
||||
clineAccountId?: string
|
||||
taskId?: string // Used to identify the task in API requests
|
||||
liteLlmBaseUrl?: string
|
||||
liteLlmModelId?: string
|
||||
liteLlmApiKey?: string
|
||||
liteLlmUsePromptCache?: boolean
|
||||
openAiHeaders?: Record<string, string> // Custom headers for OpenAI requests
|
||||
liteLlmModelInfo?: LiteLLMModelInfo
|
||||
anthropicBaseUrl?: string
|
||||
openRouterApiKey?: string
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
openRouterProviderSorting?: string
|
||||
awsAccessKey?: string
|
||||
awsSecretKey?: string
|
||||
@@ -54,99 +57,64 @@ export interface ApiHandlerOptions {
|
||||
awsProfile?: string
|
||||
awsBedrockApiKey?: string
|
||||
awsBedrockEndpoint?: string
|
||||
awsBedrockCustomSelected?: boolean
|
||||
awsBedrockCustomModelBaseId?: BedrockModelId
|
||||
claudeCodePath?: string
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
openAiBaseUrl?: string
|
||||
openAiApiKey?: string
|
||||
openAiModelId?: string
|
||||
openAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
ollamaModelId?: string
|
||||
ollamaBaseUrl?: string
|
||||
ollamaApiOptionsCtxNum?: string
|
||||
lmStudioModelId?: string
|
||||
lmStudioBaseUrl?: string
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
openAiNativeApiKey?: string
|
||||
deepSeekApiKey?: string
|
||||
requestyApiKey?: string
|
||||
requestyModelId?: string
|
||||
requestyModelInfo?: ModelInfo
|
||||
togetherApiKey?: string
|
||||
togetherModelId?: string
|
||||
fireworksApiKey?: string
|
||||
fireworksModelId?: string
|
||||
fireworksModelMaxCompletionTokens?: number
|
||||
fireworksModelMaxTokens?: number
|
||||
qwenApiKey?: string
|
||||
doubaoApiKey?: string
|
||||
mistralApiKey?: string
|
||||
azureApiVersion?: string
|
||||
vsCodeLmModelSelector?: LanguageModelChatSelector
|
||||
qwenApiLine?: string
|
||||
moonshotApiLine?: string
|
||||
moonshotApiKey?: string
|
||||
huggingFaceApiKey?: string
|
||||
nebiusApiKey?: string
|
||||
asksageApiUrl?: string
|
||||
asksageApiKey?: string
|
||||
xaiApiKey?: string
|
||||
thinkingBudgetTokens?: number
|
||||
reasoningEffort?: string
|
||||
sambanovaApiKey?: string
|
||||
cerebrasApiKey?: string
|
||||
groqApiKey?: string
|
||||
groqModelId?: string
|
||||
groqModelInfo?: ModelInfo
|
||||
requestTimeoutMs?: number
|
||||
sapAiCoreClientId?: string
|
||||
sapAiCoreClientSecret?: string
|
||||
sapAiResourceGroup?: string
|
||||
sapAiCoreTokenUrl?: string
|
||||
sapAiCoreBaseUrl?: string
|
||||
sapAiCoreModelId?: string
|
||||
onRetryAttempt?: (attempt: number, maxRetries: number, delay: number, error: any) => void
|
||||
// Plan mode configurations
|
||||
planModeApiModelId?: string
|
||||
planModeThinkingBudgetTokens?: number
|
||||
planModeReasoningEffort?: string
|
||||
planModeVsCodeLmModelSelector?: LanguageModelChatSelector
|
||||
planModeAwsBedrockCustomSelected?: boolean
|
||||
planModeAwsBedrockCustomModelBaseId?: BedrockModelId
|
||||
planModeOpenRouterModelId?: string
|
||||
planModeOpenRouterModelInfo?: ModelInfo
|
||||
planModeOpenAiModelId?: string
|
||||
planModeOpenAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
planModeOllamaModelId?: string
|
||||
planModeLmStudioModelId?: string
|
||||
planModeLiteLlmModelId?: string
|
||||
planModeLiteLlmModelInfo?: LiteLLMModelInfo
|
||||
planModeRequestyModelId?: string
|
||||
planModeRequestyModelInfo?: ModelInfo
|
||||
planModeTogetherModelId?: string
|
||||
planModeFireworksModelId?: string
|
||||
planModeSapAiCoreModelId?: string
|
||||
planModeGroqModelId?: string
|
||||
planModeGroqModelInfo?: ModelInfo
|
||||
planModeHuggingFaceModelId?: string
|
||||
planModeHuggingFaceModelInfo?: ModelInfo
|
||||
// Act mode configurations
|
||||
|
||||
actModeApiModelId?: string
|
||||
actModeThinkingBudgetTokens?: number
|
||||
actModeReasoningEffort?: string
|
||||
actModeVsCodeLmModelSelector?: LanguageModelChatSelector
|
||||
actModeAwsBedrockCustomSelected?: boolean
|
||||
actModeAwsBedrockCustomModelBaseId?: BedrockModelId
|
||||
actModeOpenRouterModelId?: string
|
||||
actModeOpenRouterModelInfo?: ModelInfo
|
||||
actModeOpenAiModelId?: string
|
||||
actModeOpenAiModelInfo?: OpenAiCompatibleModelInfo
|
||||
actModeOllamaModelId?: string
|
||||
actModeLmStudioModelId?: string
|
||||
actModeLiteLlmModelId?: string
|
||||
actModeLiteLlmModelInfo?: LiteLLMModelInfo
|
||||
actModeRequestyModelId?: string
|
||||
actModeRequestyModelInfo?: ModelInfo
|
||||
actModeTogetherModelId?: string
|
||||
actModeFireworksModelId?: string
|
||||
actModeSapAiCoreModelId?: string
|
||||
actModeGroqModelId?: string
|
||||
actModeGroqModelInfo?: ModelInfo
|
||||
actModeHuggingFaceModelId?: string
|
||||
actModeHuggingFaceModelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
export type ApiConfiguration = ApiHandlerOptions & {
|
||||
planModeApiProvider?: ApiProvider
|
||||
actModeApiProvider?: ApiProvider
|
||||
apiProvider?: ApiProvider
|
||||
favoritedModelIds?: string[]
|
||||
}
|
||||
|
||||
@@ -1106,49 +1074,6 @@ export const deepSeekModels = {
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Hugging Face Inference Providers
|
||||
// https://huggingface.co/docs/inference-providers/en/index
|
||||
export type HuggingFaceModelId = keyof typeof huggingFaceModels
|
||||
export const huggingFaceDefaultModelId: HuggingFaceModelId = "moonshotai/Kimi-K2-Instruct"
|
||||
export const huggingFaceModels = {
|
||||
"moonshotai/Kimi-K2-Instruct": {
|
||||
maxTokens: 131_072,
|
||||
contextWindow: 131_072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Advanced reasoning model with superior performance across coding, math, and general capabilities.",
|
||||
},
|
||||
"deepseek-ai/DeepSeek-V3-0324": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 64_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Advanced reasoning model with superior performance across coding, math, and general capabilities.",
|
||||
},
|
||||
"deepseek-ai/DeepSeek-R1": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 64_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "DeepSeek's reasoning model with step-by-step thinking capabilities.",
|
||||
},
|
||||
"meta-llama/Llama-3.1-8B-Instruct": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Efficient 8B parameter Llama model for general-purpose tasks.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Qwen
|
||||
// https://bailian.console.aliyun.com/
|
||||
export type MainlandQwenModelId = keyof typeof mainlandQwenModels
|
||||
|
||||
@@ -224,8 +224,6 @@ function convertApiProviderToProto(provider: string | undefined): ProtoApiProvid
|
||||
return ProtoApiProvider.LITELLM
|
||||
case "moonshot":
|
||||
return ProtoApiProvider.MOONSHOT
|
||||
case "huggingface":
|
||||
return ProtoApiProvider.HUGGINGFACE
|
||||
case "nebius":
|
||||
return ProtoApiProvider.NEBIUS
|
||||
case "fireworks":
|
||||
@@ -290,8 +288,6 @@ function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
|
||||
return "litellm"
|
||||
case ProtoApiProvider.MOONSHOT:
|
||||
return "moonshot"
|
||||
case ProtoApiProvider.HUGGINGFACE:
|
||||
return "huggingface"
|
||||
case ProtoApiProvider.NEBIUS:
|
||||
return "nebius"
|
||||
case ProtoApiProvider.FIREWORKS:
|
||||
@@ -318,16 +314,20 @@ function convertProtoToApiProvider(provider: ProtoApiProvider): ApiProvider {
|
||||
// Converts application ApiConfiguration to proto ApiConfiguration
|
||||
export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoApiConfiguration {
|
||||
return {
|
||||
// Global configuration fields
|
||||
apiModelId: config.apiModelId,
|
||||
apiKey: config.apiKey,
|
||||
clineAccountId: config.clineAccountId,
|
||||
taskId: config.taskId,
|
||||
liteLlmBaseUrl: config.liteLlmBaseUrl,
|
||||
liteLlmModelId: config.liteLlmModelId,
|
||||
liteLlmApiKey: config.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: config.liteLlmUsePromptCache,
|
||||
openAiHeaders: config.openAiHeaders || {},
|
||||
liteLlmModelInfo: convertLiteLLMModelInfoToProto(config.liteLlmModelInfo),
|
||||
anthropicBaseUrl: config.anthropicBaseUrl,
|
||||
openRouterApiKey: config.openRouterApiKey,
|
||||
openRouterModelId: config.openRouterModelId,
|
||||
openRouterModelInfo: convertModelInfoToProtoOpenRouter(config.openRouterModelInfo),
|
||||
openRouterProviderSorting: config.openRouterProviderSorting,
|
||||
awsAccessKey: config.awsAccessKey,
|
||||
awsSecretKey: config.awsSecretKey,
|
||||
@@ -340,115 +340,80 @@ export function convertApiConfigurationToProto(config: ApiConfiguration): ProtoA
|
||||
awsProfile: config.awsProfile,
|
||||
awsBedrockApiKey: config.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: config.awsBedrockEndpoint,
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId as string | undefined,
|
||||
vertexProjectId: config.vertexProjectId,
|
||||
vertexRegion: config.vertexRegion,
|
||||
openAiBaseUrl: config.openAiBaseUrl,
|
||||
openAiApiKey: config.openAiApiKey,
|
||||
openAiModelId: config.openAiModelId,
|
||||
openAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.openAiModelInfo),
|
||||
ollamaModelId: config.ollamaModelId,
|
||||
ollamaBaseUrl: config.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId: config.lmStudioModelId,
|
||||
lmStudioBaseUrl: config.lmStudioBaseUrl,
|
||||
geminiApiKey: config.geminiApiKey,
|
||||
geminiBaseUrl: config.geminiBaseUrl,
|
||||
openAiNativeApiKey: config.openAiNativeApiKey,
|
||||
deepSeekApiKey: config.deepSeekApiKey,
|
||||
requestyApiKey: config.requestyApiKey,
|
||||
requestyModelId: config.requestyModelId,
|
||||
requestyModelInfo: convertModelInfoToProtoOpenRouter(config.requestyModelInfo),
|
||||
togetherApiKey: config.togetherApiKey,
|
||||
togetherModelId: config.togetherModelId,
|
||||
fireworksApiKey: config.fireworksApiKey,
|
||||
fireworksModelId: config.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: config.fireworksModelMaxTokens,
|
||||
qwenApiKey: config.qwenApiKey,
|
||||
doubaoApiKey: config.doubaoApiKey,
|
||||
mistralApiKey: config.mistralApiKey,
|
||||
azureApiVersion: config.azureApiVersion,
|
||||
vsCodeLmModelSelector: config.vsCodeLmModelSelector,
|
||||
qwenApiLine: config.qwenApiLine,
|
||||
moonshotApiLine: config.moonshotApiLine,
|
||||
moonshotApiKey: config.moonshotApiKey,
|
||||
huggingFaceApiKey: config.huggingFaceApiKey,
|
||||
nebiusApiKey: config.nebiusApiKey,
|
||||
asksageApiUrl: config.asksageApiUrl,
|
||||
asksageApiKey: config.asksageApiKey,
|
||||
xaiApiKey: config.xaiApiKey,
|
||||
thinkingBudgetTokens: config.thinkingBudgetTokens,
|
||||
reasoningEffort: config.reasoningEffort,
|
||||
sambanovaApiKey: config.sambanovaApiKey,
|
||||
cerebrasApiKey: config.cerebrasApiKey,
|
||||
groqApiKey: config.groqApiKey,
|
||||
groqModelId: config.groqModelId,
|
||||
groqModelInfo: convertModelInfoToProtoOpenRouter(config.groqModelInfo),
|
||||
requestTimeoutMs: config.requestTimeoutMs,
|
||||
apiProvider: config.apiProvider ? convertApiProviderToProto(config.apiProvider) : undefined,
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
sapAiCoreClientId: config.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: config.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: config.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
|
||||
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: config.planModeApiProvider ? convertApiProviderToProto(config.planModeApiProvider) : undefined,
|
||||
planModeApiModelId: config.planModeApiModelId,
|
||||
planModeThinkingBudgetTokens: config.planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort: config.planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector: config.planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected: config.planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId: config.planModeAwsBedrockCustomModelBaseId as string | undefined,
|
||||
planModeOpenRouterModelId: config.planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo: convertModelInfoToProtoOpenRouter(config.planModeOpenRouterModelInfo),
|
||||
planModeOpenAiModelId: config.planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.planModeOpenAiModelInfo),
|
||||
planModeOllamaModelId: config.planModeOllamaModelId,
|
||||
planModeLmStudioModelId: config.planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId: config.planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo: convertLiteLLMModelInfoToProto(config.planModeLiteLlmModelInfo),
|
||||
planModeRequestyModelId: config.planModeRequestyModelId,
|
||||
planModeRequestyModelInfo: convertModelInfoToProtoOpenRouter(config.planModeRequestyModelInfo),
|
||||
planModeTogetherModelId: config.planModeTogetherModelId,
|
||||
planModeFireworksModelId: config.planModeFireworksModelId,
|
||||
planModeGroqModelId: config.planModeGroqModelId,
|
||||
planModeGroqModelInfo: convertModelInfoToProtoOpenRouter(config.planModeGroqModelInfo),
|
||||
planModeHuggingFaceModelId: config.planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.planModeHuggingFaceModelInfo),
|
||||
planModeSapAiCoreModelId: config.planModeSapAiCoreModelId,
|
||||
|
||||
// Act mode configurations
|
||||
actModeApiProvider: config.actModeApiProvider ? convertApiProviderToProto(config.actModeApiProvider) : undefined,
|
||||
actModeApiModelId: config.actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: config.actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort: config.actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector: config.actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected: config.actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId: config.actModeAwsBedrockCustomModelBaseId as string | undefined,
|
||||
actModeOpenRouterModelId: config.actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo: convertModelInfoToProtoOpenRouter(config.actModeOpenRouterModelInfo),
|
||||
actModeOpenAiModelId: config.actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo: convertOpenAiCompatibleModelInfoToProto(config.actModeOpenAiModelInfo),
|
||||
actModeOllamaModelId: config.actModeOllamaModelId,
|
||||
actModeLmStudioModelId: config.actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId: config.actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo: convertLiteLLMModelInfoToProto(config.actModeLiteLlmModelInfo),
|
||||
actModeRequestyModelId: config.actModeRequestyModelId,
|
||||
actModeRequestyModelInfo: convertModelInfoToProtoOpenRouter(config.actModeRequestyModelInfo),
|
||||
actModeTogetherModelId: config.actModeTogetherModelId,
|
||||
actModeFireworksModelId: config.actModeFireworksModelId,
|
||||
actModeGroqModelId: config.actModeGroqModelId,
|
||||
actModeGroqModelInfo: convertModelInfoToProtoOpenRouter(config.actModeGroqModelInfo),
|
||||
actModeHuggingFaceModelId: config.actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo: convertModelInfoToProtoOpenRouter(config.actModeHuggingFaceModelInfo),
|
||||
actModeSapAiCoreModelId: config.actModeSapAiCoreModelId,
|
||||
|
||||
// Favorited model IDs
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts proto ApiConfiguration to application ApiConfiguration
|
||||
export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
|
||||
return {
|
||||
// Global configuration fields
|
||||
apiModelId: protoConfig.apiModelId,
|
||||
apiKey: protoConfig.apiKey,
|
||||
clineAccountId: protoConfig.clineAccountId,
|
||||
taskId: protoConfig.taskId,
|
||||
liteLlmBaseUrl: protoConfig.liteLlmBaseUrl,
|
||||
liteLlmModelId: protoConfig.liteLlmModelId,
|
||||
liteLlmApiKey: protoConfig.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: protoConfig.liteLlmUsePromptCache,
|
||||
openAiHeaders: Object.keys(protoConfig.openAiHeaders || {}).length > 0 ? protoConfig.openAiHeaders : undefined,
|
||||
openAiHeaders: Object.keys(protoConfig.openAiHeaders).length > 0 ? protoConfig.openAiHeaders : undefined,
|
||||
liteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.liteLlmModelInfo),
|
||||
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
|
||||
openRouterApiKey: protoConfig.openRouterApiKey,
|
||||
openRouterModelId: protoConfig.openRouterModelId,
|
||||
openRouterModelInfo: convertProtoToModelInfo(protoConfig.openRouterModelInfo),
|
||||
openRouterProviderSorting: protoConfig.openRouterProviderSorting,
|
||||
awsAccessKey: protoConfig.awsAccessKey,
|
||||
awsSecretKey: protoConfig.awsSecretKey,
|
||||
@@ -461,103 +426,59 @@ export function convertProtoToApiConfiguration(protoConfig: ProtoApiConfiguratio
|
||||
awsProfile: protoConfig.awsProfile,
|
||||
awsBedrockApiKey: protoConfig.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
vertexProjectId: protoConfig.vertexProjectId,
|
||||
vertexRegion: protoConfig.vertexRegion,
|
||||
openAiBaseUrl: protoConfig.openAiBaseUrl,
|
||||
openAiApiKey: protoConfig.openAiApiKey,
|
||||
openAiModelId: protoConfig.openAiModelId,
|
||||
openAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.openAiModelInfo),
|
||||
ollamaModelId: protoConfig.ollamaModelId,
|
||||
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
|
||||
lmStudioModelId: protoConfig.lmStudioModelId,
|
||||
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
|
||||
geminiApiKey: protoConfig.geminiApiKey,
|
||||
geminiBaseUrl: protoConfig.geminiBaseUrl,
|
||||
openAiNativeApiKey: protoConfig.openAiNativeApiKey,
|
||||
deepSeekApiKey: protoConfig.deepSeekApiKey,
|
||||
requestyApiKey: protoConfig.requestyApiKey,
|
||||
requestyModelId: protoConfig.requestyModelId,
|
||||
requestyModelInfo: convertProtoToModelInfo(protoConfig.requestyModelInfo),
|
||||
togetherApiKey: protoConfig.togetherApiKey,
|
||||
togetherModelId: protoConfig.togetherModelId,
|
||||
fireworksApiKey: protoConfig.fireworksApiKey,
|
||||
fireworksModelId: protoConfig.fireworksModelId,
|
||||
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens,
|
||||
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens,
|
||||
qwenApiKey: protoConfig.qwenApiKey,
|
||||
doubaoApiKey: protoConfig.doubaoApiKey,
|
||||
mistralApiKey: protoConfig.mistralApiKey,
|
||||
azureApiVersion: protoConfig.azureApiVersion,
|
||||
vsCodeLmModelSelector: protoConfig.vsCodeLmModelSelector,
|
||||
qwenApiLine: protoConfig.qwenApiLine,
|
||||
moonshotApiLine: protoConfig.moonshotApiLine,
|
||||
moonshotApiKey: protoConfig.moonshotApiKey,
|
||||
huggingFaceApiKey: protoConfig.huggingFaceApiKey,
|
||||
nebiusApiKey: protoConfig.nebiusApiKey,
|
||||
asksageApiUrl: protoConfig.asksageApiUrl,
|
||||
asksageApiKey: protoConfig.asksageApiKey,
|
||||
xaiApiKey: protoConfig.xaiApiKey,
|
||||
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens,
|
||||
reasoningEffort: protoConfig.reasoningEffort,
|
||||
sambanovaApiKey: protoConfig.sambanovaApiKey,
|
||||
cerebrasApiKey: protoConfig.cerebrasApiKey,
|
||||
groqApiKey: protoConfig.groqApiKey,
|
||||
groqModelId: protoConfig.groqModelId,
|
||||
groqModelInfo: convertProtoToModelInfo(protoConfig.groqModelInfo),
|
||||
requestTimeoutMs: protoConfig.requestTimeoutMs,
|
||||
apiProvider: protoConfig.apiProvider !== undefined ? convertProtoToApiProvider(protoConfig.apiProvider) : undefined,
|
||||
favoritedModelIds: protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
|
||||
sapAiCoreClientId: protoConfig.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: protoConfig.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
|
||||
|
||||
// Plan mode configurations
|
||||
planModeApiProvider:
|
||||
protoConfig.planModeApiProvider !== undefined
|
||||
? convertProtoToApiProvider(protoConfig.planModeApiProvider)
|
||||
: undefined,
|
||||
planModeApiModelId: protoConfig.planModeApiModelId,
|
||||
planModeThinkingBudgetTokens: protoConfig.planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort: protoConfig.planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector: protoConfig.planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected: protoConfig.planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId: protoConfig.planModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
planModeOpenRouterModelId: protoConfig.planModeOpenRouterModelId,
|
||||
planModeOpenRouterModelInfo: convertProtoToModelInfo(protoConfig.planModeOpenRouterModelInfo),
|
||||
planModeOpenAiModelId: protoConfig.planModeOpenAiModelId,
|
||||
planModeOpenAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.planModeOpenAiModelInfo),
|
||||
planModeOllamaModelId: protoConfig.planModeOllamaModelId,
|
||||
planModeLmStudioModelId: protoConfig.planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId: protoConfig.planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.planModeLiteLlmModelInfo),
|
||||
planModeRequestyModelId: protoConfig.planModeRequestyModelId,
|
||||
planModeRequestyModelInfo: convertProtoToModelInfo(protoConfig.planModeRequestyModelInfo),
|
||||
planModeTogetherModelId: protoConfig.planModeTogetherModelId,
|
||||
planModeFireworksModelId: protoConfig.planModeFireworksModelId,
|
||||
planModeGroqModelId: protoConfig.planModeGroqModelId,
|
||||
planModeGroqModelInfo: convertProtoToModelInfo(protoConfig.planModeGroqModelInfo),
|
||||
planModeHuggingFaceModelId: protoConfig.planModeHuggingFaceModelId,
|
||||
planModeHuggingFaceModelInfo: convertProtoToModelInfo(protoConfig.planModeHuggingFaceModelInfo),
|
||||
planModeSapAiCoreModelId: protoConfig.planModeSapAiCoreModelId,
|
||||
|
||||
// Act mode configurations
|
||||
actModeApiProvider:
|
||||
protoConfig.actModeApiProvider !== undefined ? convertProtoToApiProvider(protoConfig.actModeApiProvider) : undefined,
|
||||
actModeApiModelId: protoConfig.actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: protoConfig.actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort: protoConfig.actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector: protoConfig.actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected: protoConfig.actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId: protoConfig.actModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
actModeOpenRouterModelId: protoConfig.actModeOpenRouterModelId,
|
||||
actModeOpenRouterModelInfo: convertProtoToModelInfo(protoConfig.actModeOpenRouterModelInfo),
|
||||
actModeOpenAiModelId: protoConfig.actModeOpenAiModelId,
|
||||
actModeOpenAiModelInfo: convertProtoToOpenAiCompatibleModelInfo(protoConfig.actModeOpenAiModelInfo),
|
||||
actModeOllamaModelId: protoConfig.actModeOllamaModelId,
|
||||
actModeLmStudioModelId: protoConfig.actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId: protoConfig.actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo: convertProtoToLiteLLMModelInfo(protoConfig.actModeLiteLlmModelInfo),
|
||||
actModeRequestyModelId: protoConfig.actModeRequestyModelId,
|
||||
actModeRequestyModelInfo: convertProtoToModelInfo(protoConfig.actModeRequestyModelInfo),
|
||||
actModeTogetherModelId: protoConfig.actModeTogetherModelId,
|
||||
actModeFireworksModelId: protoConfig.actModeFireworksModelId,
|
||||
actModeGroqModelId: protoConfig.actModeGroqModelId,
|
||||
actModeGroqModelInfo: convertProtoToModelInfo(protoConfig.actModeGroqModelInfo),
|
||||
actModeHuggingFaceModelId: protoConfig.actModeHuggingFaceModelId,
|
||||
actModeHuggingFaceModelInfo: convertProtoToModelInfo(protoConfig.actModeHuggingFaceModelInfo),
|
||||
actModeSapAiCoreModelId: protoConfig.actModeSapAiCoreModelId,
|
||||
|
||||
// Favorited model IDs
|
||||
favoritedModelIds:
|
||||
protoConfig.favoritedModelIds && protoConfig.favoritedModelIds.length > 0 ? protoConfig.favoritedModelIds : undefined,
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,17 +11,49 @@ import {
|
||||
*/
|
||||
export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfiguration): ProtoApiConfiguration {
|
||||
return ProtoApiConfiguration.create({
|
||||
// Global configuration fields (not mode-specific)
|
||||
// Core API fields
|
||||
apiProvider: config.apiProvider,
|
||||
apiModelId: config.apiModelId,
|
||||
apiKey: config.apiKey,
|
||||
|
||||
// Provider-specific API keys
|
||||
clineAccountId: config.clineAccountId,
|
||||
taskId: config.taskId,
|
||||
liteLlmBaseUrl: config.liteLlmBaseUrl,
|
||||
liteLlmApiKey: config.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: config.liteLlmUsePromptCache,
|
||||
openaiHeaders: config.openAiHeaders ? JSON.stringify(config.openAiHeaders) : undefined,
|
||||
anthropicBaseUrl: config.anthropicBaseUrl,
|
||||
openrouterApiKey: config.openRouterApiKey,
|
||||
openrouterProviderSorting: config.openRouterProviderSorting,
|
||||
anthropicBaseUrl: config.anthropicBaseUrl,
|
||||
openaiApiKey: config.openAiApiKey,
|
||||
openaiNativeApiKey: config.openAiNativeApiKey,
|
||||
geminiApiKey: config.geminiApiKey,
|
||||
deepseekApiKey: config.deepSeekApiKey,
|
||||
requestyApiKey: config.requestyApiKey,
|
||||
togetherApiKey: config.togetherApiKey,
|
||||
fireworksApiKey: config.fireworksApiKey,
|
||||
qwenApiKey: config.qwenApiKey,
|
||||
doubaoApiKey: config.doubaoApiKey,
|
||||
mistralApiKey: config.mistralApiKey,
|
||||
moonshotApiKey: config.moonshotApiKey,
|
||||
nebiusApiKey: config.nebiusApiKey,
|
||||
asksageApiKey: config.asksageApiKey,
|
||||
xaiApiKey: config.xaiApiKey,
|
||||
sambanovaApiKey: config.sambanovaApiKey,
|
||||
cerebrasApiKey: config.cerebrasApiKey,
|
||||
|
||||
// Model IDs - each provider has its own field
|
||||
openrouterModelId: config.openRouterModelId,
|
||||
openaiModelId: config.openAiModelId,
|
||||
anthropicModelId: config.apiModelId,
|
||||
bedrockModelId: config.apiModelId,
|
||||
vertexModelId: config.apiModelId,
|
||||
geminiModelId: config.apiModelId,
|
||||
ollamaModelId: config.ollamaModelId,
|
||||
lmStudioModelId: config.lmStudioModelId,
|
||||
litellmModelId: config.liteLlmModelId,
|
||||
requestyModelId: config.requestyModelId,
|
||||
togetherModelId: config.togetherModelId,
|
||||
fireworksModelId: config.fireworksModelId,
|
||||
|
||||
// AWS Bedrock fields
|
||||
awsBedrockCustomSelected: config.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: config.awsBedrockCustomModelBaseId,
|
||||
awsAccessKey: config.awsAccessKey,
|
||||
awsSecretKey: config.awsSecretKey,
|
||||
awsSessionToken: config.awsSessionToken,
|
||||
@@ -33,101 +65,68 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
|
||||
awsProfile: config.awsProfile,
|
||||
awsBedrockApiKey: config.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: config.awsBedrockEndpoint,
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
|
||||
// Vertex AI fields
|
||||
vertexProjectId: config.vertexProjectId,
|
||||
vertexRegion: config.vertexRegion,
|
||||
|
||||
// Base URLs and endpoints
|
||||
openaiBaseUrl: config.openAiBaseUrl,
|
||||
openaiApiKey: config.openAiApiKey,
|
||||
ollamaBaseUrl: config.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
|
||||
lmStudioBaseUrl: config.lmStudioBaseUrl,
|
||||
geminiApiKey: config.geminiApiKey,
|
||||
geminiBaseUrl: config.geminiBaseUrl,
|
||||
openaiNativeApiKey: config.openAiNativeApiKey,
|
||||
deepSeekApiKey: config.deepSeekApiKey,
|
||||
requestyApiKey: config.requestyApiKey,
|
||||
togetherApiKey: config.togetherApiKey,
|
||||
fireworksApiKey: config.fireworksApiKey,
|
||||
litellmBaseUrl: config.liteLlmBaseUrl,
|
||||
asksageApiUrl: config.asksageApiUrl,
|
||||
|
||||
// LiteLLM specific fields
|
||||
litellmApiKey: config.liteLlmApiKey,
|
||||
litellmUsePromptCache: config.liteLlmUsePromptCache,
|
||||
|
||||
// Model configuration
|
||||
thinkingBudgetTokens: config.thinkingBudgetTokens ? Number(config.thinkingBudgetTokens) : undefined,
|
||||
reasoningEffort: config.reasoningEffort,
|
||||
requestTimeoutMs: config.requestTimeoutMs ? Number(config.requestTimeoutMs) : undefined,
|
||||
|
||||
// Fireworks specific
|
||||
fireworksModelMaxCompletionTokens: config.fireworksModelMaxCompletionTokens
|
||||
? Number(config.fireworksModelMaxCompletionTokens)
|
||||
: undefined,
|
||||
fireworksModelMaxTokens: config.fireworksModelMaxTokens ? Number(config.fireworksModelMaxTokens) : undefined,
|
||||
qwenApiKey: config.qwenApiKey,
|
||||
doubaoApiKey: config.doubaoApiKey,
|
||||
mistralApiKey: config.mistralApiKey,
|
||||
moonshotApiKey: config.moonshotApiKey,
|
||||
|
||||
// Azure specific
|
||||
azureApiVersion: config.azureApiVersion,
|
||||
|
||||
// Ollama specific
|
||||
ollamaApiOptionsCtxNum: config.ollamaApiOptionsCtxNum,
|
||||
|
||||
// Qwen specific
|
||||
qwenApiLine: config.qwenApiLine,
|
||||
nebiusApiKey: config.nebiusApiKey,
|
||||
asksageApiUrl: config.asksageApiUrl,
|
||||
asksageApiKey: config.asksageApiKey,
|
||||
xaiApiKey: config.xaiApiKey,
|
||||
sambanovaApiKey: config.sambanovaApiKey,
|
||||
cerebrasApiKey: config.cerebrasApiKey,
|
||||
requestTimeoutMs: config.requestTimeoutMs ? Number(config.requestTimeoutMs) : undefined,
|
||||
|
||||
// Moonshot specific
|
||||
moonshotApiLine: config.moonshotApiLine,
|
||||
|
||||
// OpenRouter specific
|
||||
openrouterProviderSorting: config.openRouterProviderSorting,
|
||||
|
||||
// SAP AI Core specific
|
||||
sapAiCoreClientId: config.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: config.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: config.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: config.sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl: config.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: config.sapAiResourceGroup,
|
||||
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: config.planModeApiProvider,
|
||||
planModeApiModelId: config.planModeApiModelId,
|
||||
planModeThinkingBudgetTokens: config.planModeThinkingBudgetTokens
|
||||
? Number(config.planModeThinkingBudgetTokens)
|
||||
: undefined,
|
||||
planModeReasoningEffort: config.planModeReasoningEffort,
|
||||
planModeVscodeLmModelSelector: config.planModeVsCodeLmModelSelector
|
||||
? JSON.stringify(config.planModeVsCodeLmModelSelector)
|
||||
: undefined,
|
||||
planModeAwsBedrockCustomSelected: config.planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId: config.planModeAwsBedrockCustomModelBaseId,
|
||||
planModeOpenrouterModelId: config.planModeOpenRouterModelId,
|
||||
planModeOpenrouterModelInfo: config.planModeOpenRouterModelInfo
|
||||
? JSON.stringify(config.planModeOpenRouterModelInfo)
|
||||
: undefined,
|
||||
planModeOpenaiModelId: config.planModeOpenAiModelId,
|
||||
planModeOpenaiModelInfo: config.planModeOpenAiModelInfo ? JSON.stringify(config.planModeOpenAiModelInfo) : undefined,
|
||||
planModeOllamaModelId: config.planModeOllamaModelId,
|
||||
planModeLmStudioModelId: config.planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId: config.planModeLiteLlmModelId,
|
||||
planModeLiteLlmModelInfo: config.planModeLiteLlmModelInfo ? JSON.stringify(config.planModeLiteLlmModelInfo) : undefined,
|
||||
planModeRequestyModelId: config.planModeRequestyModelId,
|
||||
planModeRequestyModelInfo: config.planModeRequestyModelInfo
|
||||
? JSON.stringify(config.planModeRequestyModelInfo)
|
||||
: undefined,
|
||||
planModeTogetherModelId: config.planModeTogetherModelId,
|
||||
planModeFireworksModelId: config.planModeFireworksModelId,
|
||||
planModeSapAiCoreModelId: config.planModeSapAiCoreModelId,
|
||||
// Complex objects stored as JSON strings
|
||||
vscodeLmModelSelector: config.vsCodeLmModelSelector ? JSON.stringify(config.vsCodeLmModelSelector) : undefined,
|
||||
openrouterModelInfo: config.openRouterModelInfo ? JSON.stringify(config.openRouterModelInfo) : undefined,
|
||||
openaiModelInfo: config.openAiModelInfo ? JSON.stringify(config.openAiModelInfo) : undefined,
|
||||
requestyModelInfo: config.requestyModelInfo ? JSON.stringify(config.requestyModelInfo) : undefined,
|
||||
litellmModelInfo: config.liteLlmModelInfo ? JSON.stringify(config.liteLlmModelInfo) : undefined,
|
||||
openaiHeaders: config.openAiHeaders ? JSON.stringify(config.openAiHeaders) : undefined,
|
||||
|
||||
// Act mode configurations
|
||||
actModeApiProvider: config.actModeApiProvider,
|
||||
actModeApiModelId: config.actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: config.actModeThinkingBudgetTokens ? Number(config.actModeThinkingBudgetTokens) : undefined,
|
||||
actModeReasoningEffort: config.actModeReasoningEffort,
|
||||
actModeVscodeLmModelSelector: config.actModeVsCodeLmModelSelector
|
||||
? JSON.stringify(config.actModeVsCodeLmModelSelector)
|
||||
: undefined,
|
||||
actModeAwsBedrockCustomSelected: config.actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId: config.actModeAwsBedrockCustomModelBaseId,
|
||||
actModeOpenrouterModelId: config.actModeOpenRouterModelId,
|
||||
actModeOpenrouterModelInfo: config.actModeOpenRouterModelInfo
|
||||
? JSON.stringify(config.actModeOpenRouterModelInfo)
|
||||
: undefined,
|
||||
actModeOpenaiModelId: config.actModeOpenAiModelId,
|
||||
actModeOpenaiModelInfo: config.actModeOpenAiModelInfo ? JSON.stringify(config.actModeOpenAiModelInfo) : undefined,
|
||||
actModeOllamaModelId: config.actModeOllamaModelId,
|
||||
actModeLmStudioModelId: config.actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId: config.actModeLiteLlmModelId,
|
||||
actModeLiteLlmModelInfo: config.actModeLiteLlmModelInfo ? JSON.stringify(config.actModeLiteLlmModelInfo) : undefined,
|
||||
actModeRequestyModelId: config.actModeRequestyModelId,
|
||||
actModeRequestyModelInfo: config.actModeRequestyModelInfo ? JSON.stringify(config.actModeRequestyModelInfo) : undefined,
|
||||
actModeTogetherModelId: config.actModeTogetherModelId,
|
||||
actModeFireworksModelId: config.actModeFireworksModelId,
|
||||
actModeSapAiCoreModelId: config.actModeSapAiCoreModelId,
|
||||
// Claude Code specific
|
||||
claudeCodePath: config.claudeCodePath,
|
||||
|
||||
// Favorited model IDs
|
||||
// Arrays
|
||||
favoritedModelIds: config.favoritedModelIds || [],
|
||||
})
|
||||
}
|
||||
@@ -138,16 +137,45 @@ export function convertApiConfigurationToProtoApiConfiguration(config: ApiConfig
|
||||
export function convertProtoApiConfigurationToApiConfiguration(protoConfig: ProtoApiConfiguration): ApiConfiguration {
|
||||
// eslint-disable-next-line eslint-rules/no-protobuf-object-literals
|
||||
const config: ApiConfiguration = {
|
||||
// Global configuration fields (not mode-specific)
|
||||
// Core API fields
|
||||
apiProvider: protoConfig.apiProvider as ApiProvider,
|
||||
apiModelId: protoConfig.apiModelId,
|
||||
apiKey: protoConfig.apiKey,
|
||||
|
||||
// Provider-specific API keys
|
||||
clineAccountId: protoConfig.clineAccountId,
|
||||
taskId: protoConfig.taskId,
|
||||
liteLlmBaseUrl: protoConfig.liteLlmBaseUrl,
|
||||
liteLlmApiKey: protoConfig.liteLlmApiKey,
|
||||
liteLlmUsePromptCache: protoConfig.liteLlmUsePromptCache,
|
||||
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
|
||||
openRouterApiKey: protoConfig.openrouterApiKey,
|
||||
openRouterProviderSorting: protoConfig.openrouterProviderSorting,
|
||||
anthropicBaseUrl: protoConfig.anthropicBaseUrl,
|
||||
openAiApiKey: protoConfig.openaiApiKey,
|
||||
openAiNativeApiKey: protoConfig.openaiNativeApiKey,
|
||||
geminiApiKey: protoConfig.geminiApiKey,
|
||||
deepSeekApiKey: protoConfig.deepseekApiKey,
|
||||
requestyApiKey: protoConfig.requestyApiKey,
|
||||
togetherApiKey: protoConfig.togetherApiKey,
|
||||
fireworksApiKey: protoConfig.fireworksApiKey,
|
||||
qwenApiKey: protoConfig.qwenApiKey,
|
||||
doubaoApiKey: protoConfig.doubaoApiKey,
|
||||
mistralApiKey: protoConfig.mistralApiKey,
|
||||
moonshotApiKey: protoConfig.moonshotApiKey,
|
||||
nebiusApiKey: protoConfig.nebiusApiKey,
|
||||
asksageApiKey: protoConfig.asksageApiKey,
|
||||
xaiApiKey: protoConfig.xaiApiKey,
|
||||
sambanovaApiKey: protoConfig.sambanovaApiKey,
|
||||
cerebrasApiKey: protoConfig.cerebrasApiKey,
|
||||
|
||||
// Model IDs
|
||||
openRouterModelId: protoConfig.openrouterModelId,
|
||||
openAiModelId: protoConfig.openaiModelId,
|
||||
ollamaModelId: protoConfig.ollamaModelId,
|
||||
lmStudioModelId: protoConfig.lmStudioModelId,
|
||||
liteLlmModelId: protoConfig.litellmModelId,
|
||||
requestyModelId: protoConfig.requestyModelId,
|
||||
togetherModelId: protoConfig.togetherModelId,
|
||||
fireworksModelId: protoConfig.fireworksModelId,
|
||||
|
||||
// AWS Bedrock fields
|
||||
awsBedrockCustomSelected: protoConfig.awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId: protoConfig.awsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
awsAccessKey: protoConfig.awsAccessKey,
|
||||
awsSecretKey: protoConfig.awsSecretKey,
|
||||
awsSessionToken: protoConfig.awsSessionToken,
|
||||
@@ -159,121 +187,83 @@ export function convertProtoApiConfigurationToApiConfiguration(protoConfig: Prot
|
||||
awsAuthentication: protoConfig.awsAuthentication,
|
||||
awsBedrockApiKey: protoConfig.awsBedrockApiKey,
|
||||
awsBedrockEndpoint: protoConfig.awsBedrockEndpoint,
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
|
||||
// Vertex AI fields
|
||||
vertexProjectId: protoConfig.vertexProjectId,
|
||||
vertexRegion: protoConfig.vertexRegion,
|
||||
|
||||
// Base URLs and endpoints
|
||||
openAiBaseUrl: protoConfig.openaiBaseUrl,
|
||||
openAiApiKey: protoConfig.openaiApiKey,
|
||||
ollamaBaseUrl: protoConfig.ollamaBaseUrl,
|
||||
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
|
||||
lmStudioBaseUrl: protoConfig.lmStudioBaseUrl,
|
||||
geminiApiKey: protoConfig.geminiApiKey,
|
||||
geminiBaseUrl: protoConfig.geminiBaseUrl,
|
||||
openAiNativeApiKey: protoConfig.openaiNativeApiKey,
|
||||
deepSeekApiKey: protoConfig.deepSeekApiKey,
|
||||
requestyApiKey: protoConfig.requestyApiKey,
|
||||
togetherApiKey: protoConfig.togetherApiKey,
|
||||
fireworksApiKey: protoConfig.fireworksApiKey,
|
||||
liteLlmBaseUrl: protoConfig.litellmBaseUrl,
|
||||
asksageApiUrl: protoConfig.asksageApiUrl,
|
||||
|
||||
// LiteLLM specific fields
|
||||
liteLlmApiKey: protoConfig.litellmApiKey,
|
||||
liteLlmUsePromptCache: protoConfig.litellmUsePromptCache,
|
||||
|
||||
// Model configuration
|
||||
thinkingBudgetTokens: protoConfig.thinkingBudgetTokens ? Number(protoConfig.thinkingBudgetTokens) : undefined,
|
||||
reasoningEffort: protoConfig.reasoningEffort,
|
||||
requestTimeoutMs: protoConfig.requestTimeoutMs ? Number(protoConfig.requestTimeoutMs) : undefined,
|
||||
|
||||
// Fireworks specific
|
||||
fireworksModelMaxCompletionTokens: protoConfig.fireworksModelMaxCompletionTokens
|
||||
? Number(protoConfig.fireworksModelMaxCompletionTokens)
|
||||
: undefined,
|
||||
fireworksModelMaxTokens: protoConfig.fireworksModelMaxTokens ? Number(protoConfig.fireworksModelMaxTokens) : undefined,
|
||||
qwenApiKey: protoConfig.qwenApiKey,
|
||||
doubaoApiKey: protoConfig.doubaoApiKey,
|
||||
mistralApiKey: protoConfig.mistralApiKey,
|
||||
moonshotApiKey: protoConfig.moonshotApiKey,
|
||||
|
||||
// Azure specific
|
||||
azureApiVersion: protoConfig.azureApiVersion,
|
||||
|
||||
// Ollama specific
|
||||
ollamaApiOptionsCtxNum: protoConfig.ollamaApiOptionsCtxNum,
|
||||
|
||||
// Qwen specific
|
||||
qwenApiLine: protoConfig.qwenApiLine,
|
||||
nebiusApiKey: protoConfig.nebiusApiKey,
|
||||
asksageApiUrl: protoConfig.asksageApiUrl,
|
||||
asksageApiKey: protoConfig.asksageApiKey,
|
||||
xaiApiKey: protoConfig.xaiApiKey,
|
||||
sambanovaApiKey: protoConfig.sambanovaApiKey,
|
||||
cerebrasApiKey: protoConfig.cerebrasApiKey,
|
||||
requestTimeoutMs: protoConfig.requestTimeoutMs ? Number(protoConfig.requestTimeoutMs) : undefined,
|
||||
|
||||
// Moonshot specific
|
||||
moonshotApiLine: protoConfig.moonshotApiLine,
|
||||
|
||||
// OpenRouter specific
|
||||
openRouterProviderSorting: protoConfig.openrouterProviderSorting,
|
||||
|
||||
// SAP AI Core specific
|
||||
sapAiCoreClientId: protoConfig.sapAiCoreClientId,
|
||||
sapAiCoreClientSecret: protoConfig.sapAiCoreClientSecret,
|
||||
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
|
||||
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
|
||||
sapAiCoreBaseUrl: protoConfig.sapAiCoreBaseUrl,
|
||||
sapAiCoreTokenUrl: protoConfig.sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup: protoConfig.sapAiResourceGroup,
|
||||
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: protoConfig.planModeApiProvider as ApiProvider,
|
||||
planModeApiModelId: protoConfig.planModeApiModelId,
|
||||
planModeThinkingBudgetTokens: protoConfig.planModeThinkingBudgetTokens
|
||||
? Number(protoConfig.planModeThinkingBudgetTokens)
|
||||
: undefined,
|
||||
planModeReasoningEffort: protoConfig.planModeReasoningEffort,
|
||||
planModeAwsBedrockCustomSelected: protoConfig.planModeAwsBedrockCustomSelected,
|
||||
planModeAwsBedrockCustomModelBaseId: protoConfig.planModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
planModeOpenRouterModelId: protoConfig.planModeOpenrouterModelId,
|
||||
planModeOpenAiModelId: protoConfig.planModeOpenaiModelId,
|
||||
planModeOllamaModelId: protoConfig.planModeOllamaModelId,
|
||||
planModeLmStudioModelId: protoConfig.planModeLmStudioModelId,
|
||||
planModeLiteLlmModelId: protoConfig.planModeLiteLlmModelId,
|
||||
planModeRequestyModelId: protoConfig.planModeRequestyModelId,
|
||||
planModeTogetherModelId: protoConfig.planModeTogetherModelId,
|
||||
planModeFireworksModelId: protoConfig.planModeFireworksModelId,
|
||||
planModeSapAiCoreModelId: protoConfig.planModeSapAiCoreModelId,
|
||||
// Claude Code specific
|
||||
claudeCodePath: protoConfig.claudeCodePath,
|
||||
|
||||
// Act mode configurations
|
||||
actModeApiProvider: protoConfig.actModeApiProvider as ApiProvider,
|
||||
actModeApiModelId: protoConfig.actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: protoConfig.actModeThinkingBudgetTokens
|
||||
? Number(protoConfig.actModeThinkingBudgetTokens)
|
||||
: undefined,
|
||||
actModeReasoningEffort: protoConfig.actModeReasoningEffort,
|
||||
actModeAwsBedrockCustomSelected: protoConfig.actModeAwsBedrockCustomSelected,
|
||||
actModeAwsBedrockCustomModelBaseId: protoConfig.actModeAwsBedrockCustomModelBaseId as BedrockModelId | undefined,
|
||||
actModeOpenRouterModelId: protoConfig.actModeOpenrouterModelId,
|
||||
actModeOpenAiModelId: protoConfig.actModeOpenaiModelId,
|
||||
actModeOllamaModelId: protoConfig.actModeOllamaModelId,
|
||||
actModeLmStudioModelId: protoConfig.actModeLmStudioModelId,
|
||||
actModeLiteLlmModelId: protoConfig.actModeLiteLlmModelId,
|
||||
actModeRequestyModelId: protoConfig.actModeRequestyModelId,
|
||||
actModeTogetherModelId: protoConfig.actModeTogetherModelId,
|
||||
actModeFireworksModelId: protoConfig.actModeFireworksModelId,
|
||||
actModeSapAiCoreModelId: protoConfig.actModeSapAiCoreModelId,
|
||||
|
||||
// Favorited model IDs
|
||||
// Arrays
|
||||
favoritedModelIds: protoConfig.favoritedModelIds || [],
|
||||
}
|
||||
|
||||
// Handle complex JSON objects
|
||||
try {
|
||||
if (protoConfig.vscodeLmModelSelector) {
|
||||
config.vsCodeLmModelSelector = JSON.parse(protoConfig.vscodeLmModelSelector)
|
||||
}
|
||||
if (protoConfig.openrouterModelInfo) {
|
||||
config.openRouterModelInfo = JSON.parse(protoConfig.openrouterModelInfo)
|
||||
}
|
||||
if (protoConfig.openaiModelInfo) {
|
||||
config.openAiModelInfo = JSON.parse(protoConfig.openaiModelInfo)
|
||||
}
|
||||
if (protoConfig.requestyModelInfo) {
|
||||
config.requestyModelInfo = JSON.parse(protoConfig.requestyModelInfo)
|
||||
}
|
||||
if (protoConfig.litellmModelInfo) {
|
||||
config.liteLlmModelInfo = JSON.parse(protoConfig.litellmModelInfo)
|
||||
}
|
||||
if (protoConfig.openaiHeaders) {
|
||||
config.openAiHeaders = JSON.parse(protoConfig.openaiHeaders)
|
||||
}
|
||||
if (protoConfig.planModeVscodeLmModelSelector) {
|
||||
config.planModeVsCodeLmModelSelector = JSON.parse(protoConfig.planModeVscodeLmModelSelector)
|
||||
}
|
||||
if (protoConfig.planModeOpenrouterModelInfo) {
|
||||
config.planModeOpenRouterModelInfo = JSON.parse(protoConfig.planModeOpenrouterModelInfo)
|
||||
}
|
||||
if (protoConfig.planModeOpenaiModelInfo) {
|
||||
config.planModeOpenAiModelInfo = JSON.parse(protoConfig.planModeOpenaiModelInfo)
|
||||
}
|
||||
if (protoConfig.planModeLiteLlmModelInfo) {
|
||||
config.planModeLiteLlmModelInfo = JSON.parse(protoConfig.planModeLiteLlmModelInfo)
|
||||
}
|
||||
if (protoConfig.planModeRequestyModelInfo) {
|
||||
config.planModeRequestyModelInfo = JSON.parse(protoConfig.planModeRequestyModelInfo)
|
||||
}
|
||||
if (protoConfig.actModeVscodeLmModelSelector) {
|
||||
config.actModeVsCodeLmModelSelector = JSON.parse(protoConfig.actModeVscodeLmModelSelector)
|
||||
}
|
||||
if (protoConfig.actModeOpenrouterModelInfo) {
|
||||
config.actModeOpenRouterModelInfo = JSON.parse(protoConfig.actModeOpenrouterModelInfo)
|
||||
}
|
||||
if (protoConfig.actModeOpenaiModelInfo) {
|
||||
config.actModeOpenAiModelInfo = JSON.parse(protoConfig.actModeOpenaiModelInfo)
|
||||
}
|
||||
if (protoConfig.actModeLiteLlmModelInfo) {
|
||||
config.actModeLiteLlmModelInfo = JSON.parse(protoConfig.actModeLiteLlmModelInfo)
|
||||
}
|
||||
if (protoConfig.actModeRequestyModelInfo) {
|
||||
config.actModeRequestyModelInfo = JSON.parse(protoConfig.actModeRequestyModelInfo)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse complex JSON objects in API configuration:", error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { getHostBridgeProvider } from "@/hosts/host-providers"
|
||||
import { DiffViewProvider } from "@/integrations/editor/DiffViewProvider"
|
||||
|
||||
export class ExternalDiffViewProvider extends DiffViewProvider {
|
||||
private activeDiffEditorId: string | undefined
|
||||
override async openDiffEditor(): Promise<void> {
|
||||
if (!this.absolutePath) {
|
||||
return
|
||||
}
|
||||
const response = await getHostBridgeProvider().diffClient.openDiff({
|
||||
path: this.absolutePath,
|
||||
content: this.originalContent ?? "",
|
||||
})
|
||||
this.activeDiffEditorId = response.diffId
|
||||
}
|
||||
override async replaceText(
|
||||
content: string,
|
||||
rangeToReplace: { startLine: number; endLine: number },
|
||||
_currentLine: number,
|
||||
): Promise<void> {
|
||||
await getHostBridgeProvider().diffClient.replaceText({
|
||||
diffId: this.activeDiffEditorId,
|
||||
content: content,
|
||||
startLine: rangeToReplace.startLine,
|
||||
endLine: rangeToReplace.endLine,
|
||||
})
|
||||
}
|
||||
}
|
||||
+5
@@ -4,6 +4,11 @@ import * as vscode from "vscode"
|
||||
import { URI } from "vscode-uri"
|
||||
import { WebviewProvider } from "@core/webview"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
||||
*/
|
||||
|
||||
export class ExternalWebviewProvider extends WebviewProvider {
|
||||
// This hostname cannot be changed without updating the external webview handler.
|
||||
private RESOURCE_HOSTNAME: string = "internal.resources"
|
||||
@@ -2,18 +2,18 @@ import * as grpc from "@grpc/grpc-js"
|
||||
import { ReflectionService } from "@grpc/reflection"
|
||||
import * as health from "grpc-health-check"
|
||||
import * as hostProviders from "@hosts/host-providers"
|
||||
import { activate } from "@/extension"
|
||||
import { Controller } from "@core/controller"
|
||||
import { activate } from "../extension"
|
||||
import { Controller } from "../core/controller"
|
||||
import { extensionContext, outputChannel, postMessage } from "./vscode-context"
|
||||
import { getPackageDefinition, log } from "./utils"
|
||||
import { GrpcHandler, GrpcStreamingResponseHandler } from "@hosts/external/grpc-types"
|
||||
import { GrpcHandler, GrpcStreamingResponseHandler } from "./grpc-types"
|
||||
import { addProtobusServices } from "@generated/standalone/server-setup"
|
||||
import { StreamingResponseHandler } from "@core/controller/grpc-handler"
|
||||
import { ExternalHostBridgeClientManager } from "@hosts/external/host-bridge-client-manager"
|
||||
import { ExternalWebviewProvider } from "@hosts/external/ExternalWebviewProvider"
|
||||
import { WebviewProviderType } from "@shared/webview/types"
|
||||
import { StreamingResponseHandler } from "@/core/controller/grpc-handler"
|
||||
import { ExternalHostBridgeClientManager } from "./host-bridge-client-manager"
|
||||
import { ExternalWebviewProvider } from "./ExternalWebviewProvider"
|
||||
import { WebviewProviderType } from "@/shared/webview/types"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import { ExternalDiffViewProvider } from "@hosts/external/ExternalDiffviewProvider"
|
||||
import { ExternalDiffViewProvider } from "./ExternalDiffviewProvider"
|
||||
|
||||
export const PROTOBUS_PORT = 26040
|
||||
export const HOSTBRIDGE_PORT = 26041
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user