mirror of
https://github.com/cline/cline.git
synced 2026-09-07 04:44:58 +08:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 154aab81e9 | |||
| c4ef472aeb | |||
| e22c457d19 | |||
| c15287ace0 | |||
| d30f54a89c | |||
| 56b913d951 | |||
| 55a30e0ffa | |||
| a017f3dfd3 | |||
| 41ebe7c9d1 | |||
| 4d11f0d2fa | |||
| 4baa2474eb | |||
| c94e2cf913 | |||
| dbedc6cfaa | |||
| 2ac568e649 | |||
| b13d0e75ea | |||
| 4d395deefd | |||
| 834a5b1df2 | |||
| 3089233298 | |||
| f2b7347a5c | |||
| 04bfef75cf | |||
| 716e8f236b | |||
| f2ddab71f1 | |||
| 0b56a45a65 | |||
| d6ebd2438a | |||
| 4c07c7e5c5 | |||
| ab66a5fd93 | |||
| bd1d6159fc | |||
| 7f2d28716f | |||
| 653727db2a | |||
| d3c2f1878d | |||
| ba92be9401 | |||
| 66eb5a62ba | |||
| 499ee22b3b | |||
| 7abeae5019 | |||
| accf47cb52 | |||
| 556d3e6f79 | |||
| 9e35048db2 | |||
| 9b0f2b82ef | |||
| c2c23054b9 | |||
| 3baaa5c8b4 | |||
| abafcc7290 | |||
| d82aa0add9 | |||
| abe4721a0b | |||
| 60d55b69a8 | |||
| d18e0271d3 | |||
| af71f9da90 | |||
| 2a1c8826aa | |||
| 9a54f2d246 | |||
| d928d58a40 | |||
| 31859b5fda | |||
| 0d5d89e8c7 | |||
| af34451eec | |||
| 027a4f6386 | |||
| 49642882c5 | |||
| 21ed6bc432 | |||
| da2689f885 | |||
| 5049326f02 | |||
| b02ce46a57 | |||
| de974737c8 | |||
| d072156e9a | |||
| 4939309a09 | |||
| 1a07ca7906 | |||
| c1eefbad3f | |||
| 1bfdce9b84 | |||
| b002cdacdb | |||
| cf4005b25e | |||
| 1494d145d5 | |||
| 1ab4b3cc24 | |||
| 535b653228 | |||
| 1335fa5452 | |||
| b2a4395f71 | |||
| ae34a3a8c5 | |||
| 0fb4a6c7e9 | |||
| 855db7d8d8 | |||
| bb375b78ca | |||
| 31af254f0a | |||
| 2745cdd54b | |||
| c94c6fd8f1 | |||
| a87664318e | |||
| ba70718c83 | |||
| cf8dd1c150 | |||
| e714c74ec4 | |||
| a6f8b33895 | |||
| 02abbcf045 | |||
| 8ca2706cca | |||
| e747d211e6 | |||
| 19aa81a1cc | |||
| f5f3654966 | |||
| 344b99988b | |||
| 7d3c39bcdb | |||
| 33c1692f8a | |||
| 3be3ffae4c | |||
| 87c6708fbe | |||
| b296706bc0 | |||
| 7fdc82ae23 | |||
| 246ed87350 | |||
| a28465151f | |||
| f1967bc3a1 | |||
| ccc83d6728 | |||
| c151d719c8 | |||
| cc25833963 | |||
| 2ef3b7cf0f | |||
| 19d7bd5198 | |||
| 121066f938 | |||
| f9b88318ea | |||
| 1d64f64f43 | |||
| 766e2e6a25 | |||
| 8bd7260350 | |||
| a1f4e8b9d4 | |||
| aa3e0860cd | |||
| 67bc9932e5 | |||
| ba98b44504 | |||
| fdeb639f75 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix: do not retry request automatically on auth failure.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add support for sonar and sonar-pro models to SAP AI Core Provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added change to hide the context window usage message from env details when using next gen models and before the usage has reached an elevated state
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix task timeline display height.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Ensure tool arguments are streamed during file operations when native tool calling is enabled.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added thinking level setting for Gemini 3.0 Pro
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: add thought signature support for Gemini SDK
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: Enable native tool calling for Baseten and Kimi K2 models
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Replaces generic robot icon with Cline logo across VS Code UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add Kimi K2 Thinking to Baseten Provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add "Add Last Command to Cline" terminal menu
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Skip MCP tool with invalid name (e.g. name too long) when native tool calling is enabled.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Anthropic provider missing signature param when thinking is enabled.
|
||||
@@ -60,11 +60,11 @@ jobs:
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci --include=optional
|
||||
run: npm install --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci --include=optional
|
||||
run: cd webview-ui && npm install --include=optional
|
||||
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
|
||||
Vendored
+21
@@ -165,6 +165,27 @@
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "Open Storybook",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"storybook"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/webview-ui",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"serverReadyAction": {
|
||||
"pattern": "Local:.*http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
},
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+20
@@ -263,6 +263,26 @@
|
||||
"watch"
|
||||
],
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "storybook",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"label": "npm: storybook",
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -20,6 +20,9 @@ eslint-rules/**
|
||||
.husky/**
|
||||
.env
|
||||
|
||||
# cli
|
||||
cli/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
.nvmrc
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
# Changelog
|
||||
|
||||
## [3.38.2]
|
||||
|
||||
- Add Claude Opus 4.5
|
||||
|
||||
## [3.38.1]
|
||||
|
||||
### Fixed
|
||||
- Fixed handling of 'signature' field in sanitizeAnthropicContentBlock to properly preserve it when thinking is enabled, as required by Anthropic's API.
|
||||
|
||||
## [3.38.0]
|
||||
|
||||
### Added
|
||||
- Gemini 3 Pro Preview model
|
||||
- AquaVoice Avalon model for voice-to-text dictation
|
||||
|
||||
### Fixed
|
||||
- Automatic context truncation when AWS Bedrock token usage rate limits are exceeded
|
||||
- Removed new_task tool from system prompts, updated slash command prompts, and added helper function for native tool calling validation
|
||||
|
||||
|
||||
## [3.37.1]
|
||||
|
||||
- Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
|
||||
- Add AGENTS.md support
|
||||
- feat(models): Add free minimax/mimax-m2 model to the model picker
|
||||
|
||||
## [3.37.0]
|
||||
|
||||
### Added
|
||||
|
||||
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
|
||||
- Nous Research provider with Hermes 4 model family and custom system prompts
|
||||
- Switched to Aqua Voice's Avalon model in speech to text transcription
|
||||
- Added Linux support for speech to text
|
||||
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
|
||||
- Expanded HTTP proxy support throughout the codebase
|
||||
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Duplicate tool results prevention through existence checking
|
||||
- XML entity escaping in model content processor
|
||||
- Commit message generation in command palette
|
||||
- OpenAI Compatible provider temperature parameter type conversion
|
||||
|
||||
## Documentation
|
||||
|
||||
- Added missing proto generation step in CONTRIBUTING.md
|
||||
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)
|
||||
|
||||
## [3.36.1]
|
||||
|
||||
- fix: remove native tool calling support from Gemini and XAI provider due to invalid tool names issues
|
||||
|
||||
+7
-1
@@ -46,7 +46,11 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
```bash
|
||||
npm run install:all
|
||||
```
|
||||
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
|
||||
4. Generate Protocol Buffer files (required before first build):
|
||||
```bash
|
||||
npm run protos
|
||||
```
|
||||
5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
|
||||
|
||||
|
||||
|
||||
@@ -85,8 +89,10 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
|
||||
2. **Local Development**
|
||||
- Run `npm run install:all` to install dependencies
|
||||
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
|
||||
- Run `npm run test` to run tests locally
|
||||
- Run → Start Debugging or `>Debug: Select and Start Debugging` and wait for a new VS Code instance to open
|
||||
- **Terminal Workflow**: Use `npm run dev` (generates protos + runs watch mode) or `npm run watch` (if protos already generated)
|
||||
- Before submitting PR, run `npm run format:fix` to format your code
|
||||
|
||||
3. **Linux-specific Setup**
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
<json>
|
||||
<![CDATA[
|
||||
{
|
||||
"fontFamily": "cline-bot",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0,
|
||||
"fontURL": "https://cline.bot",
|
||||
"designerURL": "https://cline.bot",
|
||||
"licenseURL": "https://cline.bot",
|
||||
"version": "Version 1.0",
|
||||
"fontId": "cline-bot",
|
||||
"psName": "cline-bot",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "cline-bot",
|
||||
"description": "Font generated by IcoMoon."
|
||||
}
|
||||
]]>
|
||||
</json>
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="cline-bot" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="cline" data-tags="cline" horiz-adv-x="977" d="M964.553 383.11l-60.285 121.406v69.495c0 115.545-92.939 209.321-207.647 209.321h-102.986c7.536 15.071 11.722 32.654 11.722 51.074 0 64.471-51.912 116.383-115.545 116.383s-115.545-51.912-115.545-116.383 4.186-35.166 11.722-51.074h-102.986c-114.708 0-207.647-93.776-207.647-209.321v-69.495l-61.959-121.406c-5.861-11.722-5.861-26.793 0-38.515l61.959-119.732v-69.495c0-115.545 92.939-209.321 207.647-209.321h415.294c114.708 0 207.647 93.776 207.647 209.321v69.495l60.285 119.732c5.861 11.722 5.861 25.956 0 38.515v0zM426.178 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132zM731.787 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132z" />
|
||||
</font></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
@@ -76,10 +75,11 @@ func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL
|
||||
}
|
||||
}
|
||||
|
||||
// WORKAROUND: Wait for debounced state persistence to complete
|
||||
// Fixes `cline auth` issue when ran in docker environments
|
||||
// TODO: implement better solution w/ changes in StateManager
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
// Flush pending state changes to disk immediately
|
||||
// This ensures all configuration changes are persisted before the instance terminates
|
||||
if _, err := manager.GetClient().State.FlushPendingState(ctx, &cline.EmptyRequest{}); err != nil {
|
||||
return fmt.Errorf("failed to flush pending state: %w", err)
|
||||
}
|
||||
|
||||
// Success message
|
||||
fmt.Printf("\n✓ Successfully configured %s provider\n", GetProviderDisplayName(providerEnum))
|
||||
@@ -176,6 +176,7 @@ func validateQuickSetupProvider(providerID string) (cline.ApiProvider, error) {
|
||||
cline.ApiProvider_XAI: true,
|
||||
cline.ApiProvider_CEREBRAS: true,
|
||||
cline.ApiProvider_OLLAMA: true,
|
||||
cline.ApiProvider_NOUSRESEARCH: true,
|
||||
}
|
||||
|
||||
if !supportedProviders[provider] {
|
||||
|
||||
@@ -26,6 +26,7 @@ func GetBYOProviderList() []BYOProviderOption {
|
||||
{Name: "Google Gemini", Provider: cline.ApiProvider_GEMINI},
|
||||
{Name: "Ollama", Provider: cline.ApiProvider_OLLAMA},
|
||||
{Name: "Cerebras", Provider: cline.ApiProvider_CEREBRAS},
|
||||
{Name: "NousResearch", Provider: cline.ApiProvider_NOUSRESEARCH},
|
||||
{Name: "Oracle Code Assist", Provider: cline.ApiProvider_OCA},
|
||||
}
|
||||
}
|
||||
@@ -100,6 +101,8 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
|
||||
return "e.g., qwen3-coder:30b"
|
||||
case cline.ApiProvider_CEREBRAS:
|
||||
return "e.g., gpt-oss-120b"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "e.g., Hermes-4-405B"
|
||||
case cline.ApiProvider_OCA:
|
||||
return "e.g., oca/llama4"
|
||||
default:
|
||||
|
||||
@@ -111,6 +111,7 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
cline.ApiProvider_GEMINI,
|
||||
cline.ApiProvider_OLLAMA,
|
||||
cline.ApiProvider_CEREBRAS,
|
||||
cline.ApiProvider_NOUSRESEARCH,
|
||||
cline.ApiProvider_OCA,
|
||||
cline.ApiProvider_HICAP,
|
||||
}
|
||||
@@ -241,6 +242,8 @@ func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
|
||||
return cline.ApiProvider_OCA, true
|
||||
case "hicap":
|
||||
return cline.ApiProvider_HICAP, true
|
||||
case "nousResearch":
|
||||
return cline.ApiProvider_NOUSRESEARCH, true
|
||||
default:
|
||||
return cline.ApiProvider_ANTHROPIC, false // Return 0 value with false
|
||||
}
|
||||
@@ -274,6 +277,8 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
|
||||
return "oca"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "nousResearch"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -353,6 +358,8 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
|
||||
return "Oracle Code Assist"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "Hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "NousResearch"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -475,6 +482,7 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
{cline.ApiProvider_OLLAMA, "ollamaBaseUrl"}, // Ollama uses baseUrl instead of API key
|
||||
{cline.ApiProvider_CEREBRAS, "cerebrasApiKey"},
|
||||
{cline.ApiProvider_HICAP, "hicapApiKey"},
|
||||
{cline.ApiProvider_NOUSRESEARCH, "nousResearchApiKey"},
|
||||
}
|
||||
|
||||
for _, providerCheck := range providersToCheck {
|
||||
|
||||
@@ -163,6 +163,15 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
|
||||
ActModeProviderSpecificModelIDField: "actModeHicapModelId",
|
||||
}, nil
|
||||
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return ProviderFields{
|
||||
APIKeyField: "nousResearchApiKey",
|
||||
PlanModeModelIDField: "planModeApiModelId",
|
||||
ActModeModelIDField: "actModeApiModelId",
|
||||
PlanModeProviderSpecificModelIDField: "planModeNousResearchModelId",
|
||||
ActModeProviderSpecificModelIDField: "actModeNousResearchModelId",
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return ProviderFields{}, fmt.Errorf("unsupported provider: %v", provider)
|
||||
}
|
||||
@@ -278,6 +287,8 @@ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, v
|
||||
apiConfig.OcaApiKey = value
|
||||
case "hicapApiKey":
|
||||
apiConfig.HicapApiKey = value
|
||||
case "nousResearchApiKey":
|
||||
apiConfig.NousResearchApiKey = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +313,9 @@ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldNa
|
||||
case "planModeHicapModelId":
|
||||
apiConfig.PlanModeHicapModelId = value
|
||||
apiConfig.ActModeHicapModelId = value
|
||||
case "planModeNousResearchModelId":
|
||||
apiConfig.PlanModeNousResearchModelId = value
|
||||
apiConfig.ActModeNousResearchModelId = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -517,7 +517,7 @@ func (pw *ProviderWizard) applyModelChange(provider cline.ApiProvider, modelID s
|
||||
ModelInfo: modelInfo,
|
||||
}
|
||||
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, false)
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, true)
|
||||
}
|
||||
|
||||
// SwitchToBYOProvider switches to a BYO provider that's already configured.
|
||||
|
||||
@@ -221,83 +221,7 @@ func (p *ToolResultParser) ParseCodeDefinitions(content string) string {
|
||||
|
||||
// ParseWebFetch formats webFetch tool results with content preview
|
||||
func (p *ToolResultParser) ParseWebFetch(content, url string) string {
|
||||
if content == "" {
|
||||
return fmt.Sprintf("*Fetched content from %s (empty response)*", url)
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
var result strings.Builder
|
||||
|
||||
// Try to extract title
|
||||
var title string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "##") {
|
||||
title = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if title != "" {
|
||||
result.WriteString(fmt.Sprintf("**Title:** %s\n\n", title))
|
||||
}
|
||||
|
||||
// Show preview of content
|
||||
result.WriteString("**Preview:**\n")
|
||||
|
||||
charCount := 0
|
||||
maxChars := 500
|
||||
previewLines := []string{}
|
||||
|
||||
for _, line := range lines {
|
||||
// Skip markdown headers
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if charCount+len(trimmed) > maxChars {
|
||||
break
|
||||
}
|
||||
|
||||
previewLines = append(previewLines, trimmed)
|
||||
charCount += len(trimmed)
|
||||
}
|
||||
|
||||
result.WriteString(strings.Join(previewLines, " "))
|
||||
result.WriteString("...\n\n")
|
||||
|
||||
// Extract sections
|
||||
sections := []string{}
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "##") {
|
||||
section := strings.TrimSpace(strings.TrimPrefix(trimmed, "##"))
|
||||
sections = append(sections, section)
|
||||
if len(sections) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sections) > 0 {
|
||||
result.WriteString("**Sections Found:**\n")
|
||||
for _, section := range sections {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", section))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// Word count estimate
|
||||
wordCount := len(strings.Fields(content))
|
||||
result.WriteString(fmt.Sprintf("*[Full content: ~%s]*", p.formatWordCount(wordCount)))
|
||||
|
||||
return result.String()
|
||||
return ""
|
||||
}
|
||||
|
||||
// detectLanguage returns syntax highlighting language based on file extension
|
||||
|
||||
@@ -145,6 +145,7 @@ const (
|
||||
XAI = "xai"
|
||||
CEREBRAS = "cerebras"
|
||||
OCA = "oca"
|
||||
NOUSRESEARCH = "nousResearch"
|
||||
)
|
||||
|
||||
// AllProviders returns a slice of enabled provider IDs for the CLI build.
|
||||
@@ -161,6 +162,7 @@ var AllProviders = []string{
|
||||
"xai",
|
||||
"cerebras",
|
||||
"oca",
|
||||
"nousResearch",
|
||||
}
|
||||
|
||||
// ConfigField represents a configuration field requirement
|
||||
@@ -318,6 +320,15 @@ var rawConfigFields = ` [
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"name": "nousResearchApiKey",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "nousResearch",
|
||||
"required": true,
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"name": "ulid",
|
||||
"type": "string",
|
||||
@@ -435,6 +446,15 @@ var rawConfigFields = ` [
|
||||
"fieldType": "url",
|
||||
"placeholder": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"name": "minimaxApiLine",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "general",
|
||||
"required": false,
|
||||
"fieldType": "string",
|
||||
"placeholder": ""
|
||||
},
|
||||
{
|
||||
"name": "ocaMode",
|
||||
"type": "string",
|
||||
@@ -775,6 +795,24 @@ var rawModelDefinitions = ` {
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "A compact 20B open-weight Mixture-of-Experts language model designed for strong reasoning and tool use, ideal for edge devices and local inference."
|
||||
},
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 262144,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "Qwen3 Coder 30B MoE model with 3.3B activated parameters, optimized for code generation and analysis with 256K context window."
|
||||
},
|
||||
"qwen.qwen3-coder-480b-a35b-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 262144,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 1,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "Qwen3 Coder 480B flagship MoE model with 35B activated parameters, designed for complex coding tasks with advanced reasoning capabilities and 256K context window."
|
||||
}
|
||||
},
|
||||
"gemini": {
|
||||
@@ -1263,6 +1301,26 @@ var rawModelDefinitions = ` {
|
||||
"supportsPromptCache": false,
|
||||
"description": "SOTA performance with ~1500 tokens/s"
|
||||
}
|
||||
},
|
||||
"nousResearch": {
|
||||
"Hermes-4-405B": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 128000,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "This is the largest model in the Hermes 4 family, and it is the fullest expression of our design, focused on advanced reasoning and creative depth rather than optimizing inference speed or cost."
|
||||
},
|
||||
"Hermes-4-70B": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 128000,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "This incarnation of Hermes 4 balances scale and size. It handles complex reasoning tasks, while staying fast and cost effective. A versatile choice for many use cases."
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -1432,6 +1490,18 @@ func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Configure Oca API credentials`,
|
||||
}
|
||||
|
||||
// NousResearch
|
||||
definitions["nousResearch"] = ProviderDefinition{
|
||||
ID: "nousResearch",
|
||||
Name: "NousResearch",
|
||||
RequiredFields: getFieldsByProvider("nousResearch", configFields, true),
|
||||
OptionalFields: getFieldsByProvider("nousResearch", configFields, false),
|
||||
Models: modelDefinitions["nousResearch"],
|
||||
DefaultModelID: "Hermes-4-405B",
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Configure NousResearch API credentials`,
|
||||
}
|
||||
|
||||
return definitions, nil
|
||||
}
|
||||
@@ -1459,6 +1529,7 @@ func GetProviderDisplayName(providerID string) string {
|
||||
"xai": "X AI (Grok)",
|
||||
"cerebras": "Cerebras",
|
||||
"oca": "Oca",
|
||||
"nousResearch": "NousResearch",
|
||||
}
|
||||
|
||||
if name, exists := displayNames[providerID]; exists {
|
||||
|
||||
+52
-15
@@ -151,7 +151,14 @@
|
||||
"features/slash-commands/deep-planning"
|
||||
]
|
||||
},
|
||||
"features/slash-commands/workflows",
|
||||
{
|
||||
"group": "Workflows",
|
||||
"pages": [
|
||||
"features/slash-commands/workflows/index",
|
||||
"features/slash-commands/workflows/quickstart",
|
||||
"features/slash-commands/workflows/best-practices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Task Management",
|
||||
"pages": [
|
||||
@@ -189,6 +196,7 @@
|
||||
"provider-config/fireworks",
|
||||
"provider-config/zai",
|
||||
"provider-config/gcp-vertex-ai",
|
||||
"provider-config/baseten",
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
@@ -215,8 +223,7 @@
|
||||
"provider-config/vscode-language-model-api",
|
||||
"provider-config/sap-aicore",
|
||||
"provider-config/vercel-ai-gateway",
|
||||
"provider-config/requesty",
|
||||
"provider-config/baseten"
|
||||
"provider-config/requesty"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -241,13 +248,6 @@
|
||||
"exploring-clines-tools/remote-browser-support"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Enterprise",
|
||||
"pages": [
|
||||
"enterprise-solutions/overview",
|
||||
"enterprise-solutions/security-concerns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
@@ -258,15 +258,36 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Enterprise",
|
||||
"icon": "building",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Enterprise Solutions",
|
||||
"pages": [
|
||||
"enterprise-solutions/overview",
|
||||
"enterprise-solutions/onboarding",
|
||||
"enterprise-solutions/members/roles-and-permissions",
|
||||
{
|
||||
"group": "Provider Remote Configuration",
|
||||
"pages": [
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
"enterprise-solutions/provider-remote-config/aws-bedrock/admin-configuration",
|
||||
"enterprise-solutions/provider-remote-config/aws-bedrock/member-configuration"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Learn",
|
||||
"icon": "graduation-cap",
|
||||
"href": "https://cline.bot/learn"
|
||||
},
|
||||
{
|
||||
"tab": "Blog",
|
||||
"icon": "newspaper",
|
||||
"href": "https://cline.bot/blog"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -328,6 +349,22 @@
|
||||
{
|
||||
"source": "/cline-cli/samples",
|
||||
"destination": "/cline-cli/samples/overview"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/configure-AWS-Bedrock-Admin",
|
||||
"destination": "/enterprise-solutions/provider-remote-config/aws-bedrock/admin-configuration"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/configure-AWS-Bedrock-Member",
|
||||
"destination": "/enterprise-solutions/provider-remote-config/aws-bedrock/member-configuration"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/configure-workOS-authkit",
|
||||
"destination": "/enterprise-solutions/onboarding"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/Onboarding your Organization",
|
||||
"destination": "/enterprise-solutions/onboarding"
|
||||
}
|
||||
],
|
||||
"search": {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "Managing Members"
|
||||
sidebarTitle: "Managing Members"
|
||||
description: "A guide to adding, removing, and editing members in your enterprise organization."
|
||||
---
|
||||
|
||||
This guide covers the practical steps for adding, editing, and removing members from your enterprise dashboard. For a conceptual overview of roles and permissions, see the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions).
|
||||
|
||||
<Frame caption="The Members Dashboard provides a central place to manage your team.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/members-dash.png" alt="Members Dashboard" />
|
||||
</Frame>
|
||||
|
||||
## Adding Members
|
||||
|
||||
To invite someone to your organization, you must have an open seat available on your organization.
|
||||
|
||||
1. Navigate to the **Members** tab in your dashboard.
|
||||
2. Click the **Add Members** button.
|
||||
3. Enter one or more email addresses, separated by commas.
|
||||
4. Select a role for the new member(s). It's best practice to start with the "Member" role unless you know they need admin privileges.
|
||||
5. Click **Send Invitation**.
|
||||
|
||||
Invited users will receive an email with a link to join. You can cancel a pending invitation at any time by clicking the trash icon next to the user's email in the 'Pending Invites' section.
|
||||
|
||||
<Tip>
|
||||
**Managing Users at Scale**
|
||||
|
||||
When inviting a large number of users, you can paste a comma-separated list of emails directly into the invitation field. While role changes and removals are performed individually, this bulk invitation feature helps streamline the onboarding process for entire teams.
|
||||
</Tip>
|
||||
|
||||
<Frame caption="Adding members to your organization">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/adding-members.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Editing Member Roles
|
||||
|
||||
As your team's needs change, you can adjust member roles directly from the dashboard.
|
||||
|
||||
- Find the member in your list.
|
||||
- Under the "Role" column, click the dropdown menu.
|
||||
- Select their new role. The change takes effect immediately.
|
||||
|
||||
Refer to the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions) for a detailed breakdown of what each role can do.
|
||||
|
||||
## Removing Members
|
||||
|
||||
Removing a member immediately revokes their access to all organization-specific resources, including shared API keys and configurations.
|
||||
|
||||
1. Go to the **Members Dashboard**.
|
||||
2. Find the member in the list and click the red trash icon (<Icon icon="trash" iconType="solid" />).
|
||||
3. Confirm the removal when prompted.
|
||||
|
||||
<Frame caption="You will be asked to confirm before a member is permanently removed.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/remove-user.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Troubleshooting Invitations
|
||||
|
||||
If an invited user is having trouble joining, check these common issues:
|
||||
|
||||
- **Invitation Not Received**: Ask the user to check their spam or junk mail folder. If it's not there, cancel the pending invitation and try sending it again, verifying the email address is correct.
|
||||
|
||||
- **"Invalid Domain" Error**: The user's email address must belong to a domain that has been verified for your organization. Work with your IT administrator to ensure the necessary domains are configured.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "Managing Members"
|
||||
sidebarTitle: "Managing Members"
|
||||
description: "A guide to adding, removing, and editing members in your enterprise organization."
|
||||
---
|
||||
|
||||
This guide covers the practical steps for adding, editing, and removing members from your enterprise dashboard. For a conceptual overview of roles and permissions, see the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions).
|
||||
|
||||
<Frame caption="The Members Dashboard provides a central place to manage your team.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/members-dash.png" alt="Members Dashboard" />
|
||||
</Frame>
|
||||
|
||||
## Adding Members
|
||||
|
||||
To invite someone to your organization, you must have an open seat available on your organization.
|
||||
|
||||
1. Navigate to the **Members** tab in your dashboard.
|
||||
2. Click the **Add Members** button.
|
||||
3. Enter one or more email addresses, separated by commas.
|
||||
4. Select a role for the new member(s). It's best practice to start with the "Member" role unless you know they need admin privileges.
|
||||
5. Click **Send Invitation**.
|
||||
|
||||
Invited users will receive an email with a link to join. You can cancel a pending invitation at any time by clicking the trash icon next to the user's email in the 'Pending Invites' section.
|
||||
|
||||
<Tip>
|
||||
**Managing Users at Scale**
|
||||
|
||||
When inviting a large number of users, you can paste a comma-separated list of emails directly into the invitation field. While role changes and removals are performed individually, this bulk invitation feature helps streamline the onboarding process for entire teams.
|
||||
</Tip>
|
||||
|
||||
<Frame caption="Adding members to your organization">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/adding-members.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Editing Member Roles
|
||||
|
||||
As your team's needs change, you can adjust member roles directly from the dashboard.
|
||||
|
||||
- Find the member in your list.
|
||||
- Under the "Role" column, click the dropdown menu.
|
||||
- Select their new role. The change takes effect immediately.
|
||||
|
||||
Refer to the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions) for a detailed breakdown of what each role can do.
|
||||
|
||||
## Removing Members
|
||||
|
||||
Removing a member immediately revokes their access to all organization-specific resources, including shared API keys and configurations.
|
||||
|
||||
1. Go to the **Members Dashboard**.
|
||||
2. Find the member in the list and click the red trash icon (<Icon icon="trash" iconType="solid" />).
|
||||
3. Confirm the removal when prompted.
|
||||
|
||||
<Frame caption="You will be asked to confirm before a member is permanently removed.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/remove-user.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Troubleshooting Invitations
|
||||
|
||||
If an invited user is having trouble joining, check these common issues:
|
||||
|
||||
- **Invitation Not Received**: Ask the user to check their spam or junk mail folder. If it's not there, cancel the pending invitation and try sending it again, verifying the email address is correct.
|
||||
|
||||
- **"Invalid Domain" Error**: The user's email address must belong to a domain that has been verified for your organization. Work with your IT administrator to ensure the necessary domains are configured.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Members Overview"
|
||||
sidebarTitle: "Overview"
|
||||
description: "An overview of member management in your enterprise organization."
|
||||
---
|
||||
|
||||
This section provides a comprehensive guide to managing members in your enterprise organization. Here, you'll find everything you need to know about roles, permissions, and the practical steps for adding, editing, and removing members from your dashboard.
|
||||
|
||||
## Key Topics
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Roles and Permissions"
|
||||
icon="user-shield"
|
||||
href="/enterprise-solutions/members/roles-and-permissions"
|
||||
>
|
||||
A detailed breakdown of the available roles and their specific permissions.
|
||||
</Card>
|
||||
<Card
|
||||
title="Managing Members"
|
||||
icon="users-gear"
|
||||
href="/enterprise-solutions/members/managing-members"
|
||||
>
|
||||
A practical guide to adding, editing, and removing members from your
|
||||
dashboard.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: "Roles and Permissions"
|
||||
sidebarTitle: "Roles and Permissions"
|
||||
description: "An overview of member roles, permissions, and best practices for your enterprise organization."
|
||||
---
|
||||
|
||||
Choosing the right role for each member is crucial for maintaining security and ensuring your team can work effectively. This guide provides a detailed breakdown of the available roles, their specific permissions, and best practices for managing your organization.
|
||||
|
||||
## Role Definitions
|
||||
|
||||
Here’s a summary of the available roles and their intended use cases.
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card title="Owner" icon="user-crown">
|
||||
**Best for:** The primary account holder or a small number of designated leaders.
|
||||
|
||||
Owners have unrestricted access to all settings, including billing, member management, and security configurations. To maintain tight control over the organization, the number of Owners should be kept to a minimum.
|
||||
</Card>
|
||||
<Card title="Admin" icon="user-gear">
|
||||
**Best for:** Team leads or IT administrators who need to manage users and configurations.
|
||||
|
||||
Admins can invite, edit, and remove members, as well as manage provider configurations. They have broad access but cannot manage billing or change the Owner. This is a suitable role for trusted team managers.
|
||||
</Card>
|
||||
<Card title="Member" icon="user">
|
||||
**Best for:** Most developers and individual contributors.
|
||||
|
||||
Members can use Cline with the organization's shared resources but cannot change any settings or view other users' activity. This is the safest default role for new users.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Permissions Matrix
|
||||
|
||||
For a detailed comparison, this matrix outlines the specific capabilities of each role.
|
||||
|
||||
| Permission | Member | Admin | Owner |
|
||||
| --------------------------- | :----: | :----: | :----: |
|
||||
| **General Usage** | | | |
|
||||
| Use Cline | ✅ | ✅ | ✅ |
|
||||
| Access Shared API Providers | ✅ | ✅ | ✅ |
|
||||
| | | | |
|
||||
| **Member Management** | | | |
|
||||
| View Members | ❌ | ✅ | ✅ |
|
||||
| Invite New Members | ❌ | ✅ | ✅ |
|
||||
| Edit Member Roles | ❌ | ✅ | ✅ |
|
||||
| Remove Members | ❌ | ✅ | ✅ |
|
||||
| Remove Admins | ❌ | ❌ | ✅ |
|
||||
| | | | |
|
||||
| **Configuration** | | | |
|
||||
| Configure API Providers | ❌ | ✅ | ✅ |
|
||||
| Manage Security Settings | ❌ | ❌ | ✅ |
|
||||
| | | | |
|
||||
| **Billing & Ownership** | | | |
|
||||
| View Billing Information | ❌ | ❌ | ✅ |
|
||||
| Manage Subscription | ❌ | ❌ | ✅ |
|
||||
| Transfer Ownership | ❌ | ❌ | ✅ |
|
||||
|
||||
## Role Management Best Practices
|
||||
|
||||
Effective role management is fundamental to securing your organization.
|
||||
|
||||
- **Apply the Principle of Least Privilege**: Always assign the role with the minimum necessary permissions. Most users should be **Members**. Grant **Admin** rights only to those who are responsible for user management or technical configuration.
|
||||
|
||||
- **Limit the Number of Owners**: The **Owner** role should be reserved for one or two key individuals who control the account and billing. This centralization of power prevents accidental or malicious changes to critical settings.
|
||||
|
||||
- **Regularly Audit Roles**: Periodically review the list of Admins and Owners to ensure the assigned roles are still appropriate. When a team member's responsibilities change, adjust their role accordingly.
|
||||
|
||||
## Identity Providers and Domain Verification
|
||||
|
||||
For a user to successfully join and sign in to your organization, two conditions must be met:
|
||||
1. Their email must be managed by your organization's verified **Identity Provider (IDP)**, such as Microsoft Entra ID, Okta, or AWS.
|
||||
2. Your organization must have a **verified domain** with a provider like Google or Microsoft.
|
||||
|
||||
This ensures that only authenticated users from your company can access your Cline organization.
|
||||
|
||||
## Seat Management and Invitations
|
||||
|
||||
Each user in your organization, regardless of role, consumes one seat from your license.
|
||||
|
||||
- When an invitation is sent, a seat is considered "pending."
|
||||
- If an invited user does not accept, the invitation can be revoked to free up the seat.
|
||||
- Removing a member from the organization immediately frees up a seat.
|
||||
|
||||
Now that you understand the different roles and how to manage them, you can proceed to [configuring provider remote access](/enterprise-solutions/provider-remote-config/aws-bedrock/admin-configuration) for your organization.
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: "Onboarding"
|
||||
|
||||
description: "This guide explains how administrators configure SSO provisioning and user management in Cline Enterprise."
|
||||
---
|
||||
|
||||
## Overview
|
||||
Cline Enterprise integrates with your existing identity provider (IdP) via WorkOS to deliver secure SSO and zero-touch user lifecycle management. In this guide, you'll connect your IdP (Okta, Azure AD, Google Workspace, or any SAML/OIDC provider), enable just-in-time (JIT) provisioning so new users are created automatically on first sign-in, and configure role mapping so permissions stay aligned with your directory—no manual invites or seat reconciliations required.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Cline Enterprise License](https://cline.bot/enterprise)
|
||||
- Access to your identity provider (IdP) configuration (e.g., Okta, Azure AD, Google Workspace)
|
||||
- Knowledge of your organization's SSO requirements
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
### Step 1: Onboard to Cline Enterprise license
|
||||
|
||||
Your IdP administrator will receive an email with a link to register their organization with WorkOS during onboarding.
|
||||
|
||||
### Step 2: Configure Your Identity Provider
|
||||
|
||||
Connect your identity provider (IdP) to WorkOS:
|
||||
|
||||
1. In the WorkOS dashboard, go to **AuthKit → Connections**
|
||||
2. Click **Add Connection**
|
||||
3. Select your identity provider (e.g., Okta, Azure AD, Google Workspace, Generic SAML/OIDC)
|
||||
4. Follow the provider-specific setup instructions
|
||||
|
||||
Each identity provider (IdP) will have its own setup process and required fields. Be sure to follow the specific instructions in the WorkOS dashboard for your chosen provider.
|
||||
For more explicit instruction on connecting your IdP, refer to the [WorkOS SSO documentation](https://workos.com/docs/authkit/sso)
|
||||
|
||||
### Step 3: Configure User Provisioning
|
||||
|
||||
Cline Enterprise uses **just-in-time provisioning** that works automatically:
|
||||
|
||||
- **Organizations are created automatically**
|
||||
- **Users gain access automatically** on their first SSO sign-in, once their credentials have been configured by the IdP administrator.
|
||||
- **Roles sync automatically** from your IdP (Admin/Owner → Admin, Member → Member)
|
||||
- **No manual user invites or seat management** required
|
||||
|
||||
No additional configuration is needed. Users are provisioned automatically when they sign in through SSO.
|
||||
|
||||
### Step 4: Configure User Attributes Mapping
|
||||
|
||||
User roles are mapped automatically from your IdP:
|
||||
|
||||
- **Admin** in IdP → **Admin** role in Cline (Note: The first Owner of the org is created manually during onboarding)
|
||||
- **Member** in IdP → **Member** role in Cline
|
||||
|
||||
<Info>
|
||||
For what each role can access, see the [Roles and Permissions](./members/roles-and-permissions) page.
|
||||
</Info>
|
||||
|
||||
If needed, you can configure additional user attributes in the Cline Admin console:
|
||||
|
||||
1. Go to **Settings → Authentication → User Attributes**
|
||||
2. Map attributes such as email and name based on your IdP configuration
|
||||
|
||||
For information about available user attributes, see the [WorkOS User Object Documentation](https://workos.com/docs/authkit/user-management).
|
||||
|
||||
### Step 5: Test SSO Connection
|
||||
|
||||
Before allowing users to sign in, test the SSO flow to ensure everything is configured correctly.
|
||||
|
||||
**To test the connection:**
|
||||
|
||||
1. In the WorkOS dashboard (or Cline Admin console if available), locate and click **Test SSO Connection**
|
||||
2. You'll be redirected to your IdP's login page
|
||||
3. Enter valid credentials for a test user
|
||||
4. After successful authentication, you should be redirected back
|
||||
5. Confirm that the user's information (name, email, role) displays correctly
|
||||
|
||||
**Expected outcome:** The test user is authenticated, their account details are visible, and their role matches what's configured in your IdP.
|
||||
|
||||
**If the test fails:** Double-check your IdP configuration (redirect URIs, SAML certificates, attribute mappings). See the [WorkOS SSO documentation](https://workos.com/docs/authkit/sso) for troubleshooting guidance.
|
||||
|
||||
### User Access
|
||||
|
||||
Once SSO is configured, users in your IdP can access Cline automatically without manual invites or account setup.
|
||||
|
||||
**First-time sign-in flow:**
|
||||
|
||||
1. User navigates to Cline and clicks **Sign in with SSO**
|
||||
2. User authenticates via your organization's IdP
|
||||
3. Cline automatically creates their account in your Organization
|
||||
4. Role is assigned based on their IdP role (see [Step 4](#step-4-configure-user-attributes-mapping))
|
||||
5. User is redirected to Cline and can begin working
|
||||
|
||||
**What happens automatically:**
|
||||
- Account creation with correct organization assignment
|
||||
- Role and permission assignment
|
||||
- Basic profile information (name, email) populated from IdP
|
||||
|
||||
**No action required:** Users don't need to request access or wait for approval. Access is granted immediately upon successful IdP authentication.
|
||||
|
||||
### Managing Access
|
||||
|
||||
All access management and revocation of users is currently handled by your IdP:
|
||||
|
||||
- Add users → access granted automatically on first login
|
||||
- Change roles → updated on next login
|
||||
- Remove users → access revoked automatically
|
||||
|
||||
<Info>
|
||||
Role changes sync automatically on the user's next sign-in.
|
||||
</Info>
|
||||
|
||||
### Changing your IdP
|
||||
|
||||
In order to change to a different IdP, please contact support and we will guide you through this process.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
Steps to verify successful configuration:
|
||||
|
||||
1. **Test User Sign-In**: Have a test user sign in through the SSO flow (access is granted automatically on first login)
|
||||
2. **Verify User Provisioning**: Confirm that the user is automatically created and has appropriate role permissions
|
||||
3. **Check User Attributes**: Verify that user information (name, email, organization) is correctly populated
|
||||
4. **Test Role Changes**: Update a user's role in your IdP and verify it syncs on their next login
|
||||
5. **Test User Deprovisioning**: Remove a user from your IdP and verify they lose access to Cline on their next login attempt
|
||||
6. **Review Audit Logs**: Check WorkOS audit logs to ensure authentication events are being recorded
|
||||
|
||||
---
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "Configure AWS Bedrock Provider (Admin)"
|
||||
sidebarTitle: "Configure AWS Bedrock (Admin)"
|
||||
description: "This guide explains how administrators configure AWS Bedrock as the organization-wide LLM provider for Cline."
|
||||
---
|
||||
|
||||
As an administrator, you can add AWS Bedrock as the organization-wide LLM provider for all Cline users. This centralized approach ensures consistent access to Amazon's AI models while maintaining your organization's security and compliance requirements through VPC endpoints, region controls, and prompt caching optimizations.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
To get started with setting up AWS Bedrock as your organization's LLM provider, you'll need a few items in place.
|
||||
|
||||
**Administrator access to the Cline Admin console**
|
||||
You need admin privileges to enforce provider settings across your organization. If you can navigate to **Settings → Cline Settings** in the admin console at [app.cline.bot](https://app.cline.bot), you have the right access level.
|
||||
|
||||
<Info>
|
||||
**Quick Check**: Try accessing the settings page now. If you can see the provider configuration options, you're good to go.
|
||||
</Info>
|
||||
|
||||
**AWS Bedrock account with the right permissions**
|
||||
Your AWS account needs specific Bedrock permissions to work with Cline.
|
||||
|
||||
<Note>
|
||||
If you don't have direct AWS access, coordinate with your cloud team to get these permissions set up before proceeding.
|
||||
</Note>
|
||||
|
||||
**Your preferred AWS region**
|
||||
Choose your primary AWS region carefully since this will be enforced for all users.
|
||||
|
||||
<Tip>
|
||||
Check which models are available in your region first. Some newer models might not be available in all regions yet.
|
||||
</Tip>
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/AWS%20Remote%20Config.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
<Steps>
|
||||
<Step title="Access Cline Settings">
|
||||
Navigate to [app.cline.bot](https://app.cline.bot) and sign in with your administrator account. Go to **Settings → Cline Settings**.
|
||||
|
||||
<Info>
|
||||
You should see the provider configuration options if you have the correct admin access level.
|
||||
</Info>
|
||||
</Step>
|
||||
|
||||
<Step title="Enable Remote Provider Configuration">
|
||||
Toggle on **Enable settings** to reveal the remote provider configuration options. This allows you to enforce provider settings across your organization.
|
||||
</Step>
|
||||
|
||||
<Step title="Select AWS Bedrock as the API Provider">
|
||||
Open the **API Provider** dropdown menu and select **Amazon Bedrock**. This will open the Bedrock configuration panel where you'll configure all your organization-wide settings.
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Bedrock Settings">
|
||||
The configuration panel includes several settings that control how Bedrock works for your organization. Configure what you need:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Region (required)">
|
||||
Enter your preferred AWS region like `us-west-2` or `us-east-1`. This region will be enforced for all organization members.
|
||||
|
||||
[View AWS Global Infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/)
|
||||
|
||||
<Tip>
|
||||
For most organizations, `us-east-1` or `us-west-2` are recommended as they have the best model availability.
|
||||
</Tip>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Custom VPC Endpoint (optional)">
|
||||
If your organization uses a private VPC endpoint for Bedrock, specify it here to ensure all API calls go through your network infrastructure.
|
||||
|
||||
[Learn more about AWS PrivateLink](https://docs.aws.amazon.com/vpc/latest/userguide/endpoint-services-overview.html)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Cross-region Inference (optional)">
|
||||
Enable this to let Bedrock automatically route requests to other regions when your primary region has capacity constraints. Useful for maintaining availability during high-demand periods.
|
||||
|
||||
[Learn more about Inference Profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Global Inference Profile (optional)">
|
||||
Turn this on to use AWS's global inference routing, which automatically directs requests to the optimal region based on availability and latency.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Prompt Caching (optional)">
|
||||
Enable prompt caching to reduce costs and latency. Bedrock caches portions of prompts that remain consistent across requests, making repeated interactions faster and cheaper.
|
||||
|
||||
[Learn more about Prompt Caching](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</Step>
|
||||
|
||||
<Step title="Save Configuration">
|
||||
After configuring your settings, close the provider configuration panel and click **Save** on the settings page to persist your changes.
|
||||
|
||||
Once saved, all organization members signed into the Cline extension will automatically use AWS Bedrock with your configured settings. They won't be able to select other providers or switch to their personal Cline accounts.
|
||||
|
||||
<Warning>
|
||||
Members can't switch to personal Cline accounts or join other organizations once remote configuration is enabled. This ensures consistent provider usage across your team.
|
||||
</Warning>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Verification
|
||||
|
||||
To verify the configuration:
|
||||
|
||||
1. Check that the provider shows as "Amazon Bedrock" in the Enabled provider field
|
||||
2. Confirm the settings persist after refreshing the page
|
||||
3. Test with a member account to ensure they see only Bedrock as a provider
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Members don't see the configured provider**
|
||||
Ensure you clicked Save after closing the configuration panel. Verify the member account belongs to the correct organization.
|
||||
|
||||
**Configuration changes don't persist**
|
||||
Make sure to click the Save button on the main settings page, not just close the configuration panel.
|
||||
|
||||
**Need to change regions later**
|
||||
You can update the region at any time. Members will need to ensure their local AWS credentials have access to the new region. For more information, refer to the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html).
|
||||
|
||||
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your internal cloud team.
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: "Configure AWS Bedrock in VS Code (Members)"
|
||||
sidebarTitle: "Configure AWS Bedrock (Member)"
|
||||
description: "Guide for engineers configuring AWS Bedrock credentials in VS Code after admin setup"
|
||||
---
|
||||
|
||||
As a team member, you can connect your local development environment to your organization's AWS Bedrock setup. This guide walks you through configuring your AWS credentials in VS Code so you can start using models through your organization's Bedrock infrastructure. Your administrator has already configured the provider settings—you just need to add your credentials to get started.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
To successfully connect to your organization's AWS Bedrock setup, you'll need a few things ready.
|
||||
|
||||
**Cline extension installed and configured**
|
||||
The Cline extension must be installed in VS Code and you need to be signed into your organization account. If you haven't installed Cline yet, follow our [installation guide](/getting-started/installing-cline).
|
||||
|
||||
<Info>
|
||||
**Quick Check**: Open the Cline panel in VS Code. If you see your organization name in the bottom left, you're signed in correctly.
|
||||
</Info>
|
||||
|
||||
**AWS credentials with Bedrock access**
|
||||
You need AWS credentials that have permission to access Bedrock in your organization's configured region.
|
||||
|
||||
<Note>
|
||||
If you don't have AWS credentials yet, reach out to your IT or cloud team to get access keys or AWS CLI profiles configured with the necessary Bedrock permissions.
|
||||
</Note>
|
||||
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/VS%20Code%20Bedrock%20API%20Key.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
<Steps>
|
||||
<Step title="Open Cline Settings">
|
||||
Open VS Code and access the Cline settings panel using either of these methods:
|
||||
|
||||
- Click the settings icon (⚙️) in the Cline panel
|
||||
- Click on the API Provider dropdown located directly below the chat area (it will display as `bedrock.anthropic.claude-sonnet-4-20250514-v1:0` or similar)
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Select Your Authentication Method">
|
||||
Choose one of the following credential methods to authenticate with AWS Bedrock:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="AWS Bedrock API Key">
|
||||
Use dedicated AWS access keys specifically for Bedrock access.
|
||||
|
||||
[Learn more about AWS Bedrock API Keys](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html)
|
||||
|
||||
1. Select the **API Key** radio button
|
||||
2. Enter your AWS Access Key ID and Secret Access Key
|
||||
3. These credentials are stored locally and used only by the VS Code extension
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="AWS Profile">
|
||||
Use an existing AWS CLI profile configured on your machine.
|
||||
|
||||
[Learn more about AWS CLI Profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
|
||||
|
||||
1. Select the **AWS Profile** radio button
|
||||
2. Choose or enter the profile name from your `~/.aws/credentials` file
|
||||
3. Cline will use the credentials associated with that profile
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="AWS Credentials">
|
||||
Use your default AWS credential chain (environment variables, EC2 instance roles, etc.).
|
||||
|
||||
1. Select the **AWS Credentials** radio button
|
||||
2. Cline will automatically detect credentials from your environment using the standard AWS credential provider chain
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
The AWS Region is preconfigured by your administrator and does not need to be set in the extension.
|
||||
</Note>
|
||||
</Step>
|
||||
|
||||
<Step title="Verify Configuration">
|
||||
After selecting your authentication method, the extension will display checkmarks for enabled features:
|
||||
|
||||
- ✓ Supports images
|
||||
- ✓ Supports browser use
|
||||
- ✓ Supports prompt caching
|
||||
|
||||
Additional settings like cross-region inference and global inference profile will be locked (shown with a lock icon 🔒) as they're controlled by your administrator.
|
||||
</Step>
|
||||
|
||||
<Step title="Test the Connection">
|
||||
Send a test message in Cline to verify your credentials work correctly with the configured Bedrock region.
|
||||
|
||||
<Tip>
|
||||
**Testing Recommendation**
|
||||
|
||||
It is recommended to test the connection in plan mode to verify everything works correctly before using it for actual tasks.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Authentication errors ("Access Denied" or "Invalid Credentials")**
|
||||
Verify your chosen credential method has the necessary IAM permissions to call Bedrock in the configured region. Required permissions include `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream`. For more information, refer to [AWS Bedrock IAM Permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html).
|
||||
|
||||
**Region-related errors or "model not available"**
|
||||
Ask your administrator to confirm which region is configured for your organization. Ensure your AWS credentials have access to Bedrock in that specific region. [View AWS Global Infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/)
|
||||
|
||||
**Don't see AWS Bedrock as an option**
|
||||
Confirm you're signed into the correct Cline organization. Verify your administrator has saved the Bedrock configuration. Try signing out and back into the extension.
|
||||
|
||||
**AWS Credentials option not finding credentials**
|
||||
Verify AWS CLI is installed and configured with `aws configure` ([AWS CLI Installation Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)). Check that credentials are present in `~/.aws/credentials`. For EC2/ECS environments, ensure IAM roles are properly attached. If using environment variables, set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
|
||||
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
When configuring your AWS credentials, follow these security guidelines:
|
||||
|
||||
- Use IAM roles with minimum required permissions ([AWS IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html))
|
||||
- Rotate access keys regularly if using the API Key method
|
||||
- Never store credentials in code or version control
|
||||
- Prefer AWS Profile method for better credential management
|
||||
- Consider using AWS SSO/federated roles for enhanced security
|
||||
|
||||
Your organization administrator controls which models are available. The extension will automatically display available models based on your region's Bedrock configuration. For more information about available models, refer to the [AWS Bedrock Model Access documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html).
|
||||
|
||||
For further assistance, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your organization's cloud administrator.
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
title: "Security Concerns"
|
||||
---
|
||||
|
||||
## Enterprise Security with Cline
|
||||
|
||||
Cline addresses enterprise security concerns through its unique client-side architecture that prioritizes data privacy, secure cloud integration, and transparent operations. Below is a comprehensive overview of how Cline maintains robust security measures for enterprise environments.
|
||||
|
||||
### Client-Side Architecture
|
||||
|
||||
Cline operates exclusively as a client-side VSCode extension with zero server-side components. This fundamental design choice ensures that your code and data remain within your secure environment at all times. Unlike traditional AI assistants that send data to external servers for processing, Cline connects directly to your chosen cloud provider's AI endpoints, keeping all sensitive information within your infrastructure boundaries.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-arch.png"
|
||||
alt="Cline's relationship to local and remote assets"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Data Privacy Commitment
|
||||
|
||||
Cline implements a strict zero data retention policy, meaning your intellectual property never leaves your secure environment. The extension does not collect, store, or transmit your code to any central servers. This approach significantly reduces potential attack vectors that might otherwise be introduced through data transmission to third-party systems. Telemetry collection is optional and requires explicit consent.
|
||||
|
||||
### Cloud Provider Integration
|
||||
|
||||
Enterprise teams can access cutting-edge AI models through their existing cloud deployments. Cline supports seamless integration with:
|
||||
|
||||
- AWS Bedrock
|
||||
- Google Cloud Vertex AI
|
||||
- Microsoft Azure
|
||||
|
||||
These integrations utilize your organization's existing security credentials, including native IAM role assumption for AWS. This ensures that all AI processing occurs within your corporate cloud environment, maintaining compliance with your established security protocols.
|
||||
|
||||
### Open-Source Transparency
|
||||
|
||||
Cline's codebase is completely open-source, allowing for comprehensive security auditing by your internal teams. This transparency enables security professionals to verify exactly how the extension functions and confirm that it adheres to your organization's security requirements. Organizations can review the code to ensure it aligns with their security policies before deployment.
|
||||
|
||||
### Controlled Modifications
|
||||
|
||||
The extension implements safeguards against unauthorized changes to your codebase. Cline requires explicit user approval for all file modifications and terminal commands, preventing accidental or unwanted alterations. This approval-based workflow maintains the integrity of your projects while still providing AI assistance.
|
||||
|
||||
### Enterprise Deployment Support
|
||||
|
||||
For organizations with strict security review processes, Cline provides comprehensive documentation including detailed deployment diagrams, sequence diagrams illustrating all data flows, and complete security posture documentation. These materials facilitate thorough security reviews and help demonstrate compliance with enterprise data handling standards and regulations.
|
||||
|
||||
### Access Control
|
||||
|
||||
Enterprise editions of Cline (planned for Q2 2025) will include centralized administration features that allow organizations to:
|
||||
|
||||
- Manage user access with customizable permission levels
|
||||
- Provision accounts with corporate credentials
|
||||
- Immediately revoke access when needed
|
||||
- Control which AI providers and LLM endpoints can be used
|
||||
- Deploy standardized settings across the organization
|
||||
- Prevent unauthorized use of personal API keys
|
||||
|
||||
### Compliance and Governance
|
||||
|
||||
Cline's architecture supports compliance with data sovereignty requirements and enterprise data handling regulations. The planned Enterprise Complete edition will further enhance governance with detailed audit logging, compliance reporting, and automated policy enforcement mechanisms.
|
||||
|
||||
By combining client-side processing, direct cloud provider integration, and transparent operations, Cline offers enterprise teams a secure way to leverage AI assistance while maintaining strict control over their sensitive code and data.
|
||||
@@ -81,6 +81,20 @@ your-project/
|
||||
|
||||
Cline's system prompt, on the other hand, is not user-editable ([here's where you can find it](https://github.com/cline/cline/blob/main/src/core/prompts/system.ts)). For a broader look at prompt engineering best practices, check out [this resource](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview).
|
||||
|
||||
### AGENTS.md Standard Support
|
||||
|
||||
Cline also supports the [AGENTS.md](https://agents.md/) standard as a fallback
|
||||
(in addition to Cline Rules) by automatically detecting `AGENTS.md` files in
|
||||
your workspace root. This allows you to use the same rules file across different AI
|
||||
coding tools.
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── AGENTS.md
|
||||
├── src/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Tips for Writing Effective Cline Rules
|
||||
|
||||
- Be Clear and Concise: Use simple language and avoid ambiguity.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Dictation"
|
||||
description: "Communicate with Cline using your voice for faster, more natural AI collaboration"
|
||||
---
|
||||
|
||||
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about unlocking the kind of fluid collaboration that typing can't match.
|
||||
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about enabling fluid collaboration that typing can't match.
|
||||
|
||||
## Why Voice Changes Everything
|
||||
|
||||
@@ -35,11 +35,14 @@ Dictation works with any AI model you've configured. The transcription happens t
|
||||
|
||||
## System Requirements
|
||||
|
||||
<Note>
|
||||
Dictation is currently not available on Windows. Support for Windows is planned for a future release.
|
||||
</Note>
|
||||
|
||||
Dictation uses FFmpeg to capture your voice across all platforms:
|
||||
|
||||
- **macOS**: FFmpeg (via Homebrew: `brew install ffmpeg`)
|
||||
- **Linux**: FFmpeg (via apt: `sudo apt-get install ffmpeg`)
|
||||
- **Windows**: FFmpeg (via winget: `winget install Gyan.FFmpeg`)
|
||||
|
||||
If you don't have FFmpeg installed, Cline will automatically detect this and prompt you to install it with a single click.
|
||||
|
||||
|
||||
+51
-23
@@ -76,7 +76,7 @@ echo "$input" | jq -r '.timestamp | type'
|
||||
|
||||
This example script demonstrates the key mechanics of hook input/output: reading the JSON payload from stdin with `input=$(cat)`, and using `jq` to inspect the data structure and field types that your hook receives. This helps you understand what data is available before building more complex hook logic.
|
||||
|
||||
#### Make it executable
|
||||
**Make it executable**
|
||||
|
||||
```bash
|
||||
chmod +x .clinerules/hooks/TaskStart
|
||||
@@ -92,6 +92,32 @@ Start a task in Cline and verify your hook executes.
|
||||
Start with a simple hook that just logs information before building complex validation logic. This helps you understand the data structure and timing.
|
||||
</Tip>
|
||||
|
||||
|
||||
## What You Can Build
|
||||
|
||||
Once you understand the basics, hooks open up creative possibilities:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Intelligent Code Review" icon="code-branch">
|
||||
Run linters or custom validators before files get saved. Block commits that don't pass checks. Track code quality metrics over time.
|
||||
</Card>
|
||||
|
||||
<Card title="Security Enforcement" icon="shield-halved">
|
||||
Prevent operations that violate security policies. Detect when sensitive data might be exposed. Audit all file access for compliance.
|
||||
</Card>
|
||||
|
||||
<Card title="Development Analytics" icon="chart-line">
|
||||
Measure how long different operations take. Identify patterns in how the AI works. Generate productivity reports from hook data.
|
||||
</Card>
|
||||
|
||||
<Card title="Integration Hub" icon="plug">
|
||||
Connect to issue trackers when certain keywords appear. Update project management tools. Sync with external APIs at the right moments.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
The key is combining hooks with external tools. A hook can be the glue between Cline's workflow and the rest of your development ecosystem.
|
||||
|
||||
|
||||
## Hook Types
|
||||
|
||||
Cline provides multiple hook types that let you tap into different stages of the AI workflow. They're organized into categories based on their trigger points and use cases.
|
||||
@@ -341,22 +367,6 @@ Context injection affects future decisions, not current ones. When a hook runs:
|
||||
This means PreToolUse hooks are for blocking bad actions, while PostToolUse hooks are for learning from completed ones.
|
||||
|
||||
|
||||
## What You Can Build
|
||||
|
||||
Once you understand the basics, hooks open up creative possibilities:
|
||||
|
||||
- **Intelligent Code Review**:
|
||||
Run linters or custom validators before files get saved. Block commits that don't pass checks. Track code quality metrics over time.
|
||||
|
||||
- **Security Enforcement**:
|
||||
Prevent operations that violate security policies. Detect when sensitive data might be exposed. Audit all file access for compliance.
|
||||
|
||||
- **Development Analytics**: Measure how long different operations take. Identify patterns in how the AI works. Generate productivity reports from hook data.
|
||||
|
||||
- **Integration Hub**: Connect to issue trackers when certain keywords appear. Update project management tools. Sync with external APIs at the right moments.
|
||||
|
||||
The key is combining hooks with external tools. A hook can be the glue between Cline's workflow and the rest of your development ecosystem.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Running
|
||||
@@ -371,12 +381,30 @@ The key is combining hooks with external tools. A hook can be the glue between C
|
||||
- Consider moving complex logic to a background process
|
||||
|
||||
### Context Not Affecting Behavior
|
||||
- Remember: context affects FUTURE decisions, not the current tool
|
||||
- The current AI behavior is based on the previous "API Request..." block
|
||||
- Your `contextModification` gets injected into the NEXT "API Request..." block
|
||||
- Use PreToolUse for validation (blocking) if you need immediate effect
|
||||
- Ensure context modifications are clear and actionable
|
||||
- Check that context isn't being truncated (50KB limit)
|
||||
Remember that context modifications affect future AI decisions, not the current operation. The AI's current behavior is based on the previous "API Request..." block, and your `contextModification` gets injected into the next "API Request..." block. This means if you need immediate effect, you should use PreToolUse hooks for validation and return `cancel: true` in your hook's JSON response to block Cline from continuing.
|
||||
|
||||
When adding context, ensure your modifications are clear and actionable so the AI can understand and apply them effectively. Also check that your context isn't being truncated due to the 50KB limit, as this could prevent important information from reaching the AI.
|
||||
|
||||
### Handling Strings with Quotes in JSON Payloads
|
||||
When your hook needs to include strings containing unescaped quote characters (`"`) in JSON output, use jq's `--arg` flag for proper escaping:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# When $output contains unescaped quote characters (")...
|
||||
output='{"foo":"bar"}'
|
||||
|
||||
# Use the --arg flag for automatic string escaping
|
||||
jq -n --arg ctx "$output" '{cancel: false, contextModification: $ctx}'
|
||||
|
||||
# This will result in:
|
||||
# {
|
||||
# "cancel": false,
|
||||
# "contextModification": "{\"foo\":\"bar\"}"
|
||||
# }
|
||||
```
|
||||
|
||||
The `--arg` flag automatically escapes special characters, preventing JSON parsing errors when your context modification includes complex strings or nested JSON structures.
|
||||
|
||||
<Warning>
|
||||
Hooks run with the same permissions as VS Code. They can access all workspace files and environment variables. Review hooks from untrusted sources before enabling them.
|
||||
|
||||
@@ -1,445 +0,0 @@
|
||||
---
|
||||
title: "Workflows"
|
||||
sidebarTitle: "Workflows"
|
||||
---
|
||||
|
||||
Workflows allow you to define a series of steps to guide Cline through a repetitive set of tasks, such as deploying a service or submitting a PR.
|
||||
|
||||
To invoke a workflow, type `/[workflow-name.md]` in the chat.
|
||||
|
||||
## How to Create and Use Workflows
|
||||
|
||||
Workflows live alongside [Cline Rules](/features/cline-rules). Creating one is straightforward:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/workflows.png" alt="Workflows tab in Cline" />
|
||||
</Frame>
|
||||
|
||||
1. Create a markdown file with clear instructions for the steps Cline should take
|
||||
2. Save it with a `.md` extension in your workflows directory
|
||||
3. To trigger a workflow, just type `/` followed by the workflow filename
|
||||
4. Provide any required parameters when prompted
|
||||
|
||||
The real power comes from how you structure your workflow files. You can:
|
||||
|
||||
- Leverage Cline's [built-in tools](/exploring-clines-tools/cline-tools-guide) like `ask_followup_question`, `read_file`, `search_files`, and `new_task`
|
||||
- Use command-line tools you already have installed like `gh` or `docker`
|
||||
- Reference external [MCP tool calls](/mcp/mcp-overview) like Slack or Whatsapp
|
||||
- Chain multiple actions together in a specific sequence
|
||||
|
||||
## Real-world Example
|
||||
|
||||
I created a PR Review workflow that's already saving me tons of time.
|
||||
|
||||
````md pr-review.md [expandable]
|
||||
You have access to the `gh` terminal command. I already authenticated it for you. Please review it to use the PR that I asked you to review. You're already in the `cline` repo.
|
||||
|
||||
<detailed_sequence_of_steps>
|
||||
|
||||
# GitHub PR Review Process - Detailed Sequence of Steps
|
||||
|
||||
## 1. Gather PR Information
|
||||
|
||||
1. Get the PR title, description, and comments:
|
||||
|
||||
```bash
|
||||
gh pr view <PR-number> --json title,body,comments
|
||||
```
|
||||
|
||||
2. Get the full diff of the PR:
|
||||
```bash
|
||||
gh pr diff <PR-number>
|
||||
```
|
||||
|
||||
## 2. Understand the Context
|
||||
|
||||
1. Identify which files were modified in the PR:
|
||||
|
||||
```bash
|
||||
gh pr view <PR-number> --json files
|
||||
```
|
||||
|
||||
2. Examine the original files in the main branch to understand the context:
|
||||
|
||||
```xml
|
||||
<read_file>
|
||||
<path>path/to/file</path>
|
||||
</read_file>
|
||||
```
|
||||
|
||||
3. For specific sections of a file, you can use search_files:
|
||||
```xml
|
||||
<search_files>
|
||||
<path>path/to/directory</path>
|
||||
<regex>search term</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
```
|
||||
|
||||
## 3. Analyze the Changes
|
||||
|
||||
1. For each modified file, understand:
|
||||
|
||||
- What was changed
|
||||
- Why it was changed (based on PR description)
|
||||
- How it affects the codebase
|
||||
- Potential side effects
|
||||
|
||||
2. Look for:
|
||||
- Code quality issues
|
||||
- Potential bugs
|
||||
- Performance implications
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
|
||||
## 4. Ask for User Confirmation
|
||||
|
||||
1. Before making a decision, ask the user if you should approve the PR, providing your assessment and justification:
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Based on my review of PR #<PR-number>, I recommend [approving/requesting changes]. Here's my justification:
|
||||
|
||||
[Detailed justification with key points about the PR quality, implementation, and any concerns]
|
||||
|
||||
Would you like me to proceed with this recommendation?</question>
|
||||
<options>["Yes, approve the PR", "Yes, request changes", "No, I'd like to discuss further"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## 5. Ask if User Wants a Comment Drafted
|
||||
|
||||
1. After the user decides on approval/rejection, ask if they would like a comment drafted:
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Would you like me to draft a comment for this PR that you can copy and paste?</question>
|
||||
<options>["Yes, please draft a comment", "No, I'll handle the comment myself"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
2. If the user wants a comment drafted, provide a well-structured comment they can copy:
|
||||
|
||||
```
|
||||
Thank you for this PR! Here's my assessment:
|
||||
|
||||
[Detailed assessment with key points about the PR quality, implementation, and any suggestions]
|
||||
|
||||
[Include specific feedback on code quality, functionality, and testing]
|
||||
```
|
||||
|
||||
## 6. Make a Decision
|
||||
|
||||
1. Approve the PR if it meets quality standards:
|
||||
|
||||
```bash
|
||||
# For single-line comments:
|
||||
gh pr review <PR-number> --approve --body "Your approval message"
|
||||
|
||||
# For multi-line comments with proper whitespace formatting:
|
||||
cat << EOF | gh pr review <PR-number> --approve --body-file -
|
||||
Thanks @username for this PR! The implementation looks good.
|
||||
|
||||
I particularly like how you've handled X and Y.
|
||||
|
||||
Great work!
|
||||
EOF
|
||||
```
|
||||
|
||||
2. Request changes if improvements are needed:
|
||||
|
||||
```bash
|
||||
# For single-line comments:
|
||||
gh pr review <PR-number> --request-changes --body "Your feedback message"
|
||||
|
||||
# For multi-line comments with proper whitespace formatting:
|
||||
cat << EOF | gh pr review <PR-number> --request-changes --body-file -
|
||||
Thanks @username for this PR!
|
||||
|
||||
The implementation looks promising, but there are a few things to address:
|
||||
|
||||
1. Issue one
|
||||
2. Issue two
|
||||
|
||||
Please make these changes and we can merge this.
|
||||
EOF
|
||||
```
|
||||
|
||||
Note: The `cat << EOF | ... --body-file -` approach preserves all whitespace and formatting without requiring temporary files. The `-` parameter tells the command to read from standard input.
|
||||
</detailed_sequence_of_steps>
|
||||
|
||||
<example_review_process>
|
||||
|
||||
# Example PR Review Process
|
||||
|
||||
Let's walk through a real example of reviewing PR #3627 which fixes the thinking mode calculation for Claude 3.7 models.
|
||||
|
||||
## Step 1: Gather PR Information
|
||||
|
||||
```bash
|
||||
# Get PR details
|
||||
gh pr view 3627 --json title,body,comments
|
||||
|
||||
# Get the full diff
|
||||
gh pr diff 3627
|
||||
```
|
||||
|
||||
## Step 2: Understand the Context
|
||||
|
||||
```xml
|
||||
# Examine the original files to understand what's being changed
|
||||
<read_file>
|
||||
<path>src/shared/api.ts</path>
|
||||
</read_file>
|
||||
|
||||
# Look at the ThinkingBudgetSlider component implementation
|
||||
<read_file>
|
||||
<path>webview-ui/src/components/settings/ThinkingBudgetSlider.tsx</path>
|
||||
</read_file>
|
||||
|
||||
# Check how thinking mode is currently handled in API providers
|
||||
<search_files>
|
||||
<path>src/api/providers</path>
|
||||
<regex>reasoningOn</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
```
|
||||
|
||||
## Step 3: Analyze the Changes
|
||||
|
||||
From the PR information and code examination, we can see that:
|
||||
|
||||
1. The PR fixes an issue with the thinking mode budget calculation for Claude 3.7 models
|
||||
2. Currently, the thinking budget is incorrectly calculated as 80% of maxTokens (8192), resulting in 6553 tokens
|
||||
3. Claude 3.7 actually supports a much larger thinking budget (up to 64000 tokens)
|
||||
4. The PR adds a `thinkingConfig` property with `maxBudget: 64000` to model definitions
|
||||
5. It updates API handlers to use this value when reasoning mode is enabled
|
||||
6. It enhances the slider component to calculate max values based on model-specific percentages
|
||||
7. Comprehensive tests were added to verify the calculations
|
||||
|
||||
## Step 4: Ask for User Confirmation
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Based on my review of PR #3627, I recommend approving this PR. Here's my justification:
|
||||
|
||||
1. The PR correctly fixes the thinking mode budget calculation for Claude 3.7 models
|
||||
2. It properly implements the thinkingConfig.maxBudget property (64000) for Claude 3.7 models
|
||||
3. It applies the appropriate percentage (50%) for the slider calculation
|
||||
4. The changes are well-tested with comprehensive unit tests
|
||||
5. The implementation is clean and follows the project's coding standards
|
||||
6. The PR includes helpful screenshots demonstrating the fix
|
||||
|
||||
Would you like me to proceed with approving this PR?</question>
|
||||
<options>["Yes, approve the PR", "No, I'd like to discuss further", "Let me review it myself first"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## Step 5: Ask if User Wants a Comment Drafted
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Would you like me to draft a comment for this PR that you can copy and paste?</question>
|
||||
<options>["Yes, please draft a comment", "No, I'll handle the comment myself"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## Step 6: Make a Decision
|
||||
|
||||
```bash
|
||||
# Option 1: Simple one-line comment
|
||||
gh pr review 3627 --approve --body "This PR looks good! It correctly fixes the thinking mode budget calculation for Claude 3.7 models."
|
||||
|
||||
# Option 2: Multi-line comment with proper whitespace formatting
|
||||
cat << EOF | gh pr review 3627 --approve --body-file -
|
||||
This PR looks good! It correctly fixes the thinking mode budget calculation for Claude 3.7 models.
|
||||
|
||||
I particularly like:
|
||||
1. The proper implementation of thinkingConfig.maxBudget property (64000)
|
||||
2. The appropriate percentage (50%) for the slider calculation
|
||||
3. The comprehensive unit tests
|
||||
4. The clean implementation that follows project coding standards
|
||||
|
||||
Great work!
|
||||
EOF
|
||||
```
|
||||
|
||||
</example_review_process>
|
||||
|
||||
<common_gh_commands>
|
||||
|
||||
# Common GitHub CLI Commands for PR Review
|
||||
|
||||
## Basic PR Commands
|
||||
|
||||
```bash
|
||||
# List open PRs
|
||||
gh pr list
|
||||
|
||||
# View a specific PR
|
||||
gh pr view <PR-number>
|
||||
|
||||
# View PR with specific fields
|
||||
gh pr view <PR-number> --json title,body,comments,files,commits
|
||||
|
||||
# Check PR status
|
||||
gh pr status
|
||||
```
|
||||
|
||||
## Diff and File Commands
|
||||
|
||||
```bash
|
||||
# Get the full diff of a PR
|
||||
gh pr diff <PR-number>
|
||||
|
||||
# List files changed in a PR
|
||||
gh pr view <PR-number> --json files
|
||||
|
||||
# Check out a PR locally
|
||||
gh pr checkout <PR-number>
|
||||
```
|
||||
|
||||
## Review Commands
|
||||
|
||||
```bash
|
||||
# Approve a PR (single-line comment)
|
||||
gh pr review <PR-number> --approve --body "Your approval message"
|
||||
|
||||
# Approve a PR (multi-line comment with proper whitespace)
|
||||
cat << EOF | gh pr review <PR-number> --approve --body-file -
|
||||
Your multi-line
|
||||
approval message with
|
||||
|
||||
proper whitespace formatting
|
||||
EOF
|
||||
|
||||
# Request changes on a PR (single-line comment)
|
||||
gh pr review <PR-number> --request-changes --body "Your feedback message"
|
||||
|
||||
# Request changes on a PR (multi-line comment with proper whitespace)
|
||||
cat << EOF | gh pr review <PR-number> --request-changes --body-file -
|
||||
Your multi-line
|
||||
change request with
|
||||
|
||||
proper whitespace formatting
|
||||
EOF
|
||||
|
||||
# Add a comment review (without approval/rejection)
|
||||
gh pr review <PR-number> --comment --body "Your comment message"
|
||||
|
||||
# Add a comment review with proper whitespace
|
||||
cat << EOF | gh pr review <PR-number> --comment --body-file -
|
||||
Your multi-line
|
||||
comment with
|
||||
|
||||
proper whitespace formatting
|
||||
EOF
|
||||
```
|
||||
|
||||
## Additional Commands
|
||||
|
||||
```bash
|
||||
# View PR checks status
|
||||
gh pr checks <PR-number>
|
||||
|
||||
# View PR commits
|
||||
gh pr view <PR-number> --json commits
|
||||
|
||||
# Merge a PR (if you have permission)
|
||||
gh pr merge <PR-number> --merge
|
||||
```
|
||||
|
||||
</common_gh_commands>
|
||||
|
||||
<general_guidelines_for_commenting>
|
||||
When reviewing a PR, please talk normally and like a friendly reviwer. You should keep it short, and start out by thanking the author of the pr and @ mentioning them.
|
||||
|
||||
Whether or not you approve the PR, you should then give a quick summary of the changes without being too verbose or definitive, staying humble like that this is your understanding of the changes. Kind of how I'm talking to you right now.
|
||||
|
||||
If you have any suggestions, or things that need to be changed, request changes instead of approving the PR.
|
||||
|
||||
Leaving inline comments in code is good, but only do so if you have something specific to say about the code. And make sure you leave those comments first, and then request changes in the PR with a short comment explaining the overall theme of what you're asking them to change.
|
||||
</general_guidelines_for_commenting>
|
||||
|
||||
<example_comments_that_i_have_written_before>
|
||||
<brief_approve_comment>
|
||||
Looks good, though we should make this generic for all providers & models at some point
|
||||
</brief_approve_comment>
|
||||
<brief_approve_comment>
|
||||
Will this work for models that may not match across OR/Gemini? Like the thinking models?
|
||||
</brief_approve_comment>
|
||||
<approve_comment>
|
||||
This looks great! I like how you've handled the global endpoint support - adding it to the ModelInfo interface makes total sense since it's just another capability flag, similar to how we handle other model features.
|
||||
|
||||
The filtered model list approach is clean and will be easier to maintain than hardcoding which models work with global endpoints. And bumping the genai library was obviously needed for this to work.
|
||||
|
||||
Thanks for adding the docs about the limitations too - good for users to know they can't use context caches with global endpoints but might get fewer 429 errors.
|
||||
</approve_comment>
|
||||
<requesst_changes_comment>
|
||||
This is awesome. Thanks @scottsus.
|
||||
|
||||
My main concern though - does this work for all the possible VS Code themes? We struggled with this initially which is why it's not super styled currently. Please test and share screenshots with the different themes to make sure before we can merge
|
||||
</request_changes_comment>
|
||||
<request_changes_comment>
|
||||
Hey, the PR looks good overall but I'm concerned about removing those timeouts. Those were probably there for a reason - VSCode's UI can be finicky with timing.
|
||||
|
||||
Could you add back the timeouts after focusing the sidebar? Something like:
|
||||
|
||||
```typescript
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await setTimeoutPromise(100) // Give UI time to update
|
||||
visibleWebview = WebviewProvider.getSidebarInstance()
|
||||
```
|
||||
|
||||
</request_changes_comment>
|
||||
<request_changes_comment>
|
||||
Heya @alejandropta thanks for working on this!
|
||||
|
||||
A few notes:
|
||||
1 - Adding additional info to the environment variables is fairly problematic because env variables get appended to **every single message**. I don't think this is justifiable for a somewhat niche use case.
|
||||
2 - Adding this option to settings to include that could be an option, but we want our options to be simple and straightforward for new users
|
||||
3 - We're working on revisualizing the way our settings page is displayed/organized, and this could potentially be reconciled once that is in and our settings page is more clearly delineated.
|
||||
|
||||
So until the settings page is update, and this is added to settings in a way that's clean and doesn't confuse new users, I don't think we can merge this. Please bear with us.
|
||||
</request_changes_comment>
|
||||
<request_changes_comment>
|
||||
Also, don't forget to add a changeset since this fixes a user-facing bug.
|
||||
|
||||
The architectural change is solid - moving the focus logic to the command handlers makes sense. Just don't want to introduce subtle timing issues by removing those timeouts.
|
||||
</request_changes_comment>
|
||||
</example_comments_that_i_have_written_before>
|
||||
````
|
||||
|
||||
When I get a new PR to review, I used to manually gather context: checking the PR description, examining the diff, looking at surrounding files, and finally forming an opinion. Now I just:
|
||||
|
||||
1. Type `/pr-review.md` in chat
|
||||
2. Paste in the PR number
|
||||
3. Let Cline handle everything else
|
||||
|
||||
My workflow uses the `gh` command-line tool and Cline's built in `ask_followup_question` to:
|
||||
|
||||
- Pull the PR description and comments
|
||||
- Examine the diff
|
||||
- Check surrounding files for context
|
||||
- Analyze potential issues
|
||||
- Asks me if it's cool approve it if everything looks good, with justification for why it should be approved
|
||||
- If I say "yes," Cline automatically approves the PR with the `gh` command
|
||||
|
||||
This has taken my PR review process from a manual, multi-step operation to a single command that gives me everything I need to make an informed decision.
|
||||
|
||||
> This is just one example of a workflow file. You can find more in our [prompts repository](https://github.com/cline/prompts) for inspiration.
|
||||
|
||||
## Building Your Own Workflows
|
||||
|
||||
The beauty of workflows is they're completely customizable to your needs. You might create workflows for all kinds of repetitive tasks:
|
||||
|
||||
- For releases, you could have a workflow that grabs all merged PRs, builds a changelog, and handles version bumps.
|
||||
- Setting up new projects is perfect for workflows. Just run one command to create your folder structure, install dependencies, and set up configs.
|
||||
- Need to create a report? Create a workflow that grabs stats from different sources and formats them exactly how you like. You can even visualize them with a charting library and then make a presentation out of it with a library like [slidev](https://sli.dev/).
|
||||
- You can even use workflows to draft messages to your team using an MCP server like Slack or Whatsapp after you submit a PR.
|
||||
|
||||
With Workflows, your imagination is the limit. The true potential comes from spotting those annoying repetitive tasks you do all the time.
|
||||
|
||||
If you can describe something as "first I do X, then Y, then Z" - that's a perfect workflow candidate.
|
||||
|
||||
Start with something small that bugs you, turn it into a workflow, and keep refining it. You'll be shocked how much of your day can be automated this way.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: "Workflows Best Practices"
|
||||
sidebarTitle: "Best Practices"
|
||||
description: "Tips and strategies for creating effective and reliable Cline workflows."
|
||||
---
|
||||
|
||||
Creating effective workflows requires a balance of clear instructions, modular design, and intelligent tool usage. Follow these best practices to get the most out of Cline's automation capabilities.
|
||||
|
||||
## Use Cline to Build Workflows
|
||||
|
||||
We highly recommend using Cline to help you build your workflows. Since Cline understands your project's context and structure, it can be an invaluable partner in designing automation that fits your specific needs.
|
||||
|
||||
### Building your own workflows
|
||||
|
||||
Creating a workflow is simpler than you might think. There's actually a workflow for building workflows!
|
||||
|
||||
First, **save the [create-new-workflow.md](https://github.com/cline/prompts/blob/main/workflows/create-new-workflow.md) file to your workspace** (e.g., in `.clinerules/workflows/`).
|
||||
|
||||
Then, type `/create-new-workflow.md` and Cline guides you through it:
|
||||
|
||||
1. It asks for the purpose and a concise name.
|
||||
2. You describe the objective and expected outputs.
|
||||
3. You list the major steps (Cline can help determine details).
|
||||
4. It generates the properly structured workflow file.
|
||||
|
||||
<Tip>
|
||||
**Automate Your History:** The best workflows come from tasks you've already done. After completing something you'll need to repeat, tell Cline: "Create a workflow for the process I just completed." It analyzes the conversation, identifies the steps, and generates the workflow file. Your accumulated context becomes reusable automation.
|
||||
</Tip>
|
||||
|
||||
Workflows live in `.clinerules/workflows/` for project-specific ones or `~/Documents/Cline/Workflows/` for global ones you use across projects. Project workflows take precedence when names match.
|
||||
|
||||
## Workflow Design
|
||||
|
||||
<Tip>
|
||||
**Start Simple:** Begin with small, single-task workflows. As you get comfortable, you can combine them or create more complex sequences.
|
||||
</Tip>
|
||||
|
||||
### Be Modular
|
||||
Instead of creating one massive workflow file, break complex tasks into smaller, reusable workflows. This makes them easier to maintain and debug.
|
||||
|
||||
### Use Clear Comments
|
||||
Just like with code, commenting your workflow steps is crucial. Explain *why* a step is happening, not just *what* is happening. This helps both you (the future maintainer) and Cline understand the intent.
|
||||
|
||||
### Version Control
|
||||
Treat your workflows as part of your codebase. Store them in your Git repository (in `.clinerules/workflows/`) so they are versioned, reviewed, and shared with your team.
|
||||
|
||||
## Prompt Engineering for Cline
|
||||
|
||||
### Be Specific with Tool Use
|
||||
Don't just say "find the file." Be explicit about which tool Cline should use.
|
||||
|
||||
* **Bad:** "Find the user controller."
|
||||
* **Good:** "Use `search_files` to look for `UserController` in the `src/controllers` directory."
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Available Tools
|
||||
|
||||
Cline has a powerful set of tools you can use within your workflows. Here are the most common ones:
|
||||
|
||||
#### execute_command
|
||||
Executes a CLI command on your system. Use this for running tests, builds, git commands, or any other terminal operation.
|
||||
|
||||
```xml
|
||||
<execute_command>
|
||||
<command>npm run test</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
</execute_command>
|
||||
```
|
||||
|
||||
#### read_file
|
||||
Reads the contents of a file. Essential for analyzing code or configuration.
|
||||
|
||||
```xml
|
||||
<read_file>
|
||||
<path>src/config.json</path>
|
||||
</read_file>
|
||||
```
|
||||
|
||||
#### write_to_file
|
||||
Creates or overwrites a file. Use this to generate boilerplate, config files, or documentation.
|
||||
|
||||
```xml
|
||||
<write_to_file>
|
||||
<path>src/components/Button.tsx</path>
|
||||
<content>
|
||||
// File content goes here...
|
||||
</content>
|
||||
</write_to_file>
|
||||
```
|
||||
|
||||
#### search_files
|
||||
Searches for a regex pattern across files in a directory. Great for finding TODOs, usage examples, or specific code patterns.
|
||||
|
||||
```xml
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>TODO</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
```
|
||||
|
||||
#### ask_followup_question
|
||||
Asks the user for input or confirmation. This makes your workflow interactive and allows for human-in-the-loop decision making.
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Do you want to deploy to production?</question>
|
||||
<options>["Yes", "No"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
#### browser_action
|
||||
Controls a built-in browser to interact with websites or local servers. Useful for testing web UIs or scraping data.
|
||||
|
||||
```xml
|
||||
<browser_action>
|
||||
<action>launch</action>
|
||||
<url>http://localhost:3000</url>
|
||||
</browser_action>
|
||||
```
|
||||
|
||||
### Leverage MCP Tools
|
||||
You can use Model Context Protocol (MCP) tools within your workflows to interact with external services like GitHub, Slack, or databases. This allows you to create powerful end-to-end automations.
|
||||
|
||||
### Manage Context Window
|
||||
Be mindful of Cline's context window. If a workflow is too long or processes too much data, it might exceed the token limit.
|
||||
* **Break it down:** Split long workflows into smaller parts.
|
||||
* **Be concise:** Keep instructions clear and to the point.
|
||||
|
||||
## Learn More
|
||||
|
||||
<Card title="Cline Learn" icon="lightbulb" href="https://cline.bot/learn">
|
||||
Dive deeper into general prompt engineering strategies to write even better instructions for Cline.
|
||||
</Card>
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: "Workflows Overview"
|
||||
sidebarTitle: "Overview"
|
||||
description: "Learn what Cline workflows are, why they are useful, and how to structure them."
|
||||
---
|
||||
|
||||
Workflows in Cline are Markdown files that define a series of steps to guide Cline through repetitive or complex tasks. They are a powerful way to automate your development processes directly within your editor.
|
||||
|
||||
To invoke a workflow, you simply type `/` followed by the workflow's filename in the chat (e.g., `/deploy.md`).
|
||||
|
||||
## Why Use Cline Workflows?
|
||||
|
||||
* **Automation:** Automate repetitive tasks like setting up a new project, deploying a service, or running a specific test suite.
|
||||
* **Consistency:** Ensure that tasks are performed the same way every time, reducing errors.
|
||||
* **Reduced Cognitive Load:** Don't waste mental energy remembering complex sequences of commands or steps.
|
||||
* **Contextual:** Workflows run within your project's context, so Cline has access to your files and can use its tools to interact with them.
|
||||
|
||||
## How They Work
|
||||
|
||||
A workflow file is a standard Markdown file with a `.md` extension. Cline reads this file and interprets the instructions step-by-step. The real power comes from Cline's ability to use its built-in tools and other capabilities within these instructions:
|
||||
|
||||
* **Cline Tools:** Use tools like `read_file`, `write_to_file`, `execute_command`, and `ask_followup_question`.
|
||||
* **Command-Line Tools:** Instruct Cline to use any CLI tool installed on your machine (e.g., `git`, `gh`, `npm`, `docker`).
|
||||
* **MCP Tools:** Reference tools from connected Model Context Protocol (MCP) servers.
|
||||
|
||||
## Workflows vs. Rules
|
||||
|
||||
It's important to understand the difference between Cline Workflows and Cline Rules, as they serve different purposes:
|
||||
|
||||
| Feature | Purpose | When to Use |
|
||||
| :--- | :--- | :--- |
|
||||
| **Cline Rules** | Define *how* Cline should behave generally. They are always active (or contextually triggered) and set the "ground rules" for your project. | Enforcing coding standards, tech stack preferences, or project-specific constraints (e.g., "Always use TypeScript", "Never edit the `db` folder"). |
|
||||
| **Cline Workflows** | Define *what* specific task Cline should perform. They are sequences of steps invoked on-demand to automate a process. | Automating repetitive tasks like creating a component, running a release process, or generating a daily report. |
|
||||
|
||||
Think of **Rules** as the *environment* Cline works in, and **Workflows** as the *scripts* you give Cline to execute.
|
||||
|
||||
### Example: Automating a Release
|
||||
|
||||
Imagine you need to prepare a new release for your library.
|
||||
|
||||
**Without a workflow**, you might have to manually:
|
||||
1. Open `package.json` and bump the version number.
|
||||
2. Run your test suite to make sure everything is green.
|
||||
3. Update `CHANGELOG.md` with the latest commits.
|
||||
4. Run `git commit -am "v1.0.1"`.
|
||||
5. Run `git tag v1.0.1`.
|
||||
6. Run `git push origin main --tags`.
|
||||
|
||||
This is tedious and easy to mess up. You might forget to run the tests or format the changelog correctly.
|
||||
|
||||
**With a Cline workflow**, you define these steps once in a `release.md` file. Then, you just type:
|
||||
|
||||
```bash
|
||||
/release.md
|
||||
```
|
||||
|
||||
Cline will then meticulously follow your instructions: updating files, running tests, and executing git commands—pausing only if it encounters an error or needs your input.
|
||||
|
||||
## Where are Workflows Stored?
|
||||
|
||||
You can store workflows in two locations, depending on whether they are specific to a project or meant to be global.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Project-Specific Workflows">
|
||||
Store workflows that are specific to a single project in a `.clinerules/workflows/` directory in your project's root.
|
||||
|
||||
1. Create a `.clinerules` folder in your project's root directory (if it doesn't already exist).
|
||||
<Note>
|
||||
The `.clinerules` directory may be hidden by default on some systems. You might need to enable **Show Hidden Files** to see it.
|
||||
</Note>
|
||||
2. Inside `.clinerules`, create a `workflows` folder.
|
||||
3. Create your Markdown workflow files (e.g., `deploy.md`) in this folder.
|
||||
|
||||
These workflows will only be available when you have this specific project open.
|
||||
</Tab>
|
||||
<Tab title="Global Workflows">
|
||||
Store workflows that you want to use across all your projects in a global directory.
|
||||
|
||||
* **macOS/Linux:** `~/Documents/Cline/Workflows/`
|
||||
* **Windows:** `C:\Users\USERNAME\Documents\Cline\Workflows\`
|
||||
|
||||
Create your Markdown workflow files directly in this directory. They will be available in any project you open with Cline.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Manage Workflows
|
||||
|
||||
You can easily manage your workflows directly within the extension. This feature provides a unified interface to handle all your automation needs without leaving your editor or hunting through file directories. It consolidates both project-specific rules and global workflows into one view, giving you full control over your automation environment.
|
||||
|
||||
1. Click the **Manage Cline Rules and Workflows** button (<Icon icon="scale-balanced" />) at the bottom of the extension.
|
||||
2. This opens an interface where you can:
|
||||
* **View all available workflows:** See a comprehensive list of both project-specific and global workflows.
|
||||
* **Control automation:** Toggle individual workflows on and off as needed for your current task.
|
||||
* **Create and Edit:** Add new workflows or modify existing ones directly within the interface.
|
||||
* **Clean up:** Delete workflows you no longer need.
|
||||
|
||||
<Frame caption="Manage Workflows">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/workflow-menu.gif" alt="Manage Cline Rules and Workflows Interface" />
|
||||
</Frame>
|
||||
|
||||
## Workflow Structure Example
|
||||
|
||||
Here is a simple example of a workflow file (`daily-changelog.md`) that helps you create a daily changelog.
|
||||
|
||||
````markdown daily-changelog.md
|
||||
# Daily Changelog Generator
|
||||
|
||||
This workflow helps you create a changelog for your daily work.
|
||||
|
||||
1. **Check your recent git commits:**
|
||||
I will run the following command to see your commits from today.
|
||||
```bash
|
||||
git log --author="$(git config user.name)" --since="yesterday" --oneline
|
||||
```
|
||||
|
||||
2. **Summarize your work:**
|
||||
I will present the commits to you and ask for a summary of your changes to be added to the `changelog.md` file.
|
||||
|
||||
3. **Create/Append to daily changelog:**
|
||||
I will append to the `changelog.md` file. The content will include a header with the current date, the list of commits, and your summary.
|
||||
````
|
||||
|
||||
### Breakdown of the Workflow
|
||||
|
||||
This workflow demonstrates that you don't always need to provide specific tool calls (like XML blocks). Cline is smart enough to interpret your high-level instructions.
|
||||
|
||||
1. **Step 1: Check recent git commits**
|
||||
* We give Cline a specific command to run. This ensures it gets exactly the data we want (today's commits).
|
||||
<Tip>
|
||||
After Cline shows the git commit history, you may need to click the **Proceed While Running** button to allow the workflow to continue.
|
||||
</Tip>
|
||||
|
||||
2. **Step 2: Summarize your work**
|
||||
* Instead of forcing a specific tool, we simply tell Cline what to do: "ask for a summary".
|
||||
* Cline knows it needs to use its capabilities to ask you a question.
|
||||
|
||||
3. **Step 3: Create/Append to daily changelog**
|
||||
* We describe the desired outcome: "append to the `changelog.md` file" with specific content.
|
||||
* Cline figures out how to format the file and use its file-writing tools to accomplish the task.
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: "Workflows Quick Start"
|
||||
sidebarTitle: "Quick Start"
|
||||
description: "A step-by-step guide to creating your first Cline workflow."
|
||||
---
|
||||
|
||||
In this tutorial, you will create a powerful workflow that automates the process of reviewing a GitHub Pull Request. This example demonstrates how to combine CLI tools, file analysis, and user interaction into a seamless process.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* You have Cline installed.
|
||||
* You have the [GitHub CLI (`gh`)](https://cli.github.com/) installed and authenticated.
|
||||
* You have a Git repository open with a Pull Request you want to test this on.
|
||||
|
||||
## Creating a Pull Request Review Workflow
|
||||
|
||||
This workflow will automate the process of fetching PR details, analyzing the code changes for issues, and drafting a review comment.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the Workflow File">
|
||||
First, create the directory structure for your project-specific workflows.
|
||||
|
||||
1. In the root of your project, create a new folder named `.clinerules`.
|
||||
2. Inside `.clinerules`, create another folder named `workflows`.
|
||||
3. Finally, create a new file named `pr-review.md` inside the `workflows` folder.
|
||||
</Step>
|
||||
|
||||
<Step title="Write the Workflow Content">
|
||||
Open the `pr-review.md` file and add the following content. This workflow will gather PR details, analyze the changes, and help you submit a review.
|
||||
|
||||
````markdown pr-review.md
|
||||
# Pull Request Reviewer
|
||||
|
||||
This workflow helps me review a pull request by analyzing the changes and drafting a review.
|
||||
|
||||
## 1. Gather PR Information
|
||||
First, I need to understand what this PR is about. I'll fetch the title, description, and list of changed files.
|
||||
|
||||
```bash
|
||||
gh pr view PR_NUMBER --json title,body,files
|
||||
```
|
||||
|
||||
## 2. Examine Modified Files
|
||||
Now I will examine the diff to understand the specific code changes.
|
||||
|
||||
```bash
|
||||
gh pr diff PR_NUMBER
|
||||
```
|
||||
|
||||
## 3. Analyze Changes
|
||||
I will analyze the code changes for:
|
||||
* **Bugs:** Logic errors or edge cases.
|
||||
* **Performance:** Inefficient loops or operations.
|
||||
* **Security:** Vulnerabilities or unsafe practices.
|
||||
|
||||
## 4. Confirm Assessment
|
||||
Based on my analysis, I will present my findings and ask how you want to proceed.
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>I've reviewed PR #PR_NUMBER. Here is my assessment:
|
||||
|
||||
[Insert Analysis Here]
|
||||
|
||||
Do you want me to approve this PR, request changes, or just leave a comment?</question>
|
||||
<options>["Approve", "Request Changes", "Comment", "Do nothing"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## 5. Execute Review
|
||||
Finally, I will execute the review command based on your decision.
|
||||
|
||||
```bash
|
||||
# If approving:
|
||||
gh pr review PR_NUMBER --approve --body "Looks good to me! [Summary of analysis]"
|
||||
|
||||
# If requesting changes:
|
||||
gh pr review PR_NUMBER --request-changes --body "Please address the following: [Issues list]"
|
||||
|
||||
# If commenting:
|
||||
gh pr review PR_NUMBER --comment --body "[Comments]"
|
||||
```
|
||||
````
|
||||
|
||||
<Note>
|
||||
When you run this workflow, you will replace `PR_NUMBER` with the actual number of the pull request you want to review (e.g., `/pr-review.md 123`).
|
||||
</Note>
|
||||
</Step>
|
||||
|
||||
<Step title="Run the Workflow">
|
||||
Now you're ready to run your new workflow.
|
||||
|
||||
1. Open the Cline chat panel.
|
||||
2. Type `/pr-review.md` followed by the PR number (e.g., `/pr-review.md 42`) and press Enter.
|
||||
3. Cline will fetch the PR details, analyze the code, and present you with its findings before submitting the review.
|
||||
|
||||
<Tip>
|
||||
As Cline executes commands (like `gh pr view`), it may show you the output and pause. You will need to click the **Proceed While Running** button to allow Cline to analyze the content and continue with the workflow.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Other Common Use Cases
|
||||
|
||||
This is just one example. You can create workflows for a wide variety of tasks, such as:
|
||||
|
||||
* **Creating Components:** Automate the boilerplate for new files (like React components or API endpoints).
|
||||
* **Running Tests:** Create a workflow that runs your test suite and summarizes the results.
|
||||
* **Deploying Your Application:** Automate your deployment pipeline using tools like `docker` and `kubectl`.
|
||||
* **Refactoring Code:** Guide Cline through a complex refactoring process step-by-step.
|
||||
|
||||
Explore Cline's capabilities and your own development processes to find repetitive tasks that can be turned into efficient workflows.
|
||||
@@ -29,11 +29,33 @@ The "Remote Servers" tab allows you to connect to any MCP server that's accessib
|
||||
2. Fill in the required information:
|
||||
- **Server Name**: Provide a unique, descriptive name for the server
|
||||
- **Server URL**: Enter the complete URL endpoint of the MCP server (e.g., `https://example.com/mcp-sse`)
|
||||
- **Transport Type**: Select the connection protocol (Streamable HTTP is recommended for modern servers)
|
||||
3. Click "Add Server" to initiate the connection
|
||||
4. Cline will attempt to connect to the server and display the connection status
|
||||
|
||||
> **Note**: When connecting to a remote server, ensure you trust the source, as MCP servers can execute code in your environment.
|
||||
|
||||
#### Transport Types
|
||||
|
||||
Cline supports two transport protocols for remote MCP servers:
|
||||
|
||||
- **Streamable HTTP (Recommended)**: The modern MCP transport protocol with better performance, reliability, and full OAuth 2.1 authentication support. Use this for most remote servers.
|
||||
- **SSE (Legacy)**: Server-Sent Events transport. Use this only if the server specifically requires SSE or doesn't support Streamable HTTP.
|
||||
|
||||
#### OAuth Authentication
|
||||
|
||||
Some MCP servers (like Vercel's MCP) require OAuth authentication to access your data securely. When connecting to an OAuth-enabled server:
|
||||
|
||||
1. Add the server as usual with its URL
|
||||
2. If the server requires authentication, you'll see an error message asking to authenticate.
|
||||
3. Click the **"Authenticate"** button that appears
|
||||
4. Your browser will open to the server's authorization page
|
||||
5. Sign in and grant permission
|
||||
6. You'll be redirected back to Cline automatically
|
||||
7. The server will connect and show a green status dot
|
||||
|
||||
Once authenticated, your credentials are securely stored and the server will reconnect automatically when you reload Cline. You won't need to authenticate again unless you delete the server or your credentials expire.
|
||||
|
||||
### Remote Server Discovery
|
||||
|
||||
If you're looking for MCP servers to connect to, several third-party marketplaces provide directories of available servers with various capabilities.
|
||||
@@ -90,9 +112,20 @@ Toggle the switch next to each server to enable or disable it:
|
||||
If a server fails to connect:
|
||||
|
||||
1. An error message will be displayed with details about the failure
|
||||
2. Check that the server URL is correct and the server is running
|
||||
3. Use the "Restart Server" button to attempt reconnection
|
||||
4. If problems persist, you can delete the server and try adding it again
|
||||
2. **For OAuth errors**: Click the "Authenticate" button to complete the authorization flow
|
||||
3. Check that the server URL is correct and the server is running
|
||||
4. Try selecting a different transport type (Streamable HTTP vs SSE)
|
||||
5. Use the "Restart Server" button to attempt reconnection
|
||||
6. If problems persist, you can delete the server and try adding it again
|
||||
|
||||
#### OAuth-Specific Issues
|
||||
|
||||
If you're having trouble authenticating with an OAuth-enabled server:
|
||||
|
||||
- **"Authentication required" persists**: Make sure you completed the authorization flow in your browser and didn't cancel it
|
||||
- **Browser doesn't open**: Check your system's default browser settings and ensure external URLs can be opened
|
||||
- **Redirect errors**: Verify you're using the latest version of Cline - older versions may not support OAuth
|
||||
- **Reset authentication**: Delete the server and re-add it to start fresh with a new OAuth flow
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
@@ -105,10 +138,11 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
{
|
||||
"mcpServers": {
|
||||
"exampleServer": {
|
||||
"url": "https://example.com/mcp-sse",
|
||||
"url": "https://example.com/mcp-server",
|
||||
"type": "streamableHttp",
|
||||
"disabled": false,
|
||||
"autoApprove": ["tool1", "tool2"],
|
||||
"timeout": 30
|
||||
"timeout": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,9 +151,10 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
Key configuration options:
|
||||
|
||||
- **url**: The endpoint URL (for remote servers)
|
||||
- **type**: Transport protocol - `"streamableHttp"` (recommended) or `"sse"` (legacy)
|
||||
- **disabled**: Whether the server is currently enabled (true/false)
|
||||
- **autoApprove**: List of tool names that don't require confirmation
|
||||
- **timeout**: Maximum time in seconds to wait for server responses
|
||||
- **timeout**: Maximum time in seconds to wait for server responses (default: 60)
|
||||
|
||||
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
|
||||
|
||||
@@ -130,3 +165,20 @@ Once connected, Cline can use the tools and resources provided by the MCP server
|
||||
1. A tool approval prompt will appear (unless auto-approved)
|
||||
2. Review the tool details and parameters before approving
|
||||
3. The tool will execute and return results to Cline
|
||||
|
||||
### Example: Connecting to Vercel MCP
|
||||
|
||||
[Vercel MCP](https://vercel.com/docs/mcp/vercel-mcp) is an OAuth-enabled server that provides tools for managing your Vercel projects and deployments:
|
||||
|
||||
1. Click "Remote Servers" tab
|
||||
2. Enter:
|
||||
- **Server Name**: `vercel`
|
||||
- **Server URL**: `https://mcp.vercel.com`
|
||||
- **Transport Type**: Streamable HTTP (pre-selected)
|
||||
3. Click "Add Server"
|
||||
4. You'll see "Authentication required" - click the **"Authenticate"** button
|
||||
5. Sign in to Vercel in your browser and authorize Cline
|
||||
6. Return to Cline - the server will automatically connect
|
||||
7. Vercel's tools (deploy, logs, projects) are now available to Cline!
|
||||
|
||||
Your Vercel authentication persists across sessions, so you won't need to re-authenticate each time you use Cline.
|
||||
|
||||
Generated
+6
-34
@@ -5146,28 +5146,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -6490,9 +6468,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10235,12 +10213,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@@ -10609,9 +10581,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
|
||||
"integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
|
||||
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0",
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.23"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ description: "Learn how to configure and use Anthropic Claude models with Cline.
|
||||
Cline supports the following Anthropic Claude models:
|
||||
|
||||
- `claude-haiku-4-5-20251001`
|
||||
- `claude-opus-4-5-20251101`
|
||||
- `claude-opus-4-1-20250805`
|
||||
- `claude-opus-4-20250514`
|
||||
- `anthropic/claude-sonnet-4.5` (Recommended)
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Baseten"
|
||||
description: "Learn how to configure and use Baseten's Model APIs with Cline. Access frontier open-source models with enterprise-grade performance, reliability, and competitive pricing."
|
||||
---
|
||||
|
||||
Baseten provides on-demand frontier model APIs designed for production applications, not just experimentation. Built on the Baseten Inference Stack, these APIs deliver enterprise-grade performance and reliability with optimized inference for leading open-source models from OpenAI, DeepSeek, Meta, Moonshot AI, and Alibaba Cloud.
|
||||
Baseten provides on-demand frontier model APIs designed for production applications, not just experimentation. Built on the Baseten Inference Stack, these APIs deliver optimized inference for leading open-source models from OpenAI, DeepSeek, Moonshot AI, and Alibaba Cloud.
|
||||
|
||||
**Website:** [https://www.baseten.co/products/model-apis/](https://www.baseten.co/products/model-apis/)
|
||||
|
||||
@@ -14,13 +14,21 @@ Baseten provides on-demand frontier model APIs designed for production applicati
|
||||
3. **Create a Key:** Generate a new API key. Give it a descriptive name (e.g., "Cline").
|
||||
4. **Copy the Key:** Copy the API key immediately and store it securely.
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "Baseten" from the "API Provider" dropdown.
|
||||
3. **Enter API Key:** Paste your Baseten API key into the "Baseten API Key" field.
|
||||
4. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
**IMPORTANT: For Kimi K2 Thinking:** To use the `moonshotai/Kimi-K2-Thinking` model, you must enable **Native Tool Call (Experimental)** in Cline settings. This setting allows Cline to call tools through their native tool processor and is required for this reasoning model to function properly.
|
||||
|
||||
### Supported Models
|
||||
|
||||
Cline supports all current models under Baseten Model APIs, including:
|
||||
For the most updated pricing, please visit: https://www.baseten.co/products/model-apis/
|
||||
Note: Kimi K2 0711, Llama 4 Maverick, and Llama 4 Scout Model APIs have been deprecated at 5pm PT on October 8th.
|
||||
https://www.baseten.co/resources/changelog/model-api-deprecation-notice-kimi-k2-0711-scout-maverick/
|
||||
|
||||
- `moonshotai/Kimi-K2-Thinking` (Moonshot AI) - Enhanced reasoning capabilities with step-by-step thought processes (262K context) - \$0.60/\$2.50 per 1M tokens
|
||||
- `zai-org/GLM-4.6` (Z AI) - Frontier open model with advanced agentic, reasoning and coding capabilities by Z AI (200k context) \$0.60/\$2.20 per 1M tokens
|
||||
- `moonshotai/Kimi-K2-Instruct-0905` (Moonshot AI) - September update with enhanced capabilities (262K context) - \$0.60/\$2.50 per 1M tokens
|
||||
- `openai/gpt-oss-120b` (OpenAI) - 120B MoE with strong reasoning capabilities (128K context) - \$0.10/\$0.50 per 1M tokens
|
||||
@@ -31,13 +39,6 @@ https://www.baseten.co/resources/changelog/model-api-deprecation-notice-kimi-k2-
|
||||
- `deepseek-ai/DeepSeek-V3.1` - Hybrid reasoning with advanced tool calling (163K context) - \$0.50/\$1.50 per 1M tokens
|
||||
- `deepseek-ai/DeepSeek-V3-0324` - Fast general-purpose with enhanced reasoning (163K context) - \$0.77/\$0.77 per 1M tokens
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "Baseten" from the "API Provider" dropdown.
|
||||
3. **Enter API Key:** Paste your Baseten API key into the "Baseten API Key" field.
|
||||
4. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
### Production-First Architecture
|
||||
|
||||
Baseten's Model APIs are built for production environments with several key advantages:
|
||||
@@ -59,47 +60,17 @@ Baseten's Model APIs are built for production environments with several key adva
|
||||
|
||||
#### Developer Experience
|
||||
- **OpenAI compatible API** - migrate by swapping a single URL
|
||||
- **Drop-in replacement** for closed models with comprehensive observability
|
||||
- **Drop-in replacement** for closed models with comprehensive observability and analytics
|
||||
- **Seamless scaling** from Model APIs to dedicated deployments
|
||||
|
||||
### Special Features
|
||||
|
||||
#### Function Calling & Tool Use
|
||||
All Baseten models support structured outputs, function calling, and tool use as part of the Baseten Inference Stack, making them ideal for agentic applications.
|
||||
|
||||
#### Reasoning Capabilities
|
||||
DeepSeek models offer enhanced reasoning with step-by-step thought processes, while maintaining production-ready performance.
|
||||
|
||||
#### Long Context Support
|
||||
- **Up to 1 million tokens** for Llama 4 models (Maverick and Scout)
|
||||
- **262K tokens** for Qwen3 models
|
||||
- **163K tokens** for DeepSeek models
|
||||
- **Perfect for code repositories** and complex multi-turn conversations
|
||||
|
||||
#### Quantization Optimizations
|
||||
Models are deployed with advanced quantization techniques (fp4, fp8, fp16) for optimal performance while maintaining quality.
|
||||
|
||||
### Migration from Other Providers
|
||||
|
||||
Baseten's OpenAI compatibility makes migration straightforward:
|
||||
|
||||
**From OpenAI:**
|
||||
- Swap `api.openai.com` with `inference.baseten.co/v1`
|
||||
- Keep existing request/response formats
|
||||
- Benefit from significant cost savings
|
||||
|
||||
**From Other Providers:**
|
||||
- Use standard OpenAI SDK format
|
||||
- Maintain existing prompting strategies
|
||||
- Access to newer open-source models
|
||||
All Baseten models support structured outputs, function calling, and tool use as part of the Baseten Inference Stack, making them ideal for agentic applications and coding workflows.
|
||||
|
||||
### Tips and Notes
|
||||
|
||||
- **Model Selection:** Choose models based on your specific use case - reasoning models for complex tasks, coding models for development work, and flagship models for general applications.
|
||||
- **Cost Optimization:** Baseten offers some of the most competitive pricing in the market, especially for open-source models.
|
||||
- **Context Windows:** Take advantage of large context windows (up to 1M tokens) for including substantial codebases and documentation.
|
||||
- **Enterprise Ready:** Baseten is designed for production use with enterprise-grade security, compliance, and reliability.
|
||||
- **Dynamic Model Updates:** Cline automatically fetches the latest model list from Baseten, ensuring access to new models as they're released.
|
||||
- **Dynamic Model Updates:** Cline automatically fetches the latest model list from Baseten, ensuring access to new models as they're released in real time.
|
||||
- **Multi-Cloud Capacity Management (MCM):** Baseten's multi-cloud infrastructure ensures high availability and low latency globally.
|
||||
- **Support:** Baseten provides dedicated support for production deployments and can work with you on dedicated resources as you scale.
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
|
||||
|
||||
Cline supports the following Cerebras models:
|
||||
|
||||
- `qwen-3-coder-480b-free` (Free tier) - High-performance coding model at no cost
|
||||
- `qwen-3-coder-480b` - Flagship 480B parameter coding model
|
||||
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
|
||||
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
|
||||
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
|
||||
- `llama-3.3-70b` - Meta's Llama 3.3 model optimized for speed
|
||||
|
||||
Generated
-1422
File diff suppressed because it is too large
Load Diff
+46
-45
@@ -1,47 +1,48 @@
|
||||
{
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1"
|
||||
}
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+432
-943
File diff suppressed because it is too large
Load Diff
+42
-17
@@ -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.36.1",
|
||||
"version": "3.38.2",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -46,6 +46,15 @@
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"icons": {
|
||||
"cline-icon": {
|
||||
"description": "cline",
|
||||
"default": {
|
||||
"fontPath": "assets/icons/cline-bot.woff",
|
||||
"fontCharacter": "\\e900"
|
||||
}
|
||||
}
|
||||
},
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "ClineWalkthrough",
|
||||
@@ -149,6 +158,12 @@
|
||||
"category": "Cline",
|
||||
"when": "cline.isDevMode"
|
||||
},
|
||||
{
|
||||
"command": "cline.dev.expireMcpOAuthTokens",
|
||||
"title": "Expire MCP OAuth Tokens (for testing)",
|
||||
"category": "Cline",
|
||||
"when": "cline.isDevMode"
|
||||
},
|
||||
{
|
||||
"command": "cline.addToChat",
|
||||
"title": "Add to Cline",
|
||||
@@ -159,6 +174,11 @@
|
||||
"title": "Add to Cline",
|
||||
"category": "Cline"
|
||||
},
|
||||
{
|
||||
"command": "cline.addTerminalCommandToChat",
|
||||
"title": "Add Last Command to Cline",
|
||||
"category": "Cline"
|
||||
},
|
||||
{
|
||||
"command": "cline.focusChatInput",
|
||||
"title": "Jump to Chat Input",
|
||||
@@ -168,10 +188,7 @@
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"title": "Generate Commit Message with Cline",
|
||||
"category": "Cline",
|
||||
"icon": {
|
||||
"light": "assets/icons/robot_panel_light.png",
|
||||
"dark": "assets/icons/robot_panel_dark.png"
|
||||
}
|
||||
"icon": "$(cline-icon)"
|
||||
},
|
||||
{
|
||||
"command": "cline.abortGitCommitMessage",
|
||||
@@ -260,7 +277,12 @@
|
||||
"terminal/context": [
|
||||
{
|
||||
"command": "cline.addTerminalOutputToChat",
|
||||
"group": "navigation"
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "cline.addTerminalCommandToChat",
|
||||
"group": "navigation@2",
|
||||
"when": "cline.terminalHasCommandHistory"
|
||||
}
|
||||
],
|
||||
"scm/title": [
|
||||
@@ -278,11 +300,11 @@
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"when": "config.git.enabled && scmProvider == git && !cline.isGeneratingCommit"
|
||||
"when": "config.git.enabled && !cline.isGeneratingCommit"
|
||||
},
|
||||
{
|
||||
"command": "cline.abortGitCommitMessage",
|
||||
"when": "config.git.enabled && scmProvider == git && cline.isGeneratingCommit"
|
||||
"when": "config.git.enabled && cline.isGeneratingCommit"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -304,6 +326,7 @@
|
||||
"dev:cli:watch": "node scripts/dev-cli-watch.mjs",
|
||||
"postcompile-standalone": "node scripts/package-standalone.mjs",
|
||||
"postcompile-standalone-npm": "node scripts/package-standalone.mjs --target=npm",
|
||||
"dev": "npm run protos && npm run watch",
|
||||
"watch": "npm-run-all -p watch:*",
|
||||
"watch:esbuild": "node esbuild.mjs --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
@@ -330,7 +353,7 @@
|
||||
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
|
||||
"test": "npm-run-all test:unit test:integration",
|
||||
"test:integration": "vscode-test",
|
||||
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha",
|
||||
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha # Use `UPDATE_SNAPSHOTS=true npm run test:unit` to rebuild prompt snapshots",
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"test:sca-server": "npx tsx watch scripts/test-standalone-core-api-server.ts",
|
||||
"test:tp-orchestrator": "npx tsx scripts/testing-platform-orchestrator.ts",
|
||||
@@ -352,7 +375,8 @@
|
||||
"docs": "cd docs && npm run dev",
|
||||
"docs:check-links": "cd docs && npm run check",
|
||||
"docs:rename-file": "cd docs && npm run rename",
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
"report-issue": "node scripts/report-issue.js",
|
||||
"storybook": "cd webview-ui && npm run storybook"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
@@ -410,7 +434,7 @@
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
"@google/genai": "^1.11.0",
|
||||
"@google/genai": "^1.30.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
@@ -435,8 +459,8 @@
|
||||
"@opentelemetry/sdk-trace-node": "^1.30.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.37.0",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sap-ai-sdk/ai-api": "^1.17.0",
|
||||
"@sap-ai-sdk/orchestration": "^1.17.0",
|
||||
"@sap-ai-sdk/ai-api": "^2.1.0",
|
||||
"@sap-ai-sdk/orchestration": "^2.1.0",
|
||||
"@sentry/browser": "^9.12.0",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
@@ -454,7 +478,6 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"execa": "^9.5.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase": "^11.2.0",
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
@@ -464,7 +487,6 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
@@ -472,7 +494,7 @@
|
||||
"ollama": "^0.5.13",
|
||||
"open": "^10.1.2",
|
||||
"open-graph-scraper": "^6.9.0",
|
||||
"openai": "^4.83.0",
|
||||
"openai": "^6.9.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-mutex": "^1.0.0",
|
||||
"p-timeout": "^6.1.4",
|
||||
@@ -497,7 +519,10 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": ">=3.1.1"
|
||||
"tar-fs": ">=3.1.1",
|
||||
"tar": "^7.5.2",
|
||||
"vite": "^7.1.11",
|
||||
"js-yaml": "^4.1.1"
|
||||
},
|
||||
"c8": {
|
||||
"reporter": [
|
||||
|
||||
+93
-4
@@ -49,6 +49,9 @@ service FileService {
|
||||
// Toggle a Windsurf rule (enable or disable)
|
||||
rpc toggleWindsurfRule(ToggleWindsurfRuleRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Toggle an Agents rule (enable or disable)
|
||||
rpc toggleAgentsRule(ToggleAgentsRuleRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Refreshes all rule toggles (Cline, External, and Workflows)
|
||||
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
|
||||
|
||||
@@ -66,6 +69,18 @@ service FileService {
|
||||
|
||||
// Opens or creates a focus chain checklist markdown file for editing
|
||||
rpc openFocusChainFile(StringRequest) returns (Empty);
|
||||
|
||||
// Refreshes all hook toggles (discovers hooks and their enabled state)
|
||||
rpc refreshHooks(EmptyRequest) returns (HooksToggles);
|
||||
|
||||
// Toggles a hook on or off via chmod +x/-x
|
||||
rpc toggleHook(ToggleHookRequest) returns (ToggleHookResponse);
|
||||
|
||||
// Creates a new hook from template
|
||||
rpc createHook(CreateHookRequest) returns (CreateHookResponse);
|
||||
|
||||
// Deletes an existing hook file
|
||||
rpc deleteHook(DeleteHookRequest) returns (DeleteHookResponse);
|
||||
}
|
||||
|
||||
// Response for refreshRules operation
|
||||
@@ -74,8 +89,9 @@ message RefreshedRules {
|
||||
ClineRulesToggles local_cline_rules_toggles = 2;
|
||||
ClineRulesToggles local_cursor_rules_toggles = 3;
|
||||
ClineRulesToggles local_windsurf_rules_toggles = 4;
|
||||
ClineRulesToggles local_workflow_toggles = 5;
|
||||
ClineRulesToggles global_workflow_toggles = 6;
|
||||
ClineRulesToggles local_agents_rules_toggles = 5;
|
||||
ClineRulesToggles local_workflow_toggles = 6;
|
||||
ClineRulesToggles global_workflow_toggles = 7;
|
||||
}
|
||||
|
||||
// Request to toggle a Windsurf rule
|
||||
@@ -85,6 +101,13 @@ message ToggleWindsurfRuleRequest {
|
||||
bool enabled = 3; // Whether to enable or disable the rule
|
||||
}
|
||||
|
||||
// Request to toggle an Agents rule
|
||||
message ToggleAgentsRuleRequest {
|
||||
Metadata metadata = 1;
|
||||
string rule_path = 2; // Path to the rule file
|
||||
bool enabled = 3; // Whether to enable or disable the rule
|
||||
}
|
||||
|
||||
// Request to convert a list of URIs to relative paths
|
||||
message RelativePathsRequest {
|
||||
Metadata metadata = 1;
|
||||
@@ -156,10 +179,17 @@ message RuleFile {
|
||||
bool already_exists = 3; // For createRuleFile, indicates if file already existed
|
||||
}
|
||||
|
||||
// Enum for rule scope (local, global, or remote)
|
||||
enum RuleScope {
|
||||
LOCAL = 0;
|
||||
GLOBAL = 1;
|
||||
REMOTE = 2;
|
||||
}
|
||||
|
||||
// Request to toggle a Cline rule
|
||||
message ToggleClineRuleRequest {
|
||||
Metadata metadata = 1;
|
||||
bool is_global = 2; // Whether this is a global rule or workspace rule
|
||||
RuleScope scope = 2; // Scope of the rule (local, global, or remote)
|
||||
string rule_path = 3; // Path to the rule file
|
||||
bool enabled = 4; // Whether to enable or disable the rule
|
||||
}
|
||||
@@ -173,6 +203,7 @@ message ClineRulesToggles {
|
||||
message ToggleClineRules {
|
||||
ClineRulesToggles global_cline_rules_toggles = 1;
|
||||
ClineRulesToggles local_cline_rules_toggles = 2;
|
||||
ClineRulesToggles remote_rules_toggles = 3;
|
||||
}
|
||||
|
||||
// Request to toggle a Cursor rule
|
||||
@@ -187,5 +218,63 @@ message ToggleWorkflowRequest {
|
||||
Metadata metadata = 1;
|
||||
string workflow_path = 2;
|
||||
bool enabled = 3;
|
||||
bool is_global = 4;
|
||||
RuleScope scope = 4; // Scope of the workflow (local, global, or remote)
|
||||
}
|
||||
|
||||
// Maps from hook name to enabled/disabled status
|
||||
message HookInfo {
|
||||
string name = 1;
|
||||
bool enabled = 2;
|
||||
string absolutePath = 3;
|
||||
}
|
||||
|
||||
message WorkspaceHooks {
|
||||
string workspace_name = 1;
|
||||
repeated HookInfo hooks = 2;
|
||||
}
|
||||
|
||||
message HooksToggles {
|
||||
repeated HookInfo global_hooks = 1;
|
||||
repeated WorkspaceHooks workspace_hooks = 2;
|
||||
bool is_windows = 3; // Whether the system is Windows (toggles disabled)
|
||||
}
|
||||
|
||||
// Request to toggle a hook
|
||||
message ToggleHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook (e.g., "TaskStart")
|
||||
bool is_global = 3; // Whether this is a global or workspace hook
|
||||
bool enabled = 4; // Whether to enable (chmod +x) or disable (chmod -x)
|
||||
optional string workspace_name = 5; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for toggleHook operation
|
||||
message ToggleHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
// Request to create a hook
|
||||
message CreateHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook to create
|
||||
bool is_global = 3; // Whether to create in global or workspace hooks directory
|
||||
optional string workspace_name = 4; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for createHook operation
|
||||
message CreateHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
// Request to delete a hook
|
||||
message DeleteHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook to delete
|
||||
bool is_global = 3; // Whether this is a global or workspace hook
|
||||
optional string workspace_name = 4; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for deleteHook operation
|
||||
message DeleteHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ service McpService {
|
||||
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
|
||||
rpc refreshMcpMarketplace(EmptyRequest) returns (McpMarketplaceCatalog);
|
||||
rpc openMcpSettings(EmptyRequest) returns (Empty);
|
||||
rpc authenticateMcpServer(StringRequest) returns (Empty);
|
||||
|
||||
// Subscribe to MCP marketplace catalog updates
|
||||
rpc subscribeToMcpMarketplaceCatalog(EmptyRequest) returns (stream McpMarketplaceCatalog);
|
||||
@@ -43,6 +44,7 @@ message AddRemoteMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
string server_url = 3;
|
||||
optional string transport_type = 4;
|
||||
}
|
||||
|
||||
message ToggleToolAutoApproveRequest {
|
||||
@@ -91,6 +93,8 @@ message McpServer {
|
||||
repeated McpResourceTemplate resource_templates = 7;
|
||||
optional bool disabled = 8;
|
||||
optional int32 timeout = 9;
|
||||
optional bool oauth_required = 10;
|
||||
optional string oauth_auth_status = 11;
|
||||
}
|
||||
|
||||
message McpServers {
|
||||
|
||||
@@ -97,6 +97,7 @@ message OpenRouterModelInfo {
|
||||
optional bool supports_global_endpoint = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional string name = 13;
|
||||
optional double temperature = 14;
|
||||
}
|
||||
|
||||
// Shared response message for model information
|
||||
@@ -321,7 +322,7 @@ message UpdateApiConfigurationRequestNew {
|
||||
// - "options.openAiHeaders" (for options fields)
|
||||
// - "secrets.apiKey" (for secrets fields)
|
||||
// - "secrets.openRouterApiKey" (for secrets fields)
|
||||
google.protobuf.FieldMask update_mask = 3;
|
||||
repeated string update_mask = 3;
|
||||
}
|
||||
|
||||
// Request for partially updating API configuration using FieldMask
|
||||
@@ -422,6 +423,7 @@ enum ApiProvider {
|
||||
MINIMAX = 36;
|
||||
HICAP = 37;
|
||||
AIHUBMIX = 38;
|
||||
NOUSRESEARCH = 39;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
@@ -546,6 +548,7 @@ message ModelsApiConfiguration {
|
||||
optional string aihubmix_api_key = 82;
|
||||
optional string aihubmix_base_url = 83;
|
||||
optional string aihubmix_app_code = 84;
|
||||
optional string nous_research_api_key = 85;
|
||||
|
||||
// Plan mode configurations
|
||||
optional ApiProvider plan_mode_api_provider = 100;
|
||||
@@ -585,6 +588,8 @@ message ModelsApiConfiguration {
|
||||
optional OpenRouterModelInfo plan_mode_hicap_model_info = 134;
|
||||
optional string plan_mode_aihubmix_model_id = 135;
|
||||
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 136;
|
||||
optional string plan_mode_nous_research_model_id = 137;
|
||||
optional string gemini_plan_mode_thinking_level = 138;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
@@ -624,4 +629,6 @@ message ModelsApiConfiguration {
|
||||
optional OpenRouterModelInfo act_mode_hicap_model_info = 234;
|
||||
optional string act_mode_aihubmix_model_id = 235;
|
||||
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 236;
|
||||
optional string act_mode_nous_research_model_id = 237;
|
||||
optional string gemini_act_mode_thinking_level = 238;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ service StateService {
|
||||
rpc installClineCli(EmptyRequest) returns (Empty);
|
||||
rpc checkCliInstallation(EmptyRequest) returns (Boolean);
|
||||
rpc getProcessInfo(EmptyRequest) returns (ProcessInfo);
|
||||
rpc flushPendingState(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message AutoApprovalActions {
|
||||
@@ -89,6 +90,7 @@ message Secrets {
|
||||
optional string oca_api_key = 37;
|
||||
optional string oca_refresh_token = 38;
|
||||
optional string hicap_api_key = 39;
|
||||
optional string mcp_oauth_secrets = 40;
|
||||
}
|
||||
|
||||
message Settings {
|
||||
@@ -360,6 +362,7 @@ message UpdateSettingsRequest {
|
||||
optional int32 subagent_terminal_output_line_limit = 30;
|
||||
optional string cline_env = 31;
|
||||
optional bool native_tool_call_enabled = 32;
|
||||
optional OnboardingModelGroup onboarding_models = 33;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutRequest {
|
||||
@@ -387,3 +390,17 @@ message OnboardingProgressRequest {
|
||||
optional bool completed = 3;
|
||||
optional string model_selected = 4;
|
||||
}
|
||||
|
||||
message OnboardingModelGroup {
|
||||
repeated OnboardingModel models = 1;
|
||||
}
|
||||
|
||||
message OnboardingModel {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
int32 score = 3;
|
||||
int32 latency = 4;
|
||||
string badge = 5;
|
||||
string group = 6;
|
||||
OpenRouterModelInfo info = 7;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ message TaskResponse {
|
||||
int32 tokens_out = 8;
|
||||
int32 cache_writes = 9;
|
||||
int32 cache_reads = 10;
|
||||
string model_id = 11;
|
||||
}
|
||||
|
||||
// Request for getting task history with filtering
|
||||
@@ -99,6 +100,7 @@ message TaskItem {
|
||||
int32 tokens_out = 8;
|
||||
int32 cache_writes = 9;
|
||||
int32 cache_reads = 10;
|
||||
string model_id = 11;
|
||||
}
|
||||
|
||||
// Request for ask response operation
|
||||
|
||||
@@ -32,6 +32,7 @@ enum ClineAsk {
|
||||
CONDENSE = 13;
|
||||
REPORT_BUG = 14;
|
||||
SUMMARIZE_TASK = 15;
|
||||
ACT_MODE_RESPOND = 16;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
@@ -184,6 +185,11 @@ message ClineApiReqInfo {
|
||||
ApiReqRetryStatus retry_status = 9;
|
||||
}
|
||||
|
||||
message ClineModelInfo {
|
||||
string provider_id = 1;
|
||||
string model_id = 2;
|
||||
}
|
||||
|
||||
// Main ClineMessage type
|
||||
message ClineMessage {
|
||||
int64 ts = 1;
|
||||
@@ -210,6 +216,7 @@ message ClineMessage {
|
||||
ClineAskQuestion ask_question = 20;
|
||||
ClineAskNewTask ask_new_task = 21;
|
||||
ClineApiReqInfo api_req_info = 22;
|
||||
ClineModelInfo model_info = 23;
|
||||
}
|
||||
|
||||
// UiService provides methods for managing UI interactions
|
||||
|
||||
@@ -95,6 +95,7 @@ const ENABLED_PROVIDERS = [
|
||||
"ollama", // Ollama local models
|
||||
"cerebras", // Cerebras models
|
||||
"oca", // Oracle Code Assist
|
||||
"nousResearch", // NousResearch provider
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import fs from "fs"
|
||||
import https from "https"
|
||||
import path from "path"
|
||||
import { pipeline } from "stream/promises"
|
||||
import tar from "tar"
|
||||
import * as tar from "tar"
|
||||
import { promisify } from "util"
|
||||
import { createGunzip } from "zlib"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler } from "../../core/api/index"
|
||||
import { ApiStream } from "../../core/api/transform/stream"
|
||||
@@ -33,7 +33,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -255,7 +255,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
+2
-31
@@ -9,14 +9,6 @@ export interface EnvironmentConfig {
|
||||
appBaseUrl: string
|
||||
apiBaseUrl: string
|
||||
mcpBaseUrl: string
|
||||
firebase: {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
projectId: string
|
||||
storageBucket?: string
|
||||
messagingSenderId?: string
|
||||
appId?: string
|
||||
}
|
||||
}
|
||||
|
||||
class ClineEndpoint {
|
||||
@@ -28,8 +20,8 @@ class ClineEndpoint {
|
||||
private environment: Environment = Environment.production
|
||||
|
||||
private constructor() {
|
||||
// Set environment at module load
|
||||
const _env = process?.env?.CLINE_ENVIRONMENT
|
||||
// Set environment at module load. Use override if provided.
|
||||
const _env = process?.env?.CLINE_ENVIRONMENT_OVERRIDE || process?.env?.CLINE_ENVIRONMENT
|
||||
if (_env && Object.values(Environment).includes(_env as Environment)) {
|
||||
this.environment = _env as Environment
|
||||
return
|
||||
@@ -63,14 +55,6 @@ class ClineEndpoint {
|
||||
appBaseUrl: "https://staging-app.cline.bot",
|
||||
apiBaseUrl: "https://core-api.staging.int.cline.bot",
|
||||
mcpBaseUrl: "https://core-api.staging.int.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",
|
||||
},
|
||||
}
|
||||
case Environment.local:
|
||||
return {
|
||||
@@ -78,11 +62,6 @@ class ClineEndpoint {
|
||||
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",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return {
|
||||
@@ -90,14 +69,6 @@ class ClineEndpoint {
|
||||
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",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,606 @@
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
const APPLY_PATCH_PATCH_REGEX = /\*\*\* Begin Patch\s+([\s\S]*?)\s+\*\*\* End Patch/m
|
||||
|
||||
/**
|
||||
* Convert apply_patch tool calls to write_to_file and replace_in_file format
|
||||
*/
|
||||
export function convertApplyPatchToolCalls(messages: Array<ClineStorageMessage>): Array<ClineStorageMessage> {
|
||||
// Map to track tool_use_id to converted tool info and original input
|
||||
const toolUseIdMap = new Map<string, { name: string; input: any; originalInput: any }>()
|
||||
|
||||
return messages.map((message) => {
|
||||
if (!Array.isArray(message.content)) {
|
||||
return message
|
||||
}
|
||||
|
||||
const convertedContent = message.content.map((block) => {
|
||||
// Handle tool_use blocks
|
||||
if (block.type === "tool_use" && block.name === "apply_patch") {
|
||||
const converted = convertApplyPatchToToolCalls(block.input)
|
||||
// Store the conversion with original input for matching tool_result
|
||||
toolUseIdMap.set(block.id, { ...converted, originalInput: block.input })
|
||||
|
||||
return {
|
||||
...block,
|
||||
name: converted.name,
|
||||
input: converted.input,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tool_result blocks
|
||||
if (block.type === "tool_result") {
|
||||
const conversion = toolUseIdMap.get(block.tool_use_id)
|
||||
if (conversion) {
|
||||
// Reconstruct the tool_result content to match apply_patch format
|
||||
const reconstructedContent = reconstructApplyPatchResult(
|
||||
block,
|
||||
conversion.name,
|
||||
conversion.input,
|
||||
conversion.originalInput,
|
||||
)
|
||||
return {
|
||||
...block,
|
||||
content: reconstructedContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return block
|
||||
})
|
||||
|
||||
return {
|
||||
...message,
|
||||
content: convertedContent,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface ConvertedTool {
|
||||
name: string
|
||||
input: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse apply_patch input and convert to write_to_file or replace_in_file format
|
||||
*/
|
||||
function convertApplyPatchToToolCalls(input: any): ConvertedTool {
|
||||
const patchInput = typeof input === "string" ? input : input?.input || ""
|
||||
|
||||
// Parse the patch format
|
||||
const patchMatch = patchInput.match(APPLY_PATCH_PATCH_REGEX)
|
||||
if (!patchMatch) {
|
||||
// If we can't parse it, return as-is with write_to_file
|
||||
return {
|
||||
name: "write_to_file",
|
||||
input: input,
|
||||
}
|
||||
}
|
||||
|
||||
const patchContent = patchMatch[1]
|
||||
|
||||
// Extract file operation (Add, Update, or Delete)
|
||||
const fileMatch = patchContent.match(/\*\*\* (Add|Update|Delete) File: (.+?)(?:\n|$)/m)
|
||||
if (!fileMatch) {
|
||||
return {
|
||||
name: "write_to_file",
|
||||
input: input,
|
||||
}
|
||||
}
|
||||
|
||||
const action = fileMatch[1]
|
||||
const filePath = fileMatch[2].trim()
|
||||
|
||||
// If it's an Add operation, convert to write_to_file
|
||||
if (action === "Add") {
|
||||
// Extract the content after the file line
|
||||
const contentAfterFile = patchContent.substring(fileMatch.index! + fileMatch[0].length)
|
||||
return {
|
||||
name: "write_to_file",
|
||||
input: {
|
||||
absolutePath: filePath,
|
||||
content: extractNewContentFromPatch(contentAfterFile),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If it's Update or Delete, convert to replace_in_file
|
||||
if (action === "Update" || action === "Delete") {
|
||||
const diff = convertPatchToDiff(patchContent.substring(fileMatch.index! + fileMatch[0].length))
|
||||
return {
|
||||
name: "replace_in_file",
|
||||
input: {
|
||||
absolutePath: filePath,
|
||||
diff: diff,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return {
|
||||
name: "write_to_file",
|
||||
input: input,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract new content from add operation patch
|
||||
*/
|
||||
function extractNewContentFromPatch(patchContent: string): string {
|
||||
// For Add operations, the patch should contain lines starting with +
|
||||
const lines = patchContent.split("\n")
|
||||
const contentLines: string[] = []
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("+")) {
|
||||
// Remove the + prefix and exactly ONE space if present (but not if it's a tab)
|
||||
let content = line.substring(1)
|
||||
if (content.startsWith(" ") && !content.startsWith("\t")) {
|
||||
content = content.substring(1)
|
||||
}
|
||||
contentLines.push(content)
|
||||
}
|
||||
}
|
||||
|
||||
return contentLines.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert V4A patch format to SEARCH/REPLACE format
|
||||
*/
|
||||
function convertPatchToDiff(patchContent: string): string {
|
||||
const diffBlocks: string[] = []
|
||||
const lines = patchContent.split("\n")
|
||||
|
||||
let i = 0
|
||||
while (i < lines.length) {
|
||||
const line = lines[i]
|
||||
|
||||
// Skip empty lines at the start
|
||||
if (!line.trim() && i === 0) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this is the start of a hunk (@@) or a direct change line
|
||||
if (line.trim().startsWith("@@") || line.startsWith("-") || line.startsWith("+")) {
|
||||
const currentSearch: string[] = []
|
||||
const currentReplace: string[] = []
|
||||
|
||||
// Collect @@ context marker lines
|
||||
// @@ prefix marks context lines. If @@something, then "something" is context.
|
||||
// If just @@, then it's an empty context line.
|
||||
while (i < lines.length && lines[i].trim().startsWith("@@")) {
|
||||
const trimmedLine = lines[i].trim()
|
||||
// Extract the actual context content after @@
|
||||
const contextLine = trimmedLine.substring(2)
|
||||
// Always add the context line (even if empty)
|
||||
currentSearch.push(contextLine)
|
||||
currentReplace.push(contextLine)
|
||||
i++
|
||||
}
|
||||
|
||||
if (i >= lines.length) {
|
||||
break
|
||||
}
|
||||
|
||||
// Collect all remaining lines in this hunk until we hit end of content or next @@
|
||||
const hunkLines: string[] = []
|
||||
while (i < lines.length) {
|
||||
// Check if this is a new hunk (starts with @@)
|
||||
if (lines[i].trim().startsWith("@@")) {
|
||||
break
|
||||
}
|
||||
hunkLines.push(lines[i])
|
||||
i++
|
||||
}
|
||||
|
||||
// Now process the hunk to build SEARCH/REPLACE
|
||||
let hasChanges = false
|
||||
for (let j = 0; j < hunkLines.length; j++) {
|
||||
const hunkLine = hunkLines[j]
|
||||
|
||||
if (hunkLine.startsWith("-")) {
|
||||
hasChanges = true
|
||||
// Strip the - prefix and exactly ONE space if present (but not if it's a tab)
|
||||
let content = hunkLine.substring(1)
|
||||
if (content.startsWith(" ") && !content.startsWith(" \t")) {
|
||||
content = content.substring(1)
|
||||
}
|
||||
currentSearch.push(content)
|
||||
} else if (hunkLine.startsWith("+")) {
|
||||
hasChanges = true
|
||||
// Strip the + prefix and exactly ONE space if present (but not if it's a tab)
|
||||
let content = hunkLine.substring(1)
|
||||
if (content.startsWith(" ") && !content.startsWith(" \t")) {
|
||||
content = content.substring(1)
|
||||
}
|
||||
currentReplace.push(content)
|
||||
} else {
|
||||
// Context line without @@ prefix - add to both sides
|
||||
currentSearch.push(hunkLine)
|
||||
currentReplace.push(hunkLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the diff block if we have changes
|
||||
if (hasChanges && (currentSearch.length > 0 || currentReplace.length > 0)) {
|
||||
diffBlocks.push(
|
||||
"------- SEARCH\n" +
|
||||
currentSearch.join("\n") +
|
||||
"\n=======\n" +
|
||||
currentReplace.join("\n") +
|
||||
"\n+++++++ REPLACE",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return diffBlocks.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruct tool_result content to match apply_patch format by extracting
|
||||
* the final file content and converting it back to V4A patch format
|
||||
*/
|
||||
function reconstructApplyPatchResult(
|
||||
block: any,
|
||||
convertedToolName: string,
|
||||
_convertedInput: any,
|
||||
originalInput: any,
|
||||
): string | any[] {
|
||||
// Extract the content from the tool_result
|
||||
const content = typeof block.content === "string" ? block.content : ""
|
||||
|
||||
// Try to extract the final_file_content
|
||||
const finalContentMatch = content.match(/<final_file_content path="([^"]+)">\s*([\s\S]*?)\s*<\/final_file_content>/)
|
||||
|
||||
if (!finalContentMatch) {
|
||||
// If no final_file_content found, return original content
|
||||
return block.content
|
||||
}
|
||||
|
||||
const filePath = finalContentMatch[1]
|
||||
const finalContent = finalContentMatch[2]
|
||||
|
||||
// Reconstruct the result message based on the converted tool type
|
||||
if (convertedToolName === "write_to_file") {
|
||||
// For write_to_file, we just need to confirm the file was created/written
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully saved to ${filePath}.\n\nThe file has been created/updated with the new content.`
|
||||
}
|
||||
|
||||
if (convertedToolName === "replace_in_file") {
|
||||
// For replace_in_file, we need to reconstruct the V4A patch format result
|
||||
// Try to parse the original patch to get the action and build context
|
||||
const patchInput = typeof originalInput === "string" ? originalInput : originalInput?.input || ""
|
||||
const patchMatch = patchInput.match(APPLY_PATCH_PATCH_REGEX)
|
||||
|
||||
if (patchMatch) {
|
||||
const patchContent = patchMatch[1]
|
||||
const fileMatch = patchContent.match(/\*\*\* (Add|Update|Delete) File: (.+?)(?:\n|$)/m)
|
||||
|
||||
if (fileMatch) {
|
||||
const action = fileMatch[1]
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully updated in ${filePath}.\n\nThe file has been modified using ${action} operation.\n\n<final_file_content path="${filePath}">\n${finalContent}\n</final_file_content>\n\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.`
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for replace_in_file
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully updated in ${filePath}.\n\n<final_file_content path="${filePath}">\n${finalContent}\n</final_file_content>\n\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.`
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return block.content
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert write_to_file and replace_in_file tool calls to apply_patch format
|
||||
*/
|
||||
export function convertWriteToFileToolCalls(messages: Array<ClineStorageMessage>): Array<ClineStorageMessage> {
|
||||
// Map to track tool_use_id to converted tool info and original input
|
||||
const toolUseIdMap = new Map<string, { originalName: string; originalInput: any; patchInput?: string }>()
|
||||
|
||||
// First pass: collect tool_use blocks
|
||||
for (const message of messages) {
|
||||
if (!Array.isArray(message.content)) {
|
||||
continue
|
||||
}
|
||||
for (const block of message.content) {
|
||||
if (block.type === "tool_use" && (block.name === "write_to_file" || block.name === "replace_in_file")) {
|
||||
toolUseIdMap.set(block.id, {
|
||||
originalName: block.name,
|
||||
originalInput: block.input,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: find tool_results and extract final content to build proper patches
|
||||
const finalContentMap = new Map<string, string>()
|
||||
for (const message of messages) {
|
||||
if (!Array.isArray(message.content)) {
|
||||
continue
|
||||
}
|
||||
for (const block of message.content) {
|
||||
if (block.type === "tool_result" && toolUseIdMap.has(block.tool_use_id)) {
|
||||
const content = typeof block.content === "string" ? block.content : ""
|
||||
const finalContentMatch = content.match(
|
||||
/<final_file_content path="([^"]+)">\s*([\s\S]*?)\s*<\/final_file_content>/,
|
||||
)
|
||||
if (finalContentMatch) {
|
||||
finalContentMap.set(block.tool_use_id, finalContentMatch[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third pass: convert messages
|
||||
return messages.map((message) => {
|
||||
if (!Array.isArray(message.content)) {
|
||||
return message
|
||||
}
|
||||
|
||||
const convertedContent = message.content.map((block) => {
|
||||
// Handle tool_use blocks for write_to_file and replace_in_file
|
||||
if (block.type === "tool_use" && (block.name === "write_to_file" || block.name === "replace_in_file")) {
|
||||
const finalContent = finalContentMap.get(block.id)
|
||||
const patchInput = convertToPatchFormat(block.name, block.input, finalContent)
|
||||
|
||||
// Update the map with the generated patch
|
||||
const existingEntry = toolUseIdMap.get(block.id)
|
||||
if (existingEntry) {
|
||||
existingEntry.patchInput = patchInput
|
||||
}
|
||||
|
||||
return {
|
||||
...block,
|
||||
name: "apply_patch",
|
||||
input: {
|
||||
input: patchInput,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tool_result blocks
|
||||
if (block.type === "tool_result") {
|
||||
const conversion = toolUseIdMap.get(block.tool_use_id)
|
||||
if (conversion) {
|
||||
// Reconstruct the tool_result content to match apply_patch format
|
||||
const reconstructedContent = reconstructWriteToFileResult(
|
||||
block,
|
||||
conversion.originalName,
|
||||
conversion.originalInput,
|
||||
)
|
||||
return {
|
||||
...block,
|
||||
content: reconstructedContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return block
|
||||
})
|
||||
|
||||
return {
|
||||
...message,
|
||||
content: convertedContent,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert write_to_file or replace_in_file input to apply_patch format
|
||||
*/
|
||||
function convertToPatchFormat(toolName: string, input: any, finalContent?: string): string {
|
||||
const filePath = input.absolutePath || input.path || ""
|
||||
|
||||
if (toolName === "write_to_file") {
|
||||
// Convert write_to_file to Add operation
|
||||
const content = input.content || ""
|
||||
const lines = content.split("\n")
|
||||
const patchLines = ["@@"]
|
||||
patchLines.push(...lines.map((line: string) => `+ ${line}`))
|
||||
|
||||
return `apply_patch <<"EOF"
|
||||
*** Begin Patch
|
||||
*** Add File: ${filePath}
|
||||
${patchLines.join("\n")}
|
||||
*** End Patch
|
||||
EOF`
|
||||
}
|
||||
|
||||
if (toolName === "replace_in_file") {
|
||||
// Convert replace_in_file to Update operation
|
||||
const diff = input.diff || ""
|
||||
|
||||
// Parse SEARCH/REPLACE blocks and convert to V4A format with context
|
||||
const patchContent = convertDiffToPatchWithContext(diff, finalContent)
|
||||
|
||||
return `apply_patch <<"EOF"
|
||||
*** Begin Patch
|
||||
*** Update File: ${filePath}
|
||||
${patchContent}
|
||||
*** End Patch
|
||||
EOF`
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert SEARCH/REPLACE diff format to V4A patch format with additional context from final content
|
||||
*/
|
||||
function convertDiffToPatchWithContext(diff: string, finalContent?: string): string {
|
||||
const patchLines: string[] = []
|
||||
|
||||
// Match all SEARCH/REPLACE blocks
|
||||
const blockRegex = /------- SEARCH\s*\n([\s\S]*?)\n=======\s*\n([\s\S]*?)\n\+{7} REPLACE/g
|
||||
let match
|
||||
|
||||
while ((match = blockRegex.exec(diff)) !== null) {
|
||||
const searchContent = match[1]
|
||||
const replaceContent = match[2]
|
||||
|
||||
const searchLines = searchContent.split("\n")
|
||||
const replaceLines = replaceContent.split("\n")
|
||||
|
||||
// Find common prefix and suffix between search and replace
|
||||
let prefixEnd = 0
|
||||
while (
|
||||
prefixEnd < searchLines.length &&
|
||||
prefixEnd < replaceLines.length &&
|
||||
searchLines[prefixEnd] === replaceLines[prefixEnd]
|
||||
) {
|
||||
prefixEnd++
|
||||
}
|
||||
|
||||
let suffixStart = searchLines.length
|
||||
let replaceSuffixStart = replaceLines.length
|
||||
while (
|
||||
suffixStart > prefixEnd &&
|
||||
replaceSuffixStart > prefixEnd &&
|
||||
searchLines[suffixStart - 1] === replaceLines[replaceSuffixStart - 1]
|
||||
) {
|
||||
suffixStart--
|
||||
replaceSuffixStart--
|
||||
}
|
||||
|
||||
// If we have finalContent, extract additional context from it
|
||||
if (finalContent) {
|
||||
const finalLines = finalContent.split("\n")
|
||||
|
||||
// Find where the replaced content appears in the final file
|
||||
let matchIndex = -1
|
||||
for (let i = 0; i < finalLines.length; i++) {
|
||||
// Try to match the first replace line
|
||||
if (replaceLines.length > 0 && finalLines[i] === replaceLines[0]) {
|
||||
// Check if subsequent lines also match
|
||||
let allMatch = true
|
||||
for (let j = 1; j < replaceLines.length && i + j < finalLines.length; j++) {
|
||||
if (finalLines[i + j] !== replaceLines[j]) {
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allMatch) {
|
||||
matchIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchIndex >= 0) {
|
||||
// Extract up to 3 lines before as context
|
||||
const contextStart = Math.max(0, matchIndex - 3)
|
||||
const contextLines: string[] = []
|
||||
for (let i = contextStart; i < matchIndex; i++) {
|
||||
contextLines.push(finalLines[i])
|
||||
}
|
||||
|
||||
// Pad to 3 lines if needed (with empty strings)
|
||||
while (contextLines.length < 3) {
|
||||
contextLines.unshift("")
|
||||
}
|
||||
|
||||
// Add @@ marker with the first context line
|
||||
if (contextLines[0] === "") {
|
||||
patchLines.push("@@")
|
||||
} else {
|
||||
patchLines.push(`@@${contextLines[0]}`)
|
||||
}
|
||||
|
||||
// Add remaining context lines (without @@ marker)
|
||||
for (let i = 1; i < contextLines.length; i++) {
|
||||
patchLines.push(contextLines[i])
|
||||
}
|
||||
|
||||
// Add common prefix lines (without +/- markers)
|
||||
for (let i = 0; i < prefixEnd; i++) {
|
||||
patchLines.push(searchLines[i])
|
||||
}
|
||||
|
||||
// Add the actual changes (lines that differ)
|
||||
for (let i = prefixEnd; i < suffixStart; i++) {
|
||||
patchLines.push(`- ${searchLines[i]}`)
|
||||
}
|
||||
for (let i = prefixEnd; i < replaceSuffixStart; i++) {
|
||||
patchLines.push(`+ ${replaceLines[i]}`)
|
||||
}
|
||||
|
||||
// Add common suffix lines (without +/- markers)
|
||||
for (let i = suffixStart; i < searchLines.length; i++) {
|
||||
patchLines.push(searchLines[i])
|
||||
}
|
||||
|
||||
// Extract up to 3 lines after as trailing context (without @@ markers)
|
||||
const contextEnd = Math.min(finalLines.length, matchIndex + replaceLines.length + 3)
|
||||
for (let i = matchIndex + replaceLines.length; i < contextEnd; i++) {
|
||||
patchLines.push(finalLines[i])
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if no finalContent or couldn't find match, use the prefix/suffix from SEARCH/REPLACE
|
||||
patchLines.push("@@")
|
||||
|
||||
// Add common prefix lines (without +/- markers)
|
||||
for (let i = 0; i < prefixEnd; i++) {
|
||||
patchLines.push(searchLines[i])
|
||||
}
|
||||
|
||||
// Add the actual changes (lines that differ)
|
||||
for (let i = prefixEnd; i < suffixStart; i++) {
|
||||
patchLines.push(`- ${searchLines[i]}`)
|
||||
}
|
||||
for (let i = prefixEnd; i < replaceSuffixStart; i++) {
|
||||
patchLines.push(`+ ${replaceLines[i]}`)
|
||||
}
|
||||
|
||||
// Add common suffix lines (without +/- markers)
|
||||
for (let i = suffixStart; i < searchLines.length; i++) {
|
||||
patchLines.push(searchLines[i])
|
||||
}
|
||||
}
|
||||
|
||||
return patchLines.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruct tool_result content to match apply_patch result format
|
||||
*/
|
||||
function reconstructWriteToFileResult(block: any, originalToolName: string, originalInput: any): string | any[] {
|
||||
// Extract the content from the tool_result
|
||||
const content = typeof block.content === "string" ? block.content : ""
|
||||
|
||||
// Try to extract the final_file_content
|
||||
const finalContentMatch = content.match(/<final_file_content path="([^"]+)">\s*([\s\S]*?)\s*<\/final_file_content>/)
|
||||
|
||||
const filePath = originalInput.absolutePath || originalInput.path || ""
|
||||
|
||||
if (!finalContentMatch) {
|
||||
// If no final_file_content found, create a simple success message
|
||||
if (originalToolName === "write_to_file") {
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully saved to ${filePath}.\n\nThe file has been created/updated with the new content.`
|
||||
} else {
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully updated in ${filePath}.\n\nThe file has been modified.`
|
||||
}
|
||||
}
|
||||
|
||||
const finalContent = finalContentMatch[2]
|
||||
|
||||
// Reconstruct the result message based on the original tool type
|
||||
if (originalToolName === "write_to_file") {
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully saved to ${filePath}.\n\nThe file has been created/updated with the new content.`
|
||||
}
|
||||
|
||||
if (originalToolName === "replace_in_file") {
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully updated in ${filePath}.\n\nThe file has been modified using Update operation.\n\n<final_file_content path="${filePath}">\n${finalContent}\n</final_file_content>\n\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.`
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return block.content
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ClineDefaultTool } from "@/shared/tools"
|
||||
import { convertApplyPatchToolCalls, convertWriteToFileToolCalls } from "./diff-editors"
|
||||
|
||||
/**
|
||||
* Transforms tool call messages between different tool formats based on native tool support.
|
||||
* Converts between apply_patch and write_to_file/replace_in_file formats as needed.
|
||||
*
|
||||
* @param clineMessages - Array of messages containing tool calls to transform
|
||||
* @param nativeTools - Array of tools natively supported by the current provider
|
||||
* @returns Transformed messages array, or original if no transformation needed
|
||||
*/
|
||||
export function transformToolCallMessages(
|
||||
clineMessages: ClineStorageMessage[],
|
||||
nativeTools?: ClineDefaultTool[],
|
||||
): ClineStorageMessage[] {
|
||||
// Early return if no messages or native tools provided
|
||||
if (!clineMessages?.length || !nativeTools?.length) {
|
||||
return clineMessages
|
||||
}
|
||||
|
||||
// Create Sets for O(1) lookup performance
|
||||
const nativeToolSet = new Set(nativeTools)
|
||||
const usedToolSet = new Set<string>()
|
||||
|
||||
// Single pass: collect all tools used in assistant messages
|
||||
for (const msg of clineMessages) {
|
||||
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||
for (const block of msg.content) {
|
||||
if (block.type === "tool_use" && block.name) {
|
||||
usedToolSet.add(block.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Early return if no tools were used
|
||||
if (usedToolSet.size === 0) {
|
||||
return clineMessages
|
||||
}
|
||||
|
||||
// Determine which conversion to apply
|
||||
const hasApplyPatchNative = nativeToolSet.has(ClineDefaultTool.APPLY_PATCH)
|
||||
const hasFileEditNative = nativeToolSet.has(ClineDefaultTool.FILE_EDIT) || nativeToolSet.has(ClineDefaultTool.FILE_NEW)
|
||||
|
||||
const hasApplyPatchUsed = usedToolSet.has(ClineDefaultTool.APPLY_PATCH)
|
||||
const hasFileEditUsed = usedToolSet.has(ClineDefaultTool.FILE_EDIT) || usedToolSet.has(ClineDefaultTool.FILE_NEW)
|
||||
|
||||
// Convert write_to_file/replace_in_file → apply_patch
|
||||
if (hasApplyPatchNative && hasFileEditUsed) {
|
||||
return convertWriteToFileToolCalls(clineMessages)
|
||||
}
|
||||
|
||||
// Convert apply_patch → write_to_file/replace_in_file
|
||||
if (hasFileEditNative && hasApplyPatchUsed) {
|
||||
return convertApplyPatchToolCalls(clineMessages)
|
||||
}
|
||||
|
||||
return clineMessages
|
||||
}
|
||||
+12
-3
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { AIhubmixHandler } from "./providers/aihubmix"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
@@ -25,6 +25,7 @@ import { MinimaxHandler } from "./providers/minimax"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { MoonshotHandler } from "./providers/moonshot"
|
||||
import { NebiusHandler } from "./providers/nebius"
|
||||
import { NousResearchHandler } from "./providers/nousresearch"
|
||||
import { OcaHandler } from "./providers/oca"
|
||||
import { OllamaHandler } from "./providers/ollama"
|
||||
import { OpenAiHandler } from "./providers/openai"
|
||||
@@ -46,9 +47,8 @@ import { ApiStream, ApiStreamUsageChunk } from "./transform/stream"
|
||||
export type CommonApiHandlerOptions = {
|
||||
onRetryAttempt?: ApiConfiguration["onRetryAttempt"]
|
||||
}
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream
|
||||
createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[], useResponseApi?: boolean): ApiStream
|
||||
getModel(): ApiHandlerModel
|
||||
getApiStreamUsage?(): Promise<ApiStreamUsageChunk | undefined>
|
||||
}
|
||||
@@ -94,6 +94,7 @@ function createHandlerForProvider(
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
geminiThinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
})
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler({
|
||||
@@ -166,6 +167,7 @@ function createHandlerForProvider(
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
thinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
ulid: options.ulid,
|
||||
})
|
||||
@@ -250,6 +252,7 @@ function createHandlerForProvider(
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
geminiThinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
})
|
||||
case "litellm":
|
||||
return new LiteLlmHandler({
|
||||
@@ -416,6 +419,12 @@ function createHandlerForProvider(
|
||||
hicapApiKey: options.hicapApiKey,
|
||||
hicapModelId: mode === "plan" ? options.planModeHicapModelId : options.actModeHicapModelId,
|
||||
})
|
||||
case "nousResearch":
|
||||
return new NousResearchHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
nousResearchApiKey: options.nousResearchApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeNousResearchModelId : options.actModeNousResearchModelId,
|
||||
})
|
||||
default:
|
||||
return new AnthropicHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import sinon from "sinon"
|
||||
import "should"
|
||||
import { ClaudeCodeHandler } from "@core/api/providers/claude-code"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
describe("ClaudeCodeHandler", () => {
|
||||
let handler: ClaudeCodeHandler
|
||||
@@ -71,7 +71,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -140,7 +140,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { LiteLlmHandler, type LiteLlmModelInfoResponse } from "@core/api/providers/litellm"
|
||||
import { convertToOpenAiMessages } from "@core/api/transform/openai-format"
|
||||
import { expect } from "chai"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
const fakeClient = {
|
||||
@@ -109,7 +109,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("sends the system prompt and messages with the openai format", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
@@ -161,7 +161,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("inserts the cache control in the system prompt and the last two user messages", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, before, beforeEach, describe, it } from "mocha"
|
||||
import "should"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
|
||||
describe("OllamaHandler", () => {
|
||||
@@ -59,7 +59,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
const usageInfo = []
|
||||
@@ -114,7 +114,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
// Start the request and catch the error
|
||||
let errorMessage = ""
|
||||
@@ -158,7 +158,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
@@ -204,7 +204,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { AnthropicModelId, anthropicDefaultModelId, anthropicModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
@@ -43,7 +43,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: AnthropicTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
const model = this.getModel()
|
||||
@@ -70,23 +70,12 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "claude-3-7-sonnet-20250219":
|
||||
case "claude-3-5-sonnet-20241022":
|
||||
case "claude-3-5-haiku-20241022":
|
||||
case "claude-opus-4-5-20251101":
|
||||
case "claude-opus-4-20250514":
|
||||
case "claude-opus-4-1-20250805":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307": {
|
||||
/*
|
||||
The latest message will be the new user message, one before will be the assistant message from a previous request, and the user message before that will be a previously cached user message. So we need to mark the latest user message as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server know the last message to retrieve from the cache for the current request..
|
||||
*/
|
||||
const userMsgIndices = messages.reduce((acc, msg, index) => {
|
||||
if (msg.role === "user") {
|
||||
acc.push(index)
|
||||
}
|
||||
return acc
|
||||
}, [] as number[])
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex)
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, true)
|
||||
|
||||
stream = await client.messages.create(
|
||||
{
|
||||
@@ -106,7 +95,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
messages: anthropicMessages,
|
||||
// tools, // cache breakpoints go from tools > system > messages, and since tools dont change, we can just set the breakpoint at the end of system (this avoids having to set a breakpoint at the end of tools which by itself does not meet min requirements for haiku caching)
|
||||
stream: true,
|
||||
tools: nativeToolsOn ? (tools as AnthropicTool[]) : undefined,
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
// tool_choice options:
|
||||
// - none: disables tool use, even if tools are provided. Claude will not call any tools.
|
||||
// - auto: allows Claude to decide whether to call any provided tools or not. This is the default value when tools are provided.
|
||||
@@ -135,29 +124,30 @@ export class AnthropicHandler implements ApiHandler {
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
// tools,
|
||||
// tool_choice: { type: "auto" },
|
||||
messages: sanitizeAnthropicMessages(messages, false),
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
tool_choice: { type: "auto" },
|
||||
stream: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let thinkingDeltaAccumulator = ""
|
||||
const lastStartedToolCall = { id: "", name: "", arguments: "" }
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: usage.input_tokens || 0,
|
||||
outputTokens: usage.output_tokens || 0,
|
||||
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
{
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: usage.input_tokens || 0,
|
||||
outputTokens: usage.output_tokens || 0,
|
||||
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
}
|
||||
break
|
||||
case "message_delta":
|
||||
@@ -178,15 +168,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
}
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
}
|
||||
signature: chunk.content_block.signature,
|
||||
}
|
||||
break
|
||||
case "redacted_thinking":
|
||||
@@ -194,10 +176,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
redacted_data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
@@ -231,15 +210,14 @@ export class AnthropicHandler implements ApiHandler {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
}
|
||||
thinkingDeltaAccumulator += chunk.delta.thinking
|
||||
break
|
||||
case "signature_delta":
|
||||
// It's used when sending the thinking block back to the API
|
||||
// API expects this in completed form, not as array of deltas
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
if (chunk.delta.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
type: "reasoning",
|
||||
reasoning: "", // reasoning text is already sent via thinking_delta
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -47,7 +47,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
try {
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
interface BasetenHandlerOptions extends CommonApiHandlerOptions {
|
||||
basetenApiKey?: string
|
||||
@@ -98,10 +100,11 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const maxTokens = this.getOptimalMaxTokens(model)
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
@@ -115,21 +118,22 @@ export class BasetenHandler implements ApiHandler {
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
tools,
|
||||
tool_choice: tools && tools.length > 0 ? "auto" : undefined,
|
||||
})
|
||||
|
||||
let didOutputUsage = false
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
const delta = chunk?.choices?.[0]?.delta
|
||||
|
||||
// Handle reasoning field if present (for reasoning models with parsed output)
|
||||
if ((delta as any)?.reasoning) {
|
||||
const reasoningContent = (delta as any).reasoning as string
|
||||
if (delta && "reasoning" in delta && delta?.reasoning) {
|
||||
const reasoning = typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning)
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningContent,
|
||||
reasoning,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle content field
|
||||
@@ -140,6 +144,10 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
// Handle usage information - only output once
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Import proper AWS SDK types
|
||||
import type { ContentBlock, Message } from "@aws-sdk/client-bedrock-runtime"
|
||||
import {
|
||||
@@ -12,6 +11,7 @@ import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI, calculateApiCostQwen } from "@utils/cost"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
@@ -121,7 +121,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ maxRetries: 4 })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
const rawModelId = await this.getModelId()
|
||||
|
||||
@@ -342,7 +342,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createDeepseekMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -480,7 +480,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* First uses convertToR1Format to merge consecutive messages with the same role,
|
||||
* then converts to the string format that DeepSeek R1 expects
|
||||
*/
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// First use convertToR1Format to merge consecutive messages with the same role
|
||||
const r1Messages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
|
||||
@@ -513,7 +513,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Estimates token count based on text length (approximate)
|
||||
* Note: This is a rough estimation, as the actual token count depends on the tokenizer
|
||||
*/
|
||||
private estimateInputTokens(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): number {
|
||||
private estimateInputTokens(systemPrompt: string, messages: ClineStorageMessage[]): number {
|
||||
// For Deepseek R1, we estimate the token count of the formatted prompt
|
||||
// The formatted prompt includes special tokens and consistent formatting
|
||||
const formattedPrompt = this.formatDeepseekR1Prompt(systemPrompt, messages)
|
||||
@@ -680,11 +680,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing Converse API response:", error)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Failed to process response: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,9 +699,20 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
text: `[ERROR] Model stream error: ${chunk.modelStreamErrorException.message}`,
|
||||
}
|
||||
} else if (chunk.validationException) {
|
||||
// Check if this is a context window error - if so, throw it
|
||||
// so the retry mechanism can handle truncation
|
||||
const message = chunk.validationException.message || ""
|
||||
const isContextError = /input.*too long|context.*exceed|maximum.*token|input length.*max.*tokens/i.test(message)
|
||||
|
||||
if (isContextError) {
|
||||
// Throw as exception so context management can handle it
|
||||
throw chunk.validationException
|
||||
}
|
||||
|
||||
// Otherwise yield as error text
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Validation error: ${chunk.validationException.message}`,
|
||||
text: `[ERROR] Validation error: ${message}`,
|
||||
}
|
||||
} else if (chunk.throttlingException) {
|
||||
yield {
|
||||
@@ -779,7 +786,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createAnthropicMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
enable1mContextWindow: boolean,
|
||||
@@ -835,7 +842,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Formats messages for models using the Converse API specification
|
||||
* Used by both Anthropic and Nova models to avoid code duplication
|
||||
*/
|
||||
private formatMessagesForConverseAPI(messages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
private formatMessagesForConverseAPI(messages: ClineStorageMessage[]): Message[] {
|
||||
return messages.map((message) => {
|
||||
// Determine role (user or assistant)
|
||||
const role = message.role === "user" ? ConversationRole.USER : ConversationRole.ASSISTANT
|
||||
@@ -968,7 +975,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createNovaMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1008,7 +1015,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createOpenAIMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1143,7 +1150,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createQwenMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { CerebrasModelId, cerebrasDefaultModelId, cerebrasModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -46,7 +46,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
baseDelay: 5000, // Start with 5 second delay
|
||||
maxDelay: 60000, // Allow up to 60 second delays to respect rate limits
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
// Convert Anthropic messages to Cerebras format
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { filterMessagesForClaudeCode } from "@/integrations/claude-code/message-filter"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { ClaudeCodeModelId, claudeCodeDefaultModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { type ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { type ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
@@ -24,7 +24,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// Filter out image blocks since Claude Code doesn't support them
|
||||
const filteredMessages = filterMessagesForClaudeCode(messages)
|
||||
|
||||
@@ -113,7 +113,18 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
|
||||
// Yield tool_use blocks to the streaming pipeline for proper tool execution
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
call_id: content.id,
|
||||
function: {
|
||||
id: content.id,
|
||||
name: content.name,
|
||||
arguments: content.input,
|
||||
},
|
||||
},
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
@@ -8,7 +7,9 @@ import { ClineEnv } from "@/config"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -26,6 +27,7 @@ interface ClineHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
clineAccountId?: string
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class ClineHandler implements ApiHandler {
|
||||
@@ -96,7 +98,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = await this.ensureClient()
|
||||
|
||||
@@ -114,11 +116,13 @@ export class ClineHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("ClineHandler chunk:" + JSON.stringify(chunk))
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
@@ -149,6 +153,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const delta = choice?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -165,8 +170,7 @@ export class ClineHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,12 +185,13 @@ export class ClineHandler implements ApiHandler {
|
||||
"reasoning_details" in delta &&
|
||||
delta.reasoning_details &&
|
||||
// @ts-ignore-next-line
|
||||
delta.reasoning_details.length && // exists and non-0
|
||||
delta?.reasoning_details?.length && // exists and non-0
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning_details",
|
||||
reasoning_details: delta.reasoning_details,
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +199,7 @@ export class ClineHandler implements ApiHandler {
|
||||
// @ts-ignore-next-line
|
||||
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
|
||||
if (this.getModel().id === "x-ai/grok-code-fast-1") {
|
||||
if (this.getModel().id === "x-ai/grok-code-fast-1" || this.getModel().id === "minimax/minimax-m2") {
|
||||
totalCost = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -75,7 +75,7 @@ export class DeepSeekHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
@@ -97,7 +97,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -384,7 +384,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DoubaoModelId, doubaoDefaultModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -50,7 +50,7 @@ export class DoubaoHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { FireworksModelId, fireworksDefaultModelId, fireworksModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -41,7 +41,7 @@ export class FireworksHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.fireworksModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import {
|
||||
ApiError,
|
||||
@@ -7,10 +6,11 @@ import {
|
||||
type GenerateContentResponseUsageMetadata,
|
||||
GoogleGenAI,
|
||||
FunctionDeclaration as GoogleTool,
|
||||
Part,
|
||||
ThinkingLevel,
|
||||
} from "@google/genai"
|
||||
import { GeminiModelId, geminiDefaultModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { RetriableError, withRetry } from "../retry"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
@@ -28,6 +28,7 @@ interface GeminiHandlerOptions extends CommonApiHandlerOptions {
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
thinkingBudgetTokens?: number
|
||||
thinkingLevel?: string
|
||||
apiModelId?: string
|
||||
ulid?: string
|
||||
}
|
||||
@@ -110,34 +111,49 @@ export class GeminiHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: GoogleTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: GoogleTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const { id: modelId, info } = this.getModel()
|
||||
const contents = messages.map(convertAnthropicMessageToGemini)
|
||||
|
||||
// Configure thinking budget if supported
|
||||
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const _maxBudget = info.thinkingConfig?.maxBudget ?? 0
|
||||
const _thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const maxBudget = info.thinkingConfig?.maxBudget ?? 24576
|
||||
const thinkingBudget = Math.min(_thinkingBudget, maxBudget)
|
||||
// When ThinkingLevel is defineded, thinking budget cannot be zero
|
||||
// and only level is used to control thinking behavior.
|
||||
let thinkingLevel: ThinkingLevel | undefined
|
||||
if (this.options.thinkingLevel === "low") {
|
||||
thinkingLevel = ThinkingLevel.LOW
|
||||
} else if (this.options.thinkingLevel === "high") {
|
||||
thinkingLevel = ThinkingLevel.HIGH
|
||||
}
|
||||
|
||||
// Set up base generation config
|
||||
const requestConfig: GenerateContentConfig = {
|
||||
// Add base URL if configured
|
||||
httpOptions: this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined,
|
||||
...{ systemInstruction: systemPrompt },
|
||||
systemInstruction: systemPrompt,
|
||||
// Set temperature (default to 0)
|
||||
temperature: 0,
|
||||
// Gemini 3.0 recommends 1.0
|
||||
temperature: info.temperature ?? 1,
|
||||
}
|
||||
|
||||
// Add thinking config if the model supports it
|
||||
if (thinkingBudget > 0) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: thinkingBudget,
|
||||
includeThoughts: true,
|
||||
}
|
||||
requestConfig.thinkingConfig = {
|
||||
// Turn off thinking:
|
||||
// thinkingBudget: 0
|
||||
// Turn on dynamic thinking:
|
||||
// thinkingBudget: -1
|
||||
// Turn on fixed thinking budget:
|
||||
thinkingBudget: thinkingLevel ? undefined : thinkingBudget,
|
||||
thinkingLevel,
|
||||
includeThoughts: thinkingBudget > 0 || !!thinkingLevel,
|
||||
}
|
||||
|
||||
// Generate content using the configured parameters
|
||||
const sdkCallStartTime = Date.now()
|
||||
let responseId: string | undefined
|
||||
let sdkFirstChunkTime: number | undefined
|
||||
let ttftSdkMs: number | undefined
|
||||
let apiSuccess = false
|
||||
@@ -148,7 +164,8 @@ export class GeminiHandler implements ApiHandler {
|
||||
let thoughtsTokenCount = 0 // Initialize thought token counts
|
||||
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
|
||||
|
||||
if (tools?.length) {
|
||||
const isNativeToolCallsEnabled = tools?.length
|
||||
if (isNativeToolCallsEnabled) {
|
||||
requestConfig.tools = [{ functionDeclarations: tools }]
|
||||
requestConfig.toolConfig = {
|
||||
// Force the model to call 'any' function.
|
||||
@@ -176,56 +193,45 @@ export class GeminiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Handle thinking content from Gemini's response
|
||||
const candidateForThoughts = chunk?.candidates?.[0]
|
||||
const partsForThoughts = candidateForThoughts?.content?.parts
|
||||
let thoughts = "" // Initialize as empty string
|
||||
|
||||
if (partsForThoughts) {
|
||||
// This ensures partsForThoughts is a Part[] array
|
||||
for (const part of partsForThoughts) {
|
||||
const { thought, text } = part as Part
|
||||
if (thought && text) {
|
||||
// Ensure part.text exists
|
||||
// Handle the thought part
|
||||
thoughts += text + "\n" // Append thought and a newline
|
||||
const parts = chunk?.candidates?.[0]?.content?.parts || []
|
||||
for (const part of parts) {
|
||||
if (part.thought && part.text) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.responseId,
|
||||
reasoning: part.text || "",
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
} else if (part.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: part.text,
|
||||
id: chunk.responseId,
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thoughts.trim() !== "") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thoughts.trim(),
|
||||
}
|
||||
thoughts = "" // Reset thoughts after yielding
|
||||
}
|
||||
|
||||
if (chunk.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.text,
|
||||
}
|
||||
}
|
||||
|
||||
if (tools && chunk.functionCalls && chunk.functionCalls?.length > 0) {
|
||||
for (const functionCall of chunk.functionCalls) {
|
||||
if (functionCall.args) {
|
||||
console.log("[GeminiHandler] tool call received:", functionCall)
|
||||
if (part.functionCall) {
|
||||
const functionCall = part.functionCall
|
||||
const args = Object.entries(functionCall.args || {}).filter(([_key, val]) => !!val)
|
||||
if (functionCall.args && args.length > 0) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: chunk.responseId,
|
||||
tool_call: {
|
||||
function: {
|
||||
id: functionCall.id || functionCall.name,
|
||||
id: chunk.responseId,
|
||||
name: functionCall.name,
|
||||
arguments: JSON.stringify(functionCall.args),
|
||||
},
|
||||
},
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usageMetadata) {
|
||||
responseId = chunk.responseId
|
||||
lastUsageMetadata = chunk.usageMetadata
|
||||
promptTokens = lastUsageMetadata.promptTokenCount ?? promptTokens
|
||||
outputTokens = lastUsageMetadata.candidatesTokenCount ?? outputTokens
|
||||
@@ -251,6 +257,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens: 0,
|
||||
totalCost,
|
||||
id: responseId,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { GroqModelId, groqDefaultModelId, groqModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -192,7 +192,7 @@ export class GroqHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const modelFamily = this.detectModelFamily(model.id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { hicapModelInfoSaneDefaults, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -44,7 +44,7 @@ export class HicapHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.hicapModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuaweiCloudMaasModelId, huaweiCloudMaasDefaultModelId, huaweiCloudMaasModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -62,7 +62,7 @@ export class HuaweiCloudMaaSHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuggingFaceModelId, huggingFaceDefaultModelId, huggingFaceModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -69,7 +69,7 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { isAnthropicModelId } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
@@ -183,7 +184,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam | Anthropic.Messages.TextBlockParam = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -40,7 +40,7 @@ export class LmStudioHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { MinimaxModelId, ModelInfo, minimaxDefaultModelId, minimaxModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
@@ -45,7 +46,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -66,12 +67,11 @@ export class MinimaxHandler implements ApiHandler {
|
||||
tool_choice: nativeToolsOn ? { type: "any" } : undefined,
|
||||
})
|
||||
|
||||
let thinkingDeltaAccumulator = ""
|
||||
const lastStartedToolCall = { id: "", name: "", arguments: "" }
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
case "message_start": {
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
@@ -82,6 +82,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
break
|
||||
}
|
||||
case "message_delta":
|
||||
// tells us stop_reason, stop_sequence, and output tokens along the way and at the end of the message
|
||||
yield {
|
||||
@@ -100,13 +101,11 @@ export class MinimaxHandler implements ApiHandler {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
}
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
if (chunk.content_block.thinking && chunk.content_block.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking,
|
||||
signature: chunk.content_block.signature,
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -115,10 +114,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
redacted_data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
@@ -147,20 +143,19 @@ export class MinimaxHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
|
||||
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
}
|
||||
thinkingDeltaAccumulator += chunk.delta.thinking
|
||||
break
|
||||
case "signature_delta":
|
||||
// It's used when sending the thinking block back to the API
|
||||
// API expects this in completed form, not as array of deltas
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
if (chunk.delta.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Mistral } from "@mistralai/mistralai"
|
||||
import { HTTPClient } from "@mistralai/mistralai/lib/http"
|
||||
import { Tool as MistralTool } from "@mistralai/mistralai/models/components/tool"
|
||||
import { MistralModelId, ModelInfo, mistralDefaultModelId, mistralModels } from "@shared/api"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -48,7 +48,7 @@ export class MistralHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const stream = await client.chat
|
||||
.stream({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ModelInfo, MoonshotModelId, moonshotDefaultModelId, moonshotModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -40,7 +40,7 @@ export class MoonshotHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, type NebiusModelId, nebiusDefaultModelId, nebiusModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -39,7 +39,7 @@ export class NebiusHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ModelInfo, NousResearchModelId, nousResearchDefaultModelId, nousResearchModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface NousResearchHandlerOptions extends CommonApiHandlerOptions {
|
||||
nousResearchApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class NousResearchHandler implements ApiHandler {
|
||||
private options: NousResearchHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: NousResearchHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
if (!this.options.nousResearchApiKey) {
|
||||
throw new Error("NousResearch API key is required")
|
||||
}
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://inference-api.nousResearch.com/v1",
|
||||
apiKey: this.options.nousResearchApiKey,
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating NousResearch client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: openAiMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: (delta.reasoning_content as string | undefined) || "",
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: NousResearchModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
|
||||
if (modelId && modelId in nousResearchModels) {
|
||||
const id = modelId as NousResearchModelId
|
||||
return { id, info: nousResearchModels[id] }
|
||||
}
|
||||
return { id: nousResearchDefaultModelId, info: nousResearchModels[nousResearchDefaultModelId] }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { APIError, OpenAIError } from "openai"
|
||||
import type { FinalRequestOptions, Headers as OpenAIHeaders } from "openai/core"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { OcaAuthService } from "@/services/auth/oca/OcaAuthService"
|
||||
import {
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
} from "@/services/auth/oca/utils/constants"
|
||||
import { createOcaHeaders } from "@/services/auth/oca/utils/utils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, type CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -38,7 +37,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
protected initializeClient(options: OcaHandlerOptions) {
|
||||
return new (class OCIOpenAI extends OpenAI {
|
||||
protected override async prepareOptions(opts: FinalRequestOptions<unknown>): Promise<void> {
|
||||
protected override async prepareOptions(opts: any): Promise<void> {
|
||||
const token = await OcaAuthService.getInstance().getAuthToken()
|
||||
if (!token) {
|
||||
throw new OpenAIError("Unable to handle auth, Oracle Code Assist (OCA) access token is not available")
|
||||
@@ -55,7 +54,7 @@ export class OcaHandler implements ApiHandler {
|
||||
status: number | undefined,
|
||||
error: Object | undefined,
|
||||
message: string | undefined,
|
||||
headers: OpenAIHeaders | undefined,
|
||||
headers: any | undefined,
|
||||
): APIError {
|
||||
interface OciError {
|
||||
code?: string
|
||||
@@ -75,7 +74,8 @@ export class OcaHandler implements ApiHandler {
|
||||
if (opcRequestId) {
|
||||
ociErrorMessage += `\n(${OCI_HEADER_OPC_REQUEST_ID}: ${opcRequestId})`
|
||||
}
|
||||
return super.makeStatusError(status, error, ociErrorMessage, headers)
|
||||
const statusCode = typeof status === "number" ? status : 500
|
||||
return super.makeStatusError(statusCode, error ?? {}, ociErrorMessage, headers)
|
||||
}
|
||||
})({
|
||||
baseURL:
|
||||
@@ -139,7 +139,7 @@ export class OcaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
|
||||
@@ -151,7 +151,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
// Configuration for extended thinking
|
||||
const budgetTokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = budgetTokens !== 0 ? true : false
|
||||
const reasoningOn = budgetTokens !== 0
|
||||
const thinkingConfig = reasoningOn ? { type: "enabled", budget_tokens: budgetTokens } : undefined
|
||||
|
||||
let temperature: number | undefined = this.options.ocaModelInfo?.temperature ?? 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { type Config, type Message, Ollama } from "ollama"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOllamaMessages } from "../transform/ollama-format"
|
||||
@@ -48,7 +48,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const ollamaMessages: Message[] = [{ role: "system", content: systemPrompt }, ...convertToOllamaMessages(messages)]
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, OpenAiNativeModelId, openAiNativeDefaultModelId, openAiNativeModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToOpenAIResponsesInput } from "../transform/openai-response-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
@@ -61,7 +63,20 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
@withRetry()
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
useResponseFormat = false,
|
||||
): ApiStream {
|
||||
if (useResponseFormat) {
|
||||
yield* this.createResponseStream(systemPrompt, messages, tools)
|
||||
} else {
|
||||
yield* this.createCompletionStream(systemPrompt, messages, tools)
|
||||
}
|
||||
}
|
||||
|
||||
private async *createCompletionStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
@@ -115,6 +130,9 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
case "gpt-5-2025-08-07":
|
||||
case "gpt-5-mini-2025-08-07":
|
||||
case "gpt-5-nano-2025-08-07":
|
||||
case "gpt-5.1-2025-11-13":
|
||||
case "gpt-5.1-chat-latest":
|
||||
case "gpt-5.1": {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
temperature: 1,
|
||||
@@ -148,6 +166,7 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
@@ -181,6 +200,208 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *createResponseStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
// Convert messages to Responses API input format
|
||||
const input = convertToOpenAIResponsesInput(messages)
|
||||
|
||||
// Convert ChatCompletion tools to Responses API format if provided
|
||||
const responseTools = tools
|
||||
?.filter((tool) => tool.type === "function")
|
||||
.map((tool: any) => ({
|
||||
type: "function" as const,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
parameters: tool.function.parameters,
|
||||
strict: tool.function.strict ?? true, // Responses API defaults to strict mode
|
||||
}))
|
||||
|
||||
Logger.debug("OpenAI Responses Input: " + JSON.stringify(input))
|
||||
|
||||
// const lastAssistantMessage = [...messages].reverse().find((msg) => msg.role === "assistant" && msg.id)
|
||||
// const previous_response_id = lastAssistantMessage?.id
|
||||
|
||||
// Create the response using Responses API
|
||||
const stream = await client.responses.create({
|
||||
model: model.id,
|
||||
instructions: systemPrompt,
|
||||
input,
|
||||
stream: true,
|
||||
tools: responseTools,
|
||||
// previous_response_id,
|
||||
// store: true,
|
||||
reasoning: { effort: "medium", summary: "auto" },
|
||||
// include: ["reasoning.encrypted_content"],
|
||||
})
|
||||
|
||||
// Process the response stream
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("OpenAI Responses Chunk: " + JSON.stringify(chunk))
|
||||
|
||||
// Handle different event types from Responses API
|
||||
if (chunk.type === "response.output_item.added") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call" && item.id) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning" && item.encrypted_content && item.id) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
reasoning: "",
|
||||
redacted_data: item.encrypted_content,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_item.done") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id || item.call_id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
details: item.summary,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.added") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.part.text,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_text.delta") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.done") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
details: chunk.part,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_text.delta") {
|
||||
// Handle text content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "text",
|
||||
text: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_text.delta") {
|
||||
// Handle reasoning content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.delta") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.item_id,
|
||||
arguments: chunk.delta,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.done") {
|
||||
// Handle completed function call
|
||||
if (chunk.item_id && chunk.name && chunk.arguments) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
chunk.type === "response.incomplete" &&
|
||||
chunk.response?.status === "incomplete" &&
|
||||
chunk.response?.incomplete_details?.reason === "max_output_tokens"
|
||||
) {
|
||||
console.log("Ran out of tokens")
|
||||
if (chunk.response?.output_text?.length > 0) {
|
||||
console.log("Partial output:", chunk.response.output_text)
|
||||
} else {
|
||||
console.log("Ran out of tokens during reasoning")
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.type === "response.completed" && chunk.response?.usage) {
|
||||
// Handle usage information when response is complete
|
||||
const usage = chunk.response.usage
|
||||
const inputTokens = usage.input_tokens || 0
|
||||
const outputTokens = usage.output_tokens || 0
|
||||
const cacheReadTokens = usage.output_tokens_details?.reasoning_tokens || 0
|
||||
const cacheWriteTokens = usage.input_tokens_details?.cached_tokens || 0
|
||||
const totalTokens = usage.total_tokens || 0
|
||||
Logger.log(`Total tokens from Responses API usage: ${totalTokens}`)
|
||||
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
|
||||
const nonCachedInputTokens = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: nonCachedInputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: cacheWriteTokens,
|
||||
cacheReadTokens: cacheReadTokens,
|
||||
totalCost: totalCost,
|
||||
id: chunk.response.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: OpenAiNativeModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in openAiNativeModels) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { azureOpenAiDefaultApiVersion, ModelInfo, OpenAiCompatibleModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { AzureOpenAI } from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -65,11 +65,7 @@ export class OpenAiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ChatCompletionTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.openAiModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
@@ -81,7 +77,13 @@ export class OpenAiHandler implements ApiHandler {
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
let temperature: number | undefined = this.options.openAiModelInfo?.temperature ?? openAiModelInfoSaneDefaults.temperature
|
||||
let temperature: number | undefined
|
||||
if (this.options.openAiModelInfo?.temperature !== undefined) {
|
||||
const tempValue = Number(this.options.openAiModelInfo.temperature)
|
||||
temperature = tempValue === 0 ? undefined : tempValue
|
||||
} else {
|
||||
temperature = openAiModelInfoSaneDefaults.temperature
|
||||
}
|
||||
let reasoningEffort: ChatCompletionReasoningEffort | undefined
|
||||
let maxTokens: number | undefined
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { StateManager } from "@core/storage/StateManager"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -20,6 +21,7 @@ interface OpenRouterHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterProviderSorting?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class OpenRouterHandler implements ApiHandler {
|
||||
@@ -54,7 +56,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
this.lastGenerationId = undefined
|
||||
|
||||
@@ -67,6 +69,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
let didOutputUsage: boolean = false
|
||||
@@ -127,8 +130,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +144,9 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning_details",
|
||||
reasoning_details: delta.reasoning_details,
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,11 +217,11 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.openRouterModelId
|
||||
const modelInfo = this.options.openRouterModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
const modelId = this.options.openRouterModelId || openRouterDefaultModelId
|
||||
const cachedModelInfo = StateManager.get().getModelInfo("openRouter", modelId)
|
||||
return {
|
||||
id: modelId,
|
||||
info: cachedModelInfo || openRouterDefaultModelInfo,
|
||||
}
|
||||
return { id: openRouterDefaultModelId, info: openRouterDefaultModelInfo }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user