mirror of
https://github.com/cline/cline.git
synced 2026-09-09 15:02:23 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf762c54e |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix empty content field issue
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: enable Add Header button in OpenAICompatibleProvider UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Remove orphaned tool_results after truncation
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix: render model description in markdown
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Hicap api integration as provider
|
||||
@@ -1,19 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
for i in {1..100}; do
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PostToolUse hook custom errorMessage"
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "PostToolUse hook custom errorMessage: foo"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
for i in {1..100}; do
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PreToolUse hook custom errorMessage"
|
||||
"contextModification": "Make sure that all variables in python code in this repo always start with the string 'EVIEDEBUG_'. This is important.",
|
||||
"errorMessage": "PreToolUse hook custom errorMessage: foo"
|
||||
}
|
||||
EOF
|
||||
|
||||
+59
-26
@@ -4,7 +4,7 @@
|
||||
|
||||
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks can be placed in either:
|
||||
- **Global hooks directory**: `~/Documents/Cline/Hooks/` (applies to all workspaces)
|
||||
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to the workspace the repo is part of)
|
||||
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to specific workspace)
|
||||
|
||||
Hooks run automatically when enabled.
|
||||
|
||||
@@ -20,51 +20,51 @@ Hooks run automatically when enabled.
|
||||
### TaskStart Hook
|
||||
- **When**: Runs when a NEW task is started (not when resuming)
|
||||
- **Purpose**: Initialize task context, validate task requirements, set up environment
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskStart`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskStart` (all platforms)
|
||||
|
||||
### TaskResume Hook
|
||||
- **When**: Runs when an EXISTING task is resumed (after user clicks resume button)
|
||||
- **Purpose**: Validate resumed task state, restore context, check for changes since last run
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskResume`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskResume` (all platforms)
|
||||
|
||||
### TaskCancel Hook
|
||||
- **When**: Runs when a task is cancelled or a hook is aborted by the user (only if there's actual active work or work was started)
|
||||
- **When**: Runs when a task is cancelled by the user (only if there's actual active work or work was started)
|
||||
- **Purpose**: Clean up resources, log cancellation, save state
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskCancel`
|
||||
- **Note**: This hook is NOT cancellable
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskCancel` (all platforms)
|
||||
- **Note**: This hook is NOT cancellable and will complete even if the task is being aborted
|
||||
|
||||
### TaskComplete Hook (coming soon!)
|
||||
### TaskComplete Hook
|
||||
- **When**: Runs when a task is marked as complete
|
||||
- **Purpose**: Log completion status, perform final cleanup, generate reports
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskComplete`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskComplete` (all platforms)
|
||||
|
||||
### UserPromptSubmit Hook
|
||||
- **When**: Runs when the user submits a prompt/message (initial task, resume, or feedback)
|
||||
- **Purpose**: Validate user input, preprocess prompts, add context to user messages
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit`
|
||||
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit` (all platforms)
|
||||
|
||||
### PreToolUse Hook
|
||||
- **When**: Runs BEFORE a tool is executed
|
||||
- **Purpose**: Validate parameters, block execution, or add context
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse`
|
||||
- **Workspace Location**: `.clinerules/hooks/PreToolUse`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PreToolUse` (all platforms)
|
||||
|
||||
### PostToolUse Hook
|
||||
- **When**: Runs AFTER a tool completes
|
||||
- **Purpose**: Observe results, track patterns, or add context
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse`
|
||||
- **Workspace Location**: `.clinerules/hooks/PostToolUse`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PostToolUse` (all platforms)
|
||||
|
||||
### PreCompact Hook (coming soon!)
|
||||
### PreCompact Hook
|
||||
- **When**: Runs BEFORE the conversation context is compacted/truncated
|
||||
- **Purpose**: Observe compaction events, log context management, track token usage
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact`
|
||||
- **Workspace Location**: `.clinerules/hooks/PreCompact`
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PreCompact` (all platforms)
|
||||
|
||||
## Cross-Platform Hook Format
|
||||
|
||||
@@ -74,12 +74,13 @@ Cline uses a git-style approach for hooks that works consistently across all pla
|
||||
- **No file extensions**: Hooks are named exactly `PreToolUse` or `PostToolUse` (no `.bat`, `.cmd`, `.sh` etc.)
|
||||
- **Shebang required**: First line must be a shebang (e.g., `#!/usr/bin/env bash` or `#!/usr/bin/env node`)
|
||||
- **Executable on Unix**: On Unix/Linux/macOS, hooks must be executable: `chmod +x PreToolUse`
|
||||
- **Windows**: Not currently supported.
|
||||
- **Windows**: No special permissions needed - hooks are executed through the shell
|
||||
|
||||
### How It Works
|
||||
|
||||
Like git hooks, Cline executes hook files through a shell that interprets the shebang line:
|
||||
- On Unix/Linux/macOS: Native shell execution with shebang support
|
||||
- On Windows: Shell execution handles shebang interpretation
|
||||
|
||||
This means:
|
||||
- ✅ Same hook script works on all platforms
|
||||
@@ -97,6 +98,12 @@ nano ~/Documents/Cline/Hooks/PreToolUse
|
||||
chmod +x ~/Documents/Cline/Hooks/PreToolUse
|
||||
```
|
||||
|
||||
**On Windows:**
|
||||
```batch
|
||||
REM Create hook file (note: no file extension)
|
||||
notepad %USERPROFILE%\Documents\Cline\Hooks\PreToolUse
|
||||
```
|
||||
|
||||
## Context Injection Timing
|
||||
|
||||
**IMPORTANT**: Context injected by hooks affects **FUTURE AI decisions**, not the current tool execution.
|
||||
@@ -211,6 +218,27 @@ All hooks must return:
|
||||
- `false` (or omitted): Allow execution to continue
|
||||
- `true`: Block execution and show error message to user
|
||||
|
||||
## Context Modification Format
|
||||
|
||||
Use structured prefixes to help the AI understand context type:
|
||||
|
||||
- `WORKSPACE_RULES:` - Project conventions and requirements
|
||||
- `FILE_OPERATIONS:` - File creation/modification patterns
|
||||
- `TOOL_RESULT:` - Outcomes of tool executions
|
||||
- `PERFORMANCE:` - Performance concerns
|
||||
- `VALIDATION:` - Validation results
|
||||
- Custom prefixes as needed
|
||||
|
||||
Example:
|
||||
```bash
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: This is a TypeScript project. All new files must use .ts or .tsx extensions."
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## Hook Execution Limits
|
||||
|
||||
- **Timeout**: Hooks must complete within 30 seconds (configurable via `HOOK_EXECUTION_TIMEOUT_MS`)
|
||||
@@ -232,7 +260,7 @@ if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
|
||||
{
|
||||
"cancel": true,
|
||||
"errorMessage": "Cannot create .js files in TypeScript project",
|
||||
"contextModification": "Use .ts/.tsx extensions only"
|
||||
"contextModification": "WORKSPACE_RULES: Use .ts/.tsx extensions only"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
@@ -254,7 +282,7 @@ if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
"contextModification": "FILE_OPERATIONS: Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
@@ -274,7 +302,7 @@ if [[ "$execution_time" -gt 5000 ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
"contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
@@ -300,7 +328,7 @@ echo '{"cancel": false}'
|
||||
Cline supports two levels of hooks:
|
||||
|
||||
### Global Hooks
|
||||
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
|
||||
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux) or `%USERPROFILE%\Documents\Cline\Hooks\` (Windows)
|
||||
- **Scope**: Apply to ALL workspaces and projects
|
||||
- **Use Case**: Organization-wide policies, personal preferences, universal validations
|
||||
- **Priority**: Order not guaranteed when combined with workspace hooks
|
||||
@@ -328,12 +356,16 @@ When multiple hooks exist (global and/or workspace):
|
||||
|
||||
1. The global hooks directory is automatically created at:
|
||||
- macOS/Linux: `~/Documents/Cline/Hooks/`
|
||||
- Windows: `%USERPROFILE%\Documents\Cline\Hooks\`
|
||||
|
||||
2. Add your hook script:
|
||||
```bash
|
||||
# Unix/Linux/macOS
|
||||
nano ~/Documents/Cline/Hooks/PreToolUse
|
||||
chmod +x ~/Documents/Cline/Hooks/PreToolUse
|
||||
|
||||
# Windows
|
||||
notepad %USERPROFILE%\Documents\Cline\Hooks\PreToolUse
|
||||
```
|
||||
|
||||
3. Enable hooks in Cline settings
|
||||
@@ -401,6 +433,7 @@ If you have multiple workspace roots, you can place hooks in each root's `.cline
|
||||
|
||||
### Context Not Affecting Behavior
|
||||
- Remember: context affects FUTURE decisions, not the current tool
|
||||
- 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)
|
||||
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskCancel running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
for i in {1..100}; do
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskCancel response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskCancel hook custom errorMessage"
|
||||
"cancel", false,
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "TaskCancel hook custom errorMessage: foo"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskResume running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
for i in {1..100}; do
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskResume response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskResume hook custom errorMessage"
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "TaskResume hook custom errorMessage: foo"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskStart running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
for i in {1..100}; do
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskStart response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskStart hook custom errorMessage"
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "TaskStart hook custom errorMessage: foo"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "UserPromptSubmit running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
for i in {1..100}; do
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "UserPromptSubmit response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "UserPromptSubmit hook custom errorMessage"
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "UserPromptSubmit hook custom errorMessage: foo"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -20,9 +20,6 @@ eslint-rules/**
|
||||
.husky/**
|
||||
.env
|
||||
|
||||
# cli
|
||||
cli/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
.nvmrc
|
||||
|
||||
+1
-57
@@ -1,62 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## 3.37.1
|
||||
|
||||
- cf8dd1c: Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
|
||||
- 02abbcf: Add AGENTS.md support
|
||||
- 855db7d: 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
|
||||
- fix: disable native tool callings for grok code models
|
||||
- Add MCP tool usage to GLM
|
||||
- Removes reasoning_details content field from Anthropic providers
|
||||
|
||||
## [3.36.0]
|
||||
|
||||
- Add: Hooks allow you to inject custom logic into Cline's workflow
|
||||
- Add: new provider AIhubmix
|
||||
- Add: Use http_proxy, https_proxy and no_proxy in JetBrains
|
||||
- Fix: Oca Token Refresh logic
|
||||
- Fix: issues where assistant message with empty content is added to conversation history
|
||||
- Fix: bug where the checkbox shows in the model selector dropdown
|
||||
- Fix: Switch from defaultUserAgentProvider to customUserAgent for Bedrock
|
||||
- Fix: support for `<think>` tags for better compatibility with open-source models
|
||||
- Fix: refinements to the GLM-4.6 system prompt
|
||||
|
||||
## [3.35.1]
|
||||
|
||||
- Add: Hicap API integration as provider
|
||||
- Fix: enable Add Header button in OpenAICompatibleProvider UI
|
||||
- Fix: Remove orphaned tool_results after truncation and empty content field issues in native tool call
|
||||
- Fix: render model description in markdown
|
||||
|
||||
## [3.35.0]
|
||||
## 3.35.0
|
||||
|
||||
- Add native tool calling support with configurable setting.
|
||||
- Auto-approve is now always-on with a redesigned expanding menu. Settings simplified and notifications moved to General Settings.
|
||||
|
||||
+1
-7
@@ -46,11 +46,7 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
```bash
|
||||
npm run install:all
|
||||
```
|
||||
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.)
|
||||
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.)
|
||||
|
||||
|
||||
|
||||
@@ -89,10 +85,8 @@ 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**
|
||||
|
||||
+60
-66
@@ -1,68 +1,62 @@
|
||||
{
|
||||
"name": "cline",
|
||||
"version": "1.0.3",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"main": "cline-core.js",
|
||||
"bin": {
|
||||
"cline": "./bin/cline",
|
||||
"cline-host": "./bin/cline-host"
|
||||
},
|
||||
"man": "./man/cline.1",
|
||||
"scripts": {
|
||||
"postinstall": "node postinstall.js"
|
||||
},
|
||||
"bundleDependencies": [
|
||||
"@grpc/grpc-js",
|
||||
"@grpc/reflection",
|
||||
"better-sqlite3",
|
||||
"grpc-health-check",
|
||||
"open",
|
||||
"vscode-uri"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"claude",
|
||||
"dev",
|
||||
"mcp",
|
||||
"openrouter",
|
||||
"coding",
|
||||
"agent",
|
||||
"autonomous",
|
||||
"chatgpt",
|
||||
"sonnet",
|
||||
"ai",
|
||||
"llama",
|
||||
"cli"
|
||||
],
|
||||
"author": {
|
||||
"name": "Cline Bot Inc."
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cline/cline"
|
||||
},
|
||||
"homepage": "https://cline.bot",
|
||||
"bugs": {
|
||||
"url": "https://github.com/cline/cline/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.13.3",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"grpc-health-check": "^2.0.2",
|
||||
"open": "^10.1.2",
|
||||
"vscode-uri": "^3.1.0"
|
||||
},
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux"
|
||||
],
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
"name": "cline",
|
||||
"version": "1.0.3",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"main": "cline-core.js",
|
||||
"bin": {
|
||||
"cline": "./bin/cline",
|
||||
"cline-host": "./bin/cline-host"
|
||||
},
|
||||
"man": "./man/cline.1",
|
||||
"scripts": {
|
||||
"postinstall": "node postinstall.js"
|
||||
},
|
||||
"bundleDependencies": [
|
||||
"@grpc/grpc-js",
|
||||
"@grpc/reflection",
|
||||
"better-sqlite3",
|
||||
"grpc-health-check",
|
||||
"open",
|
||||
"vscode-uri"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"claude",
|
||||
"dev",
|
||||
"mcp",
|
||||
"openrouter",
|
||||
"coding",
|
||||
"agent",
|
||||
"autonomous",
|
||||
"chatgpt",
|
||||
"sonnet",
|
||||
"ai",
|
||||
"llama",
|
||||
"cli"
|
||||
],
|
||||
"author": {
|
||||
"name": "Cline Bot Inc."
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cline/cline"
|
||||
},
|
||||
"homepage": "https://cline.bot",
|
||||
"bugs": {
|
||||
"url": "https://github.com/cline/cline/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.13.3",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"grpc-health-check": "^2.0.2",
|
||||
"open": "^10.1.2",
|
||||
"vscode-uri": "^3.1.0"
|
||||
},
|
||||
"os": ["darwin", "linux"],
|
||||
"cpu": ["x64", "arm64"]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL
|
||||
}
|
||||
|
||||
// Create task manager for state operations
|
||||
manager, err := createTaskManager(ctx)
|
||||
manager, err := task.NewManagerForDefault(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create task manager: %w", err)
|
||||
}
|
||||
@@ -75,12 +75,6 @@ func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
fmt.Printf(" Model: %s\n", finalModelID)
|
||||
@@ -176,7 +170,6 @@ 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,7 +26,6 @@ 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},
|
||||
}
|
||||
}
|
||||
@@ -101,8 +100,6 @@ 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,7 +111,6 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
cline.ApiProvider_GEMINI,
|
||||
cline.ApiProvider_OLLAMA,
|
||||
cline.ApiProvider_CEREBRAS,
|
||||
cline.ApiProvider_NOUSRESEARCH,
|
||||
cline.ApiProvider_OCA,
|
||||
cline.ApiProvider_HICAP,
|
||||
}
|
||||
@@ -214,15 +213,13 @@ func extractProviderFromState(stateData map[string]interface{}, mode string) *Pr
|
||||
// mapProviderStringToEnum converts provider string from state to ApiProvider enum
|
||||
// Returns (provider, ok) where ok is false if the provider is unknown
|
||||
func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
|
||||
normalizedStr := strings.ToLower(providerStr)
|
||||
|
||||
// Map string values to enum values
|
||||
switch normalizedStr {
|
||||
switch providerStr {
|
||||
case "anthropic":
|
||||
return cline.ApiProvider_ANTHROPIC, true
|
||||
case "openai", "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
|
||||
case "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
|
||||
return cline.ApiProvider_OPENAI, true
|
||||
case "openai-native": // This is the native, official Open AI provider
|
||||
case "openai", "openai-native": // This is the native, official Open AI provider
|
||||
return cline.ApiProvider_OPENAI_NATIVE, true
|
||||
case "openrouter":
|
||||
return cline.ApiProvider_OPENROUTER, true
|
||||
@@ -242,8 +239,6 @@ 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
|
||||
}
|
||||
@@ -277,8 +272,6 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
|
||||
return "oca"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "nousResearch"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -358,8 +351,6 @@ 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"
|
||||
}
|
||||
@@ -482,7 +473,6 @@ 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,15 +163,6 @@ 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)
|
||||
}
|
||||
@@ -287,8 +278,6 @@ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, v
|
||||
apiConfig.OcaApiKey = value
|
||||
case "hicapApiKey":
|
||||
apiConfig.HicapApiKey = value
|
||||
case "nousResearchApiKey":
|
||||
apiConfig.NousResearchApiKey = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,9 +302,6 @@ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldNa
|
||||
case "planModeHicapModelId":
|
||||
apiConfig.PlanModeHicapModelId = value
|
||||
apiConfig.ActModeHicapModelId = value
|
||||
case "planModeNousResearchModelId":
|
||||
apiConfig.PlanModeNousResearchModelId = value
|
||||
apiConfig.ActModeNousResearchModelId = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,14 +106,6 @@ func (tr *ToolRenderer) generateToolHeader(tool *types.ToolMessage, verbTense st
|
||||
}
|
||||
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
|
||||
|
||||
case string(types.ToolTypeFileDeleted):
|
||||
if verbTense == "wants to" {
|
||||
action = "wants to delete"
|
||||
} else {
|
||||
action = "is deleting"
|
||||
}
|
||||
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
|
||||
|
||||
case string(types.ToolTypeListFilesTopLevel):
|
||||
if verbTense == "wants to" {
|
||||
action = "wants to list files in"
|
||||
@@ -207,7 +199,7 @@ func (tr *ToolRenderer) GenerateToolContentPreview(tool *types.ToolMessage) stri
|
||||
previewMd := fmt.Sprintf("```\n%s\n```", preview)
|
||||
return tr.renderMarkdown(previewMd)
|
||||
|
||||
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch), string(types.ToolTypeFileDeleted):
|
||||
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch):
|
||||
// No preview for read/fetch operations
|
||||
return ""
|
||||
|
||||
@@ -234,8 +226,7 @@ func (tr *ToolRenderer) GenerateToolContentBody(tool *types.ToolMessage) string
|
||||
toolParser := NewToolResultParser(tr.mdRenderer)
|
||||
|
||||
switch tool.Tool {
|
||||
case string(types.ToolTypeReadFile),
|
||||
string(types.ToolTypeFileDeleted):
|
||||
case string(types.ToolTypeReadFile):
|
||||
// readFile: show header only, no body
|
||||
return ""
|
||||
|
||||
|
||||
@@ -256,8 +256,6 @@ func determineAutoApprovalAction(msg *types.ClineMessage) (string, error) {
|
||||
case types.ToolTypeEditedExistingFile,
|
||||
types.ToolTypeNewFileCreated:
|
||||
return "edit_files", nil
|
||||
case types.ToolTypeFileDeleted:
|
||||
return "apply_patch", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported tool type: %s", toolMsg.Tool)
|
||||
}
|
||||
|
||||
@@ -180,6 +180,8 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
|
||||
settings.PlanModeHuaweiCloudMaasModelId = strPtr(value)
|
||||
case "plan_mode_oca_model_id":
|
||||
settings.PlanModeOcaModelId = strPtr(value)
|
||||
case "plan_mode_vercel_ai_gateway_model_id":
|
||||
settings.PlanModeVercelAiGatewayModelId = strPtr(value)
|
||||
case "act_mode_api_model_id":
|
||||
settings.ActModeApiModelId = strPtr(value)
|
||||
case "act_mode_reasoning_effort":
|
||||
@@ -216,6 +218,8 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
|
||||
settings.ActModeHuaweiCloudMaasModelId = strPtr(value)
|
||||
case "act_mode_oca_model_id":
|
||||
settings.ActModeOcaModelId = strPtr(value)
|
||||
case "act_mode_vercel_ai_gateway_model_id":
|
||||
settings.ActModeVercelAiGatewayModelId = strPtr(value)
|
||||
|
||||
// Boolean fields
|
||||
case "aws_use_cross_region_inference":
|
||||
@@ -732,6 +736,8 @@ func setSecretField(secrets *cline.Secrets, key, value string) error {
|
||||
secrets.HuaweiCloudMaasApiKey = strPtr(value)
|
||||
case "baseten_api_key":
|
||||
secrets.BasetenApiKey = strPtr(value)
|
||||
case "vercel_ai_gateway_api_key":
|
||||
secrets.VercelAiGatewayApiKey = strPtr(value)
|
||||
case "dify_api_key":
|
||||
secrets.DifyApiKey = strPtr(value)
|
||||
case "oca_api_key":
|
||||
|
||||
@@ -107,7 +107,6 @@ const (
|
||||
ToolTypeEditedExistingFile ToolType = "editedExistingFile"
|
||||
ToolTypeNewFileCreated ToolType = "newFileCreated"
|
||||
ToolTypeReadFile ToolType = "readFile"
|
||||
ToolTypeFileDeleted ToolType = "fileDeleted"
|
||||
ToolTypeListFilesTopLevel ToolType = "listFilesTopLevel"
|
||||
ToolTypeListFilesRecursive ToolType = "listFilesRecursive"
|
||||
ToolTypeListCodeDefinitionNames ToolType = "listCodeDefinitionNames"
|
||||
|
||||
@@ -145,7 +145,6 @@ const (
|
||||
XAI = "xai"
|
||||
CEREBRAS = "cerebras"
|
||||
OCA = "oca"
|
||||
NOUSRESEARCH = "nousResearch"
|
||||
)
|
||||
|
||||
// AllProviders returns a slice of enabled provider IDs for the CLI build.
|
||||
@@ -162,7 +161,6 @@ var AllProviders = []string{
|
||||
"xai",
|
||||
"cerebras",
|
||||
"oca",
|
||||
"nousResearch",
|
||||
}
|
||||
|
||||
// ConfigField represents a configuration field requirement
|
||||
@@ -320,15 +318,6 @@ 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",
|
||||
@@ -446,15 +435,6 @@ 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",
|
||||
@@ -795,24 +775,6 @@ 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": {
|
||||
@@ -1301,26 +1263,6 @@ 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."
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -1490,18 +1432,6 @@ 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
|
||||
}
|
||||
@@ -1529,7 +1459,6 @@ func GetProviderDisplayName(providerID string) string {
|
||||
"xai": "X AI (Grok)",
|
||||
"cerebras": "Cerebras",
|
||||
"oca": "Oca",
|
||||
"nousResearch": "NousResearch",
|
||||
}
|
||||
|
||||
if name, exists := displayNames[providerID]; exists {
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
---
|
||||
title: "GitHub Actions Integration"
|
||||
description: "Automatically respond to GitHub issues by mentioning @cline in comments using Cline CLI in GitHub Actions."
|
||||
---
|
||||
|
||||
# GitHub Integration Sample
|
||||
|
||||
Automate GitHub issue analysis with AI. Mention `@cline` in any issue comment to trigger an autonomous investigation that reads files, analyzes code, and provides actionable insights - all running automatically in GitHub Actions.
|
||||
|
||||
|
||||
<Note>
|
||||
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation). If you're new to Cline CLI, we recommend starting with the [GitHub RCA sample](../github-issue-rca) first, as it's simpler and will help you understand the fundamentals before setting up GitHub Actions.
|
||||
</Note>
|
||||
|
||||
## The Workflow
|
||||
|
||||
Trigger Cline by mentioning `@cline` in any issue comment:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss0a-comment.png" alt="Issue comment with @cline mention" width="600" />
|
||||
</Frame>
|
||||
|
||||
Cline's automated analysis appears as a new comment, with insights drawn from your actual codebase:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss0b-final.png" alt="Automated analysis response from Cline" width="600" />
|
||||
</Frame>
|
||||
|
||||
The entire investigation runs autonomously in GitHub Actions - from file exploration to posting results.
|
||||
|
||||
Let's configure your repository.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, you'll need:
|
||||
|
||||
- **Cline CLI knowledge** - Completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and understand basic usage
|
||||
- **GitHub repository** - With admin access to configure Actions and secrets
|
||||
- **GitHub Actions familiarity** - Basic understanding of workflows and CI/CD
|
||||
- **API provider account** - OpenRouter, Anthropic, or similar with API key
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Copy the Workflow File
|
||||
|
||||
|
||||
|
||||
Copy the workflow file from this sample to your repository. The workflow file must be placed in the `.github/workflows/` directory in your repository root for GitHub Actions to detect and run it. In this case, we'll name it `cline-responder.yml`.
|
||||
|
||||
```bash
|
||||
# In your repository root
|
||||
mkdir -p .github/workflows
|
||||
curl -o .github/workflows/cline-responder.yml https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-integration/cline-responder.yml
|
||||
```
|
||||
|
||||
Alternatively, you can copy the full workflow file directly into `.github/workflows/cline-responder.yml`:
|
||||
|
||||
<Accordion title="Click to view the complete cline-responder.yml workflow">
|
||||
```yaml
|
||||
name: Cline Issue Assistant
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
respond:
|
||||
runs-on: ubuntu-latest
|
||||
environment: cline-actions
|
||||
steps:
|
||||
- name: Check for @cline mention
|
||||
id: detect
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const body = context.payload.comment?.body || "";
|
||||
const isPR = !!context.payload.issue?.pull_request;
|
||||
const hit = body.toLowerCase().includes("@cline");
|
||||
core.setOutput("hit", (!isPR && hit) ? "true" : "false");
|
||||
core.setOutput("issue_number", String(context.payload.issue?.number || ""));
|
||||
core.setOutput("issue_url", context.payload.issue?.html_url || "");
|
||||
core.setOutput("comment_body", body);
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Node v20 is needed for Cline CLI on GitHub Actions Linux
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Cline CLI
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
run: |
|
||||
# Install the Cline CLI
|
||||
sudo npm install -g cline
|
||||
|
||||
- name: Create Cline Instance
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
env:
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
CLINE_DIR: ${{ runner.temp }}/cline
|
||||
run: |
|
||||
# Create instance and capture output
|
||||
INSTANCE_OUTPUT=$(cline instance new 2>&1)
|
||||
|
||||
# Parse address from output (format: " Address: 127.0.0.1:36733")
|
||||
CLINE_ADDRESS=$(echo "$INSTANCE_OUTPUT" | grep "Address:" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+')
|
||||
echo "CLINE_ADDRESS=$CLINE_ADDRESS" >> $GITHUB_ENV
|
||||
|
||||
# Configure API key
|
||||
cline config set open-router-api-key=$OPENROUTER_API_KEY --address $CLINE_ADDRESS -v
|
||||
|
||||
- name: Download analyze script
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
run: |
|
||||
export GITORG="YOUR-GITHUB-ORG"
|
||||
export GITREPO="YOUR-GITHUB-REPO"
|
||||
|
||||
curl -L https://raw.githubusercontent.com/${GITORG}/${GITREPO}/refs/heads/main/git-scripts/analyze-issue.sh -o analyze-issue.sh
|
||||
chmod +x analyze-issue.sh
|
||||
|
||||
- name: Run analysis
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
id: analyze
|
||||
env:
|
||||
ISSUE_URL: ${{ steps.detect.outputs.issue_url }}
|
||||
COMMENT: ${{ steps.detect.outputs.comment_body }}
|
||||
CLINE_ADDRESS: ${{ env.CLINE_ADDRESS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
RESULT=$(./analyze-issue.sh "${ISSUE_URL}" "Analyze this issue. The user asked: ${COMMENT}" "$CLINE_ADDRESS")
|
||||
|
||||
{
|
||||
echo 'result<<EOF'
|
||||
printf "%s\n" "$RESULT"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Post response
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ steps.detect.outputs.issue_number }}
|
||||
RESULT: ${{ steps.analyze.outputs.result }}
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: Number(process.env.ISSUE_NUMBER),
|
||||
body: process.env.RESULT || "(no output)"
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Warning>
|
||||
**You MUST edit the workflow file before committing!**
|
||||
|
||||
Open `.github/workflows/cline-responder.yml` and update the "Download analyze script" step within the workflow to specify your GitHub organization and repository where the analysis script is stored:
|
||||
|
||||
```yaml
|
||||
export GITORG="YOUR-GITHUB-ORG" # Change this!
|
||||
export GITREPO="YOUR-GITHUB-REPO" # Change this!
|
||||
```
|
||||
|
||||
**Example:** If your repository is `github.com/acme/myproject`, set:
|
||||
```yaml
|
||||
export GITORG="acme"
|
||||
export GITREPO="myproject"
|
||||
```
|
||||
|
||||
This tells the workflow where to download the analysis script from your repository after you commit it in step 3.
|
||||
</Warning>
|
||||
|
||||
The workflow will look for new or updated issues, check for `@cline` mentions, and then
|
||||
start up an instance of the Cline CLI to dig into the issue, providing feedback
|
||||
as a reply to the issue.
|
||||
|
||||
### 2. Configure API Keys
|
||||
|
||||
Add your AI provider API keys as repository secrets:
|
||||
|
||||
1. Go to your GitHub repository
|
||||
2. Navigate to **Settings** → **Environment** and Add a new environment.
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss01-environment.png" alt="Navigate to Actions secrets" width="600" />
|
||||
</Frame>
|
||||
|
||||
Make sure to name it "cline-actions" so that it matches the `environment`
|
||||
value at the top of the `cline-responder.yml` file.
|
||||
|
||||
3. Click **New repository secret**
|
||||
4. Add a secret for the `OPENROUTER_API_KEY` with a value of an API key from
|
||||
[openrouter.com](https://openrouter.com).
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss02-api-key.png" alt="Add API key secret" width="600" />
|
||||
</Frame>
|
||||
|
||||
5. Verify your secret is configured:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss03-ready.png" alt="API key configured" width="600" />
|
||||
</Frame>
|
||||
|
||||
Now you're ready to supply Cline with the credentials it needs in a GitHub Action.
|
||||
|
||||
### 3. Add Analysis Script
|
||||
|
||||
Add the analysis script from the `github-issue-rca` sample to your repository. **First, you'll need to create a `git-scripts` directory in your repository root where the script will be located.** Choose one of these options:
|
||||
|
||||
**Option A: Download directly (Recommended)**
|
||||
|
||||
```bash
|
||||
# In your repository root, create the directory and download the script
|
||||
mkdir -p git-scripts
|
||||
curl -o git-scripts/analyze-issue.sh https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
|
||||
chmod +x git-scripts/analyze-issue.sh
|
||||
```
|
||||
|
||||
**Option B: Manual copy-paste**
|
||||
|
||||
Create the directory and file manually, then paste the script content:
|
||||
|
||||
```bash
|
||||
# In your repository root
|
||||
mkdir -p git-scripts
|
||||
# Create and edit the file with your preferred editor
|
||||
nano git-scripts/analyze-issue.sh # or use vim, code, etc.
|
||||
```
|
||||
|
||||
<Accordion title="Click to view the complete analyze-issue.sh script">
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Analyze a GitHub issue using Cline CLI
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
After pasting the script content, make it executable:
|
||||
```bash
|
||||
chmod +x git-scripts/analyze-issue.sh
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
This analysis script calls Cline to execute a prompt on a GitHub issue,
|
||||
summarizing the output to populate the reply to the issue.
|
||||
|
||||
### 4. Commit and Push
|
||||
|
||||
```bash
|
||||
git add .github/workflows/cline-responder.yml
|
||||
git add git-scripts/analyze-issue.sh
|
||||
git commit -m "Add Cline issue assistant workflow"
|
||||
git push
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once set up, simply mention `@cline` in any issue comment:
|
||||
|
||||
```
|
||||
@cline what's causing this error?
|
||||
|
||||
@cline analyze the root cause
|
||||
|
||||
@cline what are the security implications?
|
||||
```
|
||||
|
||||
GitHub Actions will:
|
||||
1. Detect the `@cline` mention
|
||||
2. Start a Cline CLI instance
|
||||
3. Download the analysis script
|
||||
4. Analyze the issue using act mode with yolo (fully autonomous)
|
||||
5. Post Cline's analysis as a new comment
|
||||
|
||||
**Note**: The workflow only triggers on issue comments, not pull request
|
||||
comments.
|
||||
|
||||
## How It Works
|
||||
|
||||
The workflow (`cline-responder.yml`):
|
||||
|
||||
1. **Triggers** on issue comments (created or edited)
|
||||
2. **Detects** `@cline` mentions (case-insensitive)
|
||||
3. **Installs** Cline CLI globally using npm
|
||||
4. **Creates** a Cline instance using `cline instance new`
|
||||
5. **Configures** authentication using `cline config set open-router-api-key=...
|
||||
--address ...`
|
||||
6. **Downloads** the reusable `analyze-issue.sh` script from the
|
||||
`github-issue-rca` sample
|
||||
7. **Runs** analysis with the instance address
|
||||
8. **Posts** the analysis result as a comment
|
||||
|
||||
## Related Samples
|
||||
|
||||
- **[github-issue-rca](./github-issue-rca)**: The reusable script that powers this integration
|
||||
@@ -1,383 +0,0 @@
|
||||
---
|
||||
title: "GitHub Issue RCA Sample"
|
||||
description: "Automated GitHub issue analysis using Cline CLI to identify root causes."
|
||||
---
|
||||
|
||||
# GitHub Root Cause Analysis
|
||||
|
||||
Automated GitHub issue analysis using Cline CLI. This script uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues, outputting clean, parseable results that can be easily integrated into your development workflows.
|
||||
|
||||
<Note>
|
||||
**New to Cline CLI?** This sample assumes you have already completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and authenticated with `cline auth`. If you haven't set up Cline CLI yet, please start there first.
|
||||
</Note>
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/cli-rca.gif" alt="CLI Root Cause Analysis Demo" width="600" />
|
||||
</Frame>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This sample assumes you have already:
|
||||
|
||||
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/cline-cli/installation))
|
||||
- **At least one AI model provider** configured (e.g., OpenRouter, Anthropic, OpenAI)
|
||||
- **Basic familiarity** with Cline CLI commands
|
||||
|
||||
Additionally, you'll need:
|
||||
|
||||
- **GitHub CLI** (`gh`) installed and authenticated
|
||||
- **jq** installed for JSON parsing
|
||||
- **bash** shell (or compatible shell)
|
||||
|
||||
### Installation Instructions
|
||||
|
||||
#### macOS
|
||||
|
||||
<Note>
|
||||
These instructions require [Homebrew](https://brew.sh/) to be installed. If you don't have Homebrew, install it first by running:
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
```
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
# Install GitHub CLI
|
||||
brew install gh
|
||||
|
||||
# Install jq
|
||||
brew install jq
|
||||
|
||||
# Authenticate with GitHub
|
||||
gh auth login
|
||||
```
|
||||
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
# Install GitHub CLI (Debian/Ubuntu)
|
||||
sudo apt install gh
|
||||
|
||||
# Or for other Linux distributions, see: https://cli.github.com/manual/installation
|
||||
|
||||
# Install jq (Debian/Ubuntu)
|
||||
sudo apt install jq
|
||||
|
||||
# Authenticate with GitHub
|
||||
gh auth login
|
||||
```
|
||||
|
||||
## Getting the Script
|
||||
|
||||
**Option 1: Download directly with curl**
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
|
||||
```
|
||||
|
||||
**Option 2: Copy the full script**
|
||||
|
||||
<Accordion title="Click to view the complete analyze-issue.sh script">
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Analyze a GitHub issue using Cline CLI
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
**After downloading or creating the script**, make it executable by running:
|
||||
```bash
|
||||
chmod +x analyze-issue.sh
|
||||
```
|
||||
</Note>
|
||||
|
||||
## Quick Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Run this command in your terminal from the directory where you saved the script to analyze an issue with the default root cause prompt:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/123
|
||||
```
|
||||
|
||||
This will:
|
||||
- Fetch issue #123 from the repository
|
||||
- Analyze the issue to identify root causes
|
||||
- Provide detailed analysis with recommendations
|
||||
|
||||
### Custom Analysis Prompt
|
||||
|
||||
Ask specific questions about the issue:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/456 "What is the security impact?"
|
||||
```
|
||||
|
||||
### Using Specific Cline Instance
|
||||
|
||||
Target a particular Cline instance by address:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/123 \
|
||||
"What is the root cause of this issue?" \
|
||||
127.0.0.1:46529
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This is useful when:
|
||||
- Running multiple Cline instances
|
||||
- Using a remote Cline server
|
||||
- Testing with specific configurations
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The script will automatically handle everything: fetching the issue, analyzing it with Cline, and displaying the results. The analysis typically takes 30-60 seconds depending on the issue complexity.
|
||||
</Note>
|
||||
|
||||
## How It Works
|
||||
|
||||
Let's analyze each component of the script to understand how it works.
|
||||
|
||||
### Argument Validation
|
||||
|
||||
The script validates input and provides usage instructions:
|
||||
|
||||
```bash
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'Analyze security impact' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Validates required GitHub issue URL
|
||||
- Shows clear usage examples
|
||||
- Supports optional custom prompt
|
||||
- Supports optional Cline instance address
|
||||
|
||||
### Argument Parsing
|
||||
|
||||
The script extracts and sets up the arguments:
|
||||
|
||||
```bash
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `ISSUE_URL="$1"` - First argument is always the issue URL
|
||||
- `PROMPT="${2:-...}"` - Second argument is optional, defaults to root cause analysis
|
||||
- `ADDRESS` - Third argument is optional, only set if provided
|
||||
|
||||
### The Core Analysis Pipeline
|
||||
|
||||
This is where the magic happens:
|
||||
|
||||
```bash
|
||||
# Ask Cline for his analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
<Accordion title="Pipeline Breakdown: Understanding Each Component">
|
||||
|
||||
**1. `cline -y "$PROMPT: $ISSUE_URL"`**
|
||||
- `-y` enables yolo mode (no user interaction)
|
||||
- Constructs prompt with issue URL
|
||||
|
||||
**2. `--mode act`**
|
||||
- Enables act mode for active investigation
|
||||
- Allows Cline to use tools (read files, run commands, etc.)
|
||||
|
||||
**3. `$ADDRESS`**
|
||||
- Optional address flag for specific instance
|
||||
- Expands to `--address <ip:port>` if set
|
||||
|
||||
**4. `-F json`**
|
||||
- Outputs in JSON format for parsing
|
||||
|
||||
**5. `sed -n '/^{/,$p'`**
|
||||
- Extracts JSON from output
|
||||
- Skips any non-JSON prefix lines
|
||||
|
||||
**6. `jq -r 'select(.say == "completion_result") | .text'`**
|
||||
- Filters for completion result messages
|
||||
- Extracts the text field
|
||||
- `-r` outputs raw strings (no JSON quotes)
|
||||
|
||||
**7. `sed 's/\\n/\n/g'`**
|
||||
- Converts escaped newlines to actual newlines
|
||||
- Makes output readable
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Sample Output
|
||||
|
||||
Here's an example analyzing a real Flutter issue:
|
||||
|
||||
```bash
|
||||
$ ./analyze-issue.sh https://github.com/csells/flutter_counter/issues/2
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```markdown
|
||||
**Root Cause Analysis of Issue #2: "setState isn't cutting it"**
|
||||
|
||||
After examining the GitHub issue and analyzing the Flutter counter codebase,
|
||||
I've identified the root cause of why setState() is insufficient for this
|
||||
project's needs:
|
||||
|
||||
## Current Implementation Problems
|
||||
|
||||
The current Flutter counter app uses setState() for state management, which
|
||||
has several limitations:
|
||||
|
||||
1. **Local State Only**: setState() only works within a single widget, making
|
||||
it difficult to share state across the app
|
||||
2. **Rebuild Overhead**: Every setState() call rebuilds the entire widget tree,
|
||||
causing performance issues with complex UIs
|
||||
3. **No State Persistence**: State is lost when the widget is disposed
|
||||
4. **Testing Challenges**: setState-based logic is tightly coupled to the UI,
|
||||
making unit testing difficult
|
||||
|
||||
## Why This Matters
|
||||
|
||||
As the app grows beyond a simple counter, these limitations become critical:
|
||||
- Multiple screens need to access the count
|
||||
- State needs to persist across navigation
|
||||
- Business logic should be testable independently
|
||||
- UI should only rebuild when necessary
|
||||
|
||||
## Recommended Solutions
|
||||
|
||||
The issue mentions "Provider or Bloc" - both are excellent alternatives:
|
||||
|
||||
1. **Provider**: Simple, lightweight state management using InheritedWidget
|
||||
- Easy migration path from setState
|
||||
- Good for small to medium apps
|
||||
- Official Flutter recommendation
|
||||
|
||||
2. **Bloc**: More structured approach with clear separation between events,
|
||||
states, and business logic
|
||||
- Better for complex apps
|
||||
- Excellent testability
|
||||
- Clear architectural patterns
|
||||
|
||||
3. **Riverpod**: Modern alternative to Provider with better performance and
|
||||
developer experience
|
||||
- Compile-time safety
|
||||
- Better testing support
|
||||
- More flexible than Provider
|
||||
|
||||
4. **GetX**: Full-featured solution with state management, routing, and
|
||||
dependency injection
|
||||
- Minimal boilerplate
|
||||
- Fast and lightweight
|
||||
- All-in-one solution
|
||||
|
||||
## Next Steps
|
||||
|
||||
The current codebase needs refactoring to implement proper state management
|
||||
architecture to handle more complex state scenarios effectively. Provider
|
||||
would be the easiest migration path while Bloc provides better long-term
|
||||
scalability.
|
||||
```
|
||||
|
||||
## When to Use This Pattern
|
||||
|
||||
This script pattern is ideal for various development scenarios where automated GitHub issue analysis can accelerate your workflow.
|
||||
|
||||
### Bug Investigation
|
||||
|
||||
Quickly analyze bug reports and identify root causes without manual code exploration:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/123 \
|
||||
"What is the root cause of this bug?"
|
||||
```
|
||||
|
||||
### Feature Request Analysis
|
||||
|
||||
Understand context and implications of feature requests:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/456 \
|
||||
"What are the implementation challenges?"
|
||||
```
|
||||
|
||||
### Security Audits
|
||||
|
||||
Assess security implications of reported issues:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/789 \
|
||||
"What are the security implications?"
|
||||
```
|
||||
|
||||
### Documentation Generation
|
||||
|
||||
Generate detailed technical documentation from issues:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/654 \
|
||||
"Provide detailed technical documentation for this issue"
|
||||
```
|
||||
|
||||
### Code Review Assistance
|
||||
|
||||
Get second opinions on proposed changes:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/987 \
|
||||
"Review the proposed solution approach"
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This sample demonstrates how to build an autonomous GitHub issue analysis tool using Cline CLI:
|
||||
|
||||
1. **Building autonomous CLI tools** using Cline's capabilities
|
||||
2. **Parsing structured JSON output** from Cline CLI
|
||||
3. **Creating flexible automation scripts** with custom prompting
|
||||
4. **Integrating with GitHub** for issue analysis
|
||||
5. **Handling command-line arguments** effectively
|
||||
|
||||
This pattern can be adapted for many other automation scenarios, from pull request reviews to documentation generation to code quality analysis.
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [CLI Installation Guide](https://docs.cline.bot/cline-cli/installation)
|
||||
- [CLI Reference Documentation](https://docs.cline.bot/cline-cli/cli-reference)
|
||||
- [Three Core Flows](https://docs.cline.bot/cline-cli/three-core-flows)
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
title: "Samples Overview"
|
||||
description: Example implementations demonstrating Cline CLI capabilities
|
||||
---
|
||||
|
||||
This section provides sample implementations that demonstrate various Cline CLI features and capabilities. Each sample includes complete code, detailed explanations, and real-world usage examples.
|
||||
|
||||
## Available Samples
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card
|
||||
title="GitHub Root Cause Analysis"
|
||||
icon="magnifying-glass-chart"
|
||||
href="/cline-cli/samples/github-issue-rca"
|
||||
>
|
||||
A command-line script that uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues. Features JSON output parsing and non-interactive execution.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="GitHub Integration (Actions)"
|
||||
icon="github"
|
||||
href="/cline-cli/samples/github-integration"
|
||||
>
|
||||
Automatically respond to GitHub issues by mentioning @cline in comments. Uses Cline CLI in GitHub Actions to create an AI-powered issue assistant that analyzes and responds autonomously.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [CLI Installation Guide](/cline-cli/installation)
|
||||
- [CLI Reference Documentation](/cline-cli/cli-reference)
|
||||
- [Three Core Flows](/cline-cli/three-core-flows)
|
||||
@@ -88,14 +88,6 @@
|
||||
"cline-cli/overview",
|
||||
"cline-cli/installation",
|
||||
"cline-cli/three-core-flows",
|
||||
{
|
||||
"group": "CLI Samples",
|
||||
"pages": [
|
||||
"cline-cli/samples/overview",
|
||||
"cline-cli/samples/github-issue-rca",
|
||||
"cline-cli/samples/github-integration"
|
||||
]
|
||||
},
|
||||
"cline-cli/cli-reference"
|
||||
]
|
||||
},
|
||||
@@ -138,7 +130,6 @@
|
||||
"features/drag-and-drop",
|
||||
"features/editing-messages",
|
||||
"features/focus-chain",
|
||||
"features/hooks",
|
||||
"features/multiroot-workspace",
|
||||
"features/plan-and-act",
|
||||
{
|
||||
@@ -324,10 +315,6 @@
|
||||
{
|
||||
"source": "/getting-started/your-first-task",
|
||||
"destination": "/getting-started/your-first-project"
|
||||
},
|
||||
{
|
||||
"source": "/cline-cli/samples",
|
||||
"destination": "/cline-cli/samples/overview"
|
||||
}
|
||||
],
|
||||
"search": {
|
||||
|
||||
@@ -81,20 +81,6 @@ 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 enabling 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 unlocking the kind of fluid collaboration that typing can't match.
|
||||
|
||||
## Why Voice Changes Everything
|
||||
|
||||
@@ -35,14 +35,11 @@ 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.
|
||||
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
---
|
||||
title: "Hooks"
|
||||
sidebarTitle: "Hooks"
|
||||
description: "Inject custom logic into Cline's workflow to validate operations, monitor tool usage, and shape AI decisions"
|
||||
---
|
||||
|
||||
Hooks let you inject custom logic into Cline's workflow at key moments. Think of them as automated checkpoints where you can validate operations before they execute, monitor tool usage as it happens, and shape how Cline makes decisions.
|
||||
|
||||
Hooks run automatically when specific events happen during development. They receive detailed information about each operation, can block problematic actions before they cause issues, and can inject context that guides future AI decisions.
|
||||
|
||||
The real power comes from combining these capabilities. You can:
|
||||
|
||||
- Stop operations before they cause problems (like creating `.js` files in a TypeScript project)
|
||||
- Learn from what's happening and build up project knowledge over time
|
||||
- Monitor performance and catch issues as they emerge
|
||||
- Track everything for analytics or compliance
|
||||
- Trigger external tools or services at the right moments
|
||||
|
||||
<Warning>
|
||||
Hooks are currently supported on macOS and Linux only. Windows support is not available.
|
||||
</Warning>
|
||||
|
||||
## Getting Started
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/hooks.gif" alt="Hooks in action" />
|
||||
</Frame>
|
||||
|
||||
Enabling hooks in Cline is straightforward. Here's what you need to do:
|
||||
|
||||
<Steps>
|
||||
<Step title="Enable Hooks in Settings">
|
||||
Open Cline settings and check the **"Enable Hooks"** checkbox.
|
||||
|
||||
You can find this setting by:
|
||||
1. Opening Cline
|
||||
2. Click the "Settings" button on the top right corner
|
||||
3. Click the "Feature" section in the left side navigation menu.
|
||||
4. Scroll down until you see the "Enable Hooks" checkbox and check it.
|
||||
</Step>
|
||||
|
||||
<Step title="Choose Your Hook Location">
|
||||
Decide where to place your hooks:
|
||||
|
||||
**For personal or organization-wide hooks:**
|
||||
- Create hooks in `~/Documents/Cline/Rules/Hooks/`
|
||||
- These apply to all workspaces automatically
|
||||
|
||||
**For project-specific hooks:**
|
||||
- Create hooks in `.clinerules/hooks/` in your project root
|
||||
- These only apply to the specific workspace
|
||||
- Commit them to version control so your team can use them too
|
||||
</Step>
|
||||
|
||||
<Step title="Create Your First Hook">
|
||||
Hook files must have exact names with no file extensions. For example, to create a TaskStart hook:
|
||||
|
||||
```bash
|
||||
# Create the hook file
|
||||
vim .clinerules/hooks/TaskStart
|
||||
```
|
||||
|
||||
Add your script (must start with shebang)
|
||||
``` bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Store piped input into a variable
|
||||
input=$(cat)
|
||||
|
||||
# Dump the entire JSON payload
|
||||
echo "$input" | jq .
|
||||
|
||||
# Get the type of a field
|
||||
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**
|
||||
|
||||
```bash
|
||||
chmod +x .clinerules/hooks/TaskStart
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Test Your Hook">
|
||||
Start a task in Cline and verify your hook executes.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Tip>
|
||||
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.
|
||||
|
||||
<Note>
|
||||
The hook names below are the exact file names you need to create. For example, to use the TaskStart hook, create a file named `TaskStart` (no file extension) in your hooks directory.
|
||||
</Note>
|
||||
|
||||
Each hook receives base fields in addition to its specific data: `clineVersion`, `hookName`, `timestamp`, `taskId`, `workspaceRoots`, `userId`.
|
||||
|
||||
### Tool Execution
|
||||
|
||||
These hooks intercept and validate tool operations before and after they execute. Use them to enforce policies, track changes, and learn from operations.
|
||||
|
||||
#### PreToolUse
|
||||
|
||||
Runs before any tool executes. Use it to block invalid operations, validate parameters, and enforce project policies before changes happen.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "PreToolUse",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"preToolUse": {
|
||||
"toolName": "string",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### PostToolUse
|
||||
|
||||
Runs after a tool completes. Use it to learn from results, track performance metrics, and build project knowledge based on operations performed.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "PostToolUse",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"postToolUse": {
|
||||
"toolName": "string",
|
||||
"parameters": {},
|
||||
"result": "string",
|
||||
"success": boolean,
|
||||
"executionTimeMs": number
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### User Interaction
|
||||
|
||||
These hooks monitor and enhance user communication with Cline. Use them to validate input, inject context, and track interaction patterns.
|
||||
|
||||
#### UserPromptSubmit
|
||||
|
||||
Runs when a user sends a message to Cline. Use it to validate input, inject context based on the prompt, and track interaction patterns.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "UserPromptSubmit",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"userPromptSubmit": {
|
||||
"prompt": "string",
|
||||
"attachments": ["string"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Task Lifecycle
|
||||
|
||||
These hooks monitor and respond to task state changes from start to finish. Use them to track progress, restore state, and trigger workflows.
|
||||
|
||||
#### TaskStart
|
||||
|
||||
Runs when a new task begins. Use it to detect project type, initialize tracking, and inject initial context that shapes how Cline approaches the work.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskStart",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskStart": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string",
|
||||
"initialTask": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### TaskResume
|
||||
|
||||
Runs when a task resumes after interruption. Use it to restore state, refresh context, and log resumption for analytics or external system notifications.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskResume",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskResume": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
},
|
||||
"previousState": {
|
||||
"lastMessageTs": "string",
|
||||
"messageCount": "string",
|
||||
"conversationHistoryDeleted": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### TaskCancel
|
||||
|
||||
Runs when a task is cancelled. Use it to cleanup resources, log cancellation details, and notify external systems about interrupted work.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskCancel",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskCancel": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string",
|
||||
"completionStatus": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{/*
|
||||
#### TaskComplete
|
||||
|
||||
Runs when a task finishes successfully. Use it for final cleanup, tracking metrics, generating reports, and triggering post-task workflows.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskComplete",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskComplete": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*/}
|
||||
|
||||
### System Events
|
||||
|
||||
These hooks monitor internal Cline operations and system-level events. Use them to track context usage, log system behavior, and analyze performance patterns.
|
||||
|
||||
{/*
|
||||
#### PreCompact
|
||||
|
||||
Runs before conversation context is truncated to fit token limits. Use it to monitor compaction frequency, log events, and track context usage patterns.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "PreCompact",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"preCompact": {
|
||||
"contextSize": number,
|
||||
"messagesToCompact": number,
|
||||
"compactionStrategy": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
*/}
|
||||
|
||||
### JSON Communication
|
||||
|
||||
Hooks receive JSON via stdin and return JSON via stdout.
|
||||
|
||||
**Output structure:**
|
||||
```json
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: Use TypeScript",
|
||||
"errorMessage": "Error details if blocking"
|
||||
}
|
||||
```
|
||||
|
||||
Your hook script can output logging or diagnostic information to stdout during execution, as long as the JSON response is the last thing written. Cline will parse only the final JSON object from stdout.
|
||||
|
||||
For example:
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
echo "Processing hook..." # This is fine
|
||||
echo "Tool: $tool_name" # This is also fine
|
||||
# The JSON must be last:
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
The `cancel` field controls whether execution continues. Set it to `true` to block an action, `false` to allow it.
|
||||
|
||||
The `contextModification` field injects text into the conversation. This affects future AI decisions, not the current one. Use prefixes like `WORKSPACE_RULES:` or `PERFORMANCE:` to help categorize the context.
|
||||
|
||||
### Understanding Context Timing
|
||||
|
||||
Context injection affects future decisions, not current ones. When a hook runs:
|
||||
|
||||
1. The AI has already decided what to do
|
||||
2. The hook can block or allow it
|
||||
3. Any context gets added to the conversation
|
||||
4. The next AI request sees that context
|
||||
|
||||
This means PreToolUse hooks are for blocking bad actions, while PostToolUse hooks are for learning from completed ones.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Running
|
||||
- Ensure the "Enable Hooks" setting is checked
|
||||
- Verify the hook file is executable (`chmod +x hookname`)
|
||||
- Check the hook file has no syntax errors
|
||||
- Look for errors in VSCode's Output panel (Cline channel)
|
||||
|
||||
### Hook Timing Out
|
||||
- Reduce complexity of the hook script
|
||||
- Avoid expensive operations (network calls, heavy computations)
|
||||
- Consider moving complex logic to a background process
|
||||
|
||||
### Context Not Affecting Behavior
|
||||
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.
|
||||
</Warning>
|
||||
|
||||
## Related Features
|
||||
|
||||
Hooks complement other Cline features:
|
||||
|
||||
- [Cline Rules](/features/cline-rules) define high-level guidance that hooks can enforce
|
||||
- [Checkpoints](/features/checkpoints) let you roll back changes if a hook didn't catch an issue
|
||||
- [Auto-Approve](/features/auto-approve) works well with hooks as safety nets for automated operations
|
||||
@@ -29,33 +29,11 @@ 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.
|
||||
@@ -112,20 +90,9 @@ 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. **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
|
||||
2. Check that the server URL is correct and the server is running
|
||||
3. Use the "Restart Server" button to attempt reconnection
|
||||
4. If problems persist, you can delete the server and try adding it again
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
@@ -138,11 +105,10 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
{
|
||||
"mcpServers": {
|
||||
"exampleServer": {
|
||||
"url": "https://example.com/mcp-server",
|
||||
"type": "streamableHttp",
|
||||
"url": "https://example.com/mcp-sse",
|
||||
"disabled": false,
|
||||
"autoApprove": ["tool1", "tool2"],
|
||||
"timeout": 60
|
||||
"timeout": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,10 +117,9 @@ 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 (default: 60)
|
||||
- **timeout**: Maximum time in seconds to wait for server responses
|
||||
|
||||
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
|
||||
|
||||
@@ -165,20 +130,3 @@ 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.
|
||||
|
||||
@@ -18,7 +18,8 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
|
||||
|
||||
Cline supports the following Cerebras models:
|
||||
|
||||
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
|
||||
- `qwen-3-coder-480b-free` (Free tier) - High-performance coding model at no cost
|
||||
- `qwen-3-coder-480b` - Flagship 480B parameter coding model
|
||||
- `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
|
||||
|
||||
+1
-5
@@ -123,11 +123,7 @@ const copyWasmFiles = {
|
||||
},
|
||||
}
|
||||
|
||||
const buildEnvVars = {
|
||||
"import.meta.url": "_importMetaUrl",
|
||||
"process.env.IS_STANDALONE": JSON.stringify(standalone),
|
||||
}
|
||||
|
||||
const buildEnvVars = { "import.meta.url": "_importMetaUrl" }
|
||||
if (production) {
|
||||
// IS_DEV is always disable in production builds.
|
||||
buildEnvVars["process.env.IS_DEV"] = "false"
|
||||
|
||||
Generated
+1289
-750
File diff suppressed because it is too large
Load Diff
+7
-15
@@ -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.37.1",
|
||||
"version": "3.35.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -149,12 +149,6 @@
|
||||
"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",
|
||||
@@ -284,11 +278,11 @@
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"when": "config.git.enabled && !cline.isGeneratingCommit"
|
||||
"when": "config.git.enabled && scmProvider == git && !cline.isGeneratingCommit"
|
||||
},
|
||||
{
|
||||
"command": "cline.abortGitCommitMessage",
|
||||
"when": "config.git.enabled && cline.isGeneratingCommit"
|
||||
"when": "config.git.enabled && scmProvider == git && cline.isGeneratingCommit"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -310,7 +304,6 @@
|
||||
"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",
|
||||
@@ -337,7 +330,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 # Use `UPDATE_SNAPSHOTS=true npm run test:unit` to rebuild prompt snapshots",
|
||||
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha",
|
||||
"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",
|
||||
@@ -412,8 +405,8 @@
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
"@anthropic-ai/vertex-sdk": "^0.6.4",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
|
||||
"@aws-sdk/credential-providers": "^3.922.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
||||
"@aws-sdk/credential-providers": "^3.840.0",
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
@@ -466,6 +459,7 @@
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
"grpc-health-check": "^2.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ignore": "^7.0.3",
|
||||
"image-size": "^2.0.2",
|
||||
@@ -473,7 +467,6 @@
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"ollama": "^0.5.13",
|
||||
@@ -497,7 +490,6 @@
|
||||
"ts-morph": "^25.0.1",
|
||||
"turndown": "^7.2.0",
|
||||
"ulid": "^2.4.0",
|
||||
"undici": "^7.16.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vscode-uri": "^3.1.0",
|
||||
"web-tree-sitter": "^0.22.6",
|
||||
|
||||
+4
-23
@@ -49,9 +49,6 @@ 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);
|
||||
|
||||
@@ -77,9 +74,8 @@ message RefreshedRules {
|
||||
ClineRulesToggles local_cline_rules_toggles = 2;
|
||||
ClineRulesToggles local_cursor_rules_toggles = 3;
|
||||
ClineRulesToggles local_windsurf_rules_toggles = 4;
|
||||
ClineRulesToggles local_agents_rules_toggles = 5;
|
||||
ClineRulesToggles local_workflow_toggles = 6;
|
||||
ClineRulesToggles global_workflow_toggles = 7;
|
||||
ClineRulesToggles local_workflow_toggles = 5;
|
||||
ClineRulesToggles global_workflow_toggles = 6;
|
||||
}
|
||||
|
||||
// Request to toggle a Windsurf rule
|
||||
@@ -89,13 +85,6 @@ 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;
|
||||
@@ -167,17 +156,10 @@ 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;
|
||||
RuleScope scope = 2; // Scope of the rule (local, global, or remote)
|
||||
bool is_global = 2; // Whether this is a global rule or workspace rule
|
||||
string rule_path = 3; // Path to the rule file
|
||||
bool enabled = 4; // Whether to enable or disable the rule
|
||||
}
|
||||
@@ -191,7 +173,6 @@ 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
|
||||
@@ -206,5 +187,5 @@ message ToggleWorkflowRequest {
|
||||
Metadata metadata = 1;
|
||||
string workflow_path = 2;
|
||||
bool enabled = 3;
|
||||
RuleScope scope = 4; // Scope of the workflow (local, global, or remote)
|
||||
bool is_global = 4;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ 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);
|
||||
@@ -44,7 +43,6 @@ message AddRemoteMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
string server_url = 3;
|
||||
optional string transport_type = 4;
|
||||
}
|
||||
|
||||
message ToggleToolAutoApproveRequest {
|
||||
@@ -93,8 +91,6 @@ 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 {
|
||||
|
||||
@@ -23,6 +23,8 @@ service ModelsService {
|
||||
rpc refreshHuggingFaceModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns OpenAI models
|
||||
rpc refreshOpenAiModels(OpenAiModelsRequest) returns (StringArray);
|
||||
// Refreshes and returns Vercel AI Gateway models
|
||||
rpc refreshVercelAiGatewayModelsRpc(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns Requesty models
|
||||
rpc refreshRequestyModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns Hicap models
|
||||
@@ -43,8 +45,6 @@ service ModelsService {
|
||||
rpc getSapAiCoreModels(SapAiCoreModelsRequest) returns (SapAiCoreModelsResponse);
|
||||
// Fetches available models from OCA
|
||||
rpc refreshOcaModels(StringRequest) returns (OcaCompatibleModelInfo);
|
||||
// Fetches available models from AIhubmix
|
||||
rpc getAihubmixModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
}
|
||||
|
||||
// List of VS Code LM models
|
||||
@@ -96,7 +96,6 @@ message OpenRouterModelInfo {
|
||||
optional ThinkingConfig thinking_config = 10;
|
||||
optional bool supports_global_endpoint = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional string name = 13;
|
||||
}
|
||||
|
||||
// Shared response message for model information
|
||||
@@ -173,7 +172,6 @@ message ModelsApiSecrets {
|
||||
optional string oca_api_key = 36;
|
||||
optional string oca_refresh_token = 37;
|
||||
optional string minimax_api_key = 38;
|
||||
optional string aihubmix_api_key = 39;
|
||||
}
|
||||
|
||||
// API configuration options (non-secret settings)
|
||||
@@ -220,8 +218,6 @@ message ModelsApiOptions {
|
||||
optional string oca_mode = 39;
|
||||
optional bool aws_use_global_inference = 40;
|
||||
optional string minimax_api_line = 41;
|
||||
optional string aihubmix_base_url = 42;
|
||||
optional string aihubmix_app_code = 43;
|
||||
|
||||
// Plan mode configurations
|
||||
optional ApiProvider plan_mode_api_provider = 100;
|
||||
@@ -257,8 +253,6 @@ message ModelsApiOptions {
|
||||
optional OpenRouterModelInfo plan_mode_vercel_ai_gateway_model_info = 130;
|
||||
optional string plan_mode_oca_model_id = 131;
|
||||
optional OcaModelInfo plan_mode_oca_model_info = 132;
|
||||
optional string plan_mode_aihubmix_model_id = 133;
|
||||
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 134;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
@@ -294,8 +288,6 @@ message ModelsApiOptions {
|
||||
optional OpenRouterModelInfo act_mode_vercel_ai_gateway_model_info = 230;
|
||||
optional string act_mode_oca_model_id = 231;
|
||||
optional OcaModelInfo act_mode_oca_model_info = 232;
|
||||
optional string act_mode_aihubmix_model_id = 233;
|
||||
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 234;
|
||||
}
|
||||
|
||||
// Request for updating API configuration (legacy - uses combined configuration)
|
||||
@@ -304,24 +296,11 @@ message UpdateApiConfigurationRequest {
|
||||
ModelsApiConfiguration api_configuration = 2;
|
||||
}
|
||||
|
||||
// Combined API configuration containing both options and secrets
|
||||
message ApiConfiguration {
|
||||
ModelsApiOptions options = 1;
|
||||
ModelsApiSecrets secrets = 2;
|
||||
}
|
||||
|
||||
// Request for updating API configuration (new - uses separate options and secrets)
|
||||
message UpdateApiConfigurationRequestNew {
|
||||
Metadata metadata = 1;
|
||||
ApiConfiguration updates = 2;
|
||||
|
||||
// Required field mask specifying which fields to update.
|
||||
// Field paths use dot notation with camelCase field names:
|
||||
// - "options.ulid" (for options fields)
|
||||
// - "options.openAiHeaders" (for options fields)
|
||||
// - "secrets.apiKey" (for secrets fields)
|
||||
// - "secrets.openRouterApiKey" (for secrets fields)
|
||||
repeated string update_mask = 3;
|
||||
ModelsApiOptions options = 2;
|
||||
ModelsApiSecrets secrets = 3;
|
||||
}
|
||||
|
||||
// Request for partially updating API configuration using FieldMask
|
||||
@@ -421,8 +400,6 @@ enum ApiProvider {
|
||||
OCA = 35;
|
||||
MINIMAX = 36;
|
||||
HICAP = 37;
|
||||
AIHUBMIX = 38;
|
||||
NOUSRESEARCH = 39;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
@@ -544,10 +521,6 @@ message ModelsApiConfiguration {
|
||||
optional string minimax_api_line = 79;
|
||||
optional string hicap_model_id = 80;
|
||||
optional string hicap_api_key = 81;
|
||||
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,9 +558,6 @@ message ModelsApiConfiguration {
|
||||
optional OcaModelInfo plan_mode_oca_model_info = 132;
|
||||
optional string plan_mode_hicap_model_id = 133;
|
||||
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;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
@@ -625,7 +595,4 @@ message ModelsApiConfiguration {
|
||||
optional OcaModelInfo act_mode_oca_model_info = 232;
|
||||
optional string act_mode_hicap_model_id = 233;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ service StateService {
|
||||
rpc updateSettingsCli(UpdateSettingsRequestCli) returns (Empty);
|
||||
rpc updateTaskSettings(UpdateTaskSettingsRequest) returns (Empty);
|
||||
rpc updateTelemetrySetting(TelemetrySettingRequest) returns (Empty);
|
||||
rpc captureOnboardingProgress(OnboardingProgressRequest) returns (Empty);
|
||||
rpc setWelcomeViewCompleted(BooleanRequest) returns (Empty);
|
||||
rpc updateInfoBannerVersion(Int64Request) returns (Empty);
|
||||
rpc updateModelBannerVersion(Int64Request) returns (Empty);
|
||||
@@ -31,7 +30,6 @@ service StateService {
|
||||
rpc installClineCli(EmptyRequest) returns (Empty);
|
||||
rpc checkCliInstallation(EmptyRequest) returns (Boolean);
|
||||
rpc getProcessInfo(EmptyRequest) returns (ProcessInfo);
|
||||
rpc flushPendingState(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message AutoApprovalActions {
|
||||
@@ -90,7 +88,6 @@ 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 {
|
||||
@@ -218,13 +215,6 @@ message Settings {
|
||||
optional int32 max_consecutive_mistakes = 124;
|
||||
optional bool subagents_enabled = 125;
|
||||
optional int32 subagent_terminal_output_line_limit = 126;
|
||||
optional string aihubmix_api_key = 127;
|
||||
optional string aihubmix_base_url = 128;
|
||||
optional string aihubmix_app_code = 129;
|
||||
optional string plan_mode_aihubmix_model_id = 130;
|
||||
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 131;
|
||||
optional string act_mode_aihubmix_model_id = 132;
|
||||
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 133;
|
||||
}
|
||||
|
||||
message DictationSettings {
|
||||
@@ -362,7 +352,6 @@ message UpdateSettingsRequest {
|
||||
optional int32 subagent_terminal_output_line_limit = 30;
|
||||
optional string cline_env = 31;
|
||||
optional bool native_tool_call_enabled = 32;
|
||||
optional bool show_onboarding_flow = 33;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutRequest {
|
||||
@@ -383,10 +372,3 @@ message ProcessInfo {
|
||||
optional string version = 2;
|
||||
optional int64 uptime_ms = 3;
|
||||
}
|
||||
|
||||
message OnboardingProgressRequest {
|
||||
int32 step = 1;
|
||||
optional string action = 2;
|
||||
optional bool completed = 3;
|
||||
optional string model_selected = 4;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ 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
|
||||
@@ -100,7 +99,6 @@ message TaskItem {
|
||||
int32 tokens_out = 8;
|
||||
int32 cache_writes = 9;
|
||||
int32 cache_reads = 10;
|
||||
string model_id = 11;
|
||||
}
|
||||
|
||||
// Request for ask response operation
|
||||
|
||||
@@ -32,7 +32,6 @@ enum ClineAsk {
|
||||
CONDENSE = 13;
|
||||
REPORT_BUG = 14;
|
||||
SUMMARIZE_TASK = 15;
|
||||
ACT_MODE_RESPOND = 16;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
@@ -78,7 +77,6 @@ enum ClineSayToolType {
|
||||
LIST_CODE_DEFINITION_NAMES = 5;
|
||||
SEARCH_FILES = 6;
|
||||
WEB_FETCH = 7;
|
||||
FILE_DELETED = 8;
|
||||
}
|
||||
|
||||
// Enum for browser actions
|
||||
@@ -185,11 +183,6 @@ message ClineApiReqInfo {
|
||||
ApiReqRetryStatus retry_status = 9;
|
||||
}
|
||||
|
||||
message ClineModelInfo {
|
||||
string provider_id = 1;
|
||||
string model_id = 2;
|
||||
}
|
||||
|
||||
// Main ClineMessage type
|
||||
message ClineMessage {
|
||||
int64 ts = 1;
|
||||
@@ -216,7 +209,6 @@ 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,7 +95,6 @@ const ENABLED_PROVIDERS = [
|
||||
"ollama", // Ollama local models
|
||||
"cerebras", // Cerebras models
|
||||
"oca", // Oracle Code Assist
|
||||
"nousResearch", // NousResearch provider
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler } from "../../core/api/index"
|
||||
import { ApiStream } from "../../core/api/transform/stream"
|
||||
|
||||
|
||||
+3
-3
@@ -28,8 +28,8 @@ class ClineEndpoint {
|
||||
private environment: Environment = Environment.production
|
||||
|
||||
private constructor() {
|
||||
// Set environment at module load. Use override if provided.
|
||||
const _env = process?.env?.CLINE_ENVIRONMENT_OVERRIDE || process?.env?.CLINE_ENVIRONMENT
|
||||
// Set environment at module load
|
||||
const _env = process?.env?.CLINE_ENVIRONMENT
|
||||
if (_env && Object.values(Environment).includes(_env as Environment)) {
|
||||
this.environment = _env as Environment
|
||||
return
|
||||
@@ -62,7 +62,7 @@ class ClineEndpoint {
|
||||
environment: Environment.staging,
|
||||
appBaseUrl: "https://staging-app.cline.bot",
|
||||
apiBaseUrl: "https://core-api.staging.int.cline.bot",
|
||||
mcpBaseUrl: "https://core-api.staging.int.cline.bot/v1/mcp",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
|
||||
authDomain: "cline-staging.firebaseapp.com",
|
||||
|
||||
+4
-22
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { AIhubmixHandler } from "./providers/aihubmix"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
import { AskSageHandler } from "./providers/asksage"
|
||||
import { BasetenHandler } from "./providers/baseten"
|
||||
@@ -25,7 +24,6 @@ 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"
|
||||
@@ -368,10 +366,10 @@ function createHandlerForProvider(
|
||||
return new VercelAIGatewayHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
vercelAiGatewayApiKey: options.vercelAiGatewayApiKey,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
vercelAiGatewayModelId:
|
||||
mode === "plan" ? options.planModeVercelAiGatewayModelId : options.actModeVercelAiGatewayModelId,
|
||||
vercelAiGatewayModelInfo:
|
||||
mode === "plan" ? options.planModeVercelAiGatewayModelInfo : options.actModeVercelAiGatewayModelInfo,
|
||||
})
|
||||
case "zai":
|
||||
return new ZAiHandler({
|
||||
@@ -394,16 +392,6 @@ function createHandlerForProvider(
|
||||
: options.actModeOcaModelInfo?.supportsPromptCache,
|
||||
taskId: options.ulid,
|
||||
})
|
||||
case "aihubmix":
|
||||
return new AIhubmixHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
apiKey: options.aihubmixApiKey,
|
||||
baseURL: options.aihubmixBaseUrl,
|
||||
appCode: options.aihubmixAppCode,
|
||||
modelId: mode === "plan" ? (options as any).planModeAihubmixModelId : (options as any).actModeAihubmixModelId,
|
||||
modelInfo:
|
||||
mode === "plan" ? (options as any).planModeAihubmixModelInfo : (options as any).actModeAihubmixModelInfo,
|
||||
})
|
||||
case "minimax":
|
||||
return new MinimaxHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
@@ -417,12 +405,6 @@ 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,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { LiteLlmHandler, type LiteLlmModelInfoResponse } from "@core/api/provide
|
||||
import { convertToOpenAiMessages } from "@core/api/transform/openai-format"
|
||||
import { expect } from "chai"
|
||||
import sinon from "sinon"
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
const fakeClient = {
|
||||
chat: {
|
||||
@@ -11,12 +10,12 @@ const fakeClient = {
|
||||
create: sinon.stub(),
|
||||
},
|
||||
},
|
||||
baseURL: "https://fake.example",
|
||||
baseURL: "fake",
|
||||
}
|
||||
|
||||
describe("LiteLlmHandler", () => {
|
||||
const originalFetch = global.fetch
|
||||
const mockFetch = sinon.stub()
|
||||
let doneMockingFetch: (value: any) => void = () => {}
|
||||
|
||||
const mockModelFetch = (modelInfo: LiteLlmModelInfoResponse["data"][number]) => {
|
||||
mockFetch.resolves({
|
||||
@@ -46,11 +45,7 @@ describe("LiteLlmHandler", () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetchForTesting(mockFetch, () => {
|
||||
return new Promise((resolve) => {
|
||||
doneMockingFetch = resolve
|
||||
})
|
||||
})
|
||||
global.fetch = mockFetch
|
||||
|
||||
// Configure the stub to return a stream that closes immediately with usage data
|
||||
fakeClient.chat.completions.create.resolves(
|
||||
@@ -73,7 +68,8 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
afterEach(() => {
|
||||
sinon.reset()
|
||||
doneMockingFetch(void 0)
|
||||
|
||||
global.fetch = originalFetch
|
||||
})
|
||||
|
||||
const createAsyncIterable = (data: any[] = []) => {
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { GenerateContentConfig, GoogleGenAI } from "@google/genai"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AIhubmixHandlerOptions extends CommonApiHandlerOptions {
|
||||
apiKey?: string
|
||||
baseURL?: string
|
||||
appCode?: string
|
||||
modelId?: string
|
||||
modelInfo?: ModelInfo
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
||||
export class AIhubmixHandler implements ApiHandler {
|
||||
private options: AIhubmixHandlerOptions
|
||||
private anthropicClient: Anthropic | undefined
|
||||
private openaiClient: OpenAI | undefined
|
||||
private geminiClient: GoogleGenAI | undefined
|
||||
|
||||
constructor(options: AIhubmixHandlerOptions) {
|
||||
const { baseURL, appCode, ...rest } = options
|
||||
this.options = {
|
||||
baseURL: baseURL ?? "https://aihubmix.com",
|
||||
appCode: appCode ?? "KUWF9311",
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
|
||||
private ensureAnthropicClient(): Anthropic {
|
||||
if (!this.anthropicClient) {
|
||||
if (!this.options.apiKey) {
|
||||
throw new Error("AIhubmix API key is required")
|
||||
}
|
||||
try {
|
||||
this.anthropicClient = new Anthropic({
|
||||
apiKey: this.options.apiKey,
|
||||
baseURL: this.options.baseURL,
|
||||
defaultHeaders: {
|
||||
"APP-Code": this.options.appCode,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Anthropic client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.anthropicClient
|
||||
}
|
||||
|
||||
private ensureOpenaiClient(): OpenAI {
|
||||
if (!this.openaiClient) {
|
||||
if (!this.options.apiKey) {
|
||||
throw new Error("AIhubmix API key is required")
|
||||
}
|
||||
try {
|
||||
this.openaiClient = new OpenAI({
|
||||
apiKey: this.options.apiKey,
|
||||
baseURL: `${this.options.baseURL}/v1`,
|
||||
defaultHeaders: {
|
||||
"APP-Code": this.options.appCode,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating OpenAI client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.openaiClient
|
||||
}
|
||||
|
||||
private ensureGeminiClient(): GoogleGenAI {
|
||||
if (!this.geminiClient) {
|
||||
if (!this.options.apiKey) {
|
||||
throw new Error("AIhubmix API key is required")
|
||||
}
|
||||
try {
|
||||
this.geminiClient = new GoogleGenAI({
|
||||
apiKey: this.options.apiKey,
|
||||
httpOptions: {
|
||||
baseUrl: `${this.options.baseURL}/gemini`,
|
||||
headers: {
|
||||
// @ts-expect-error
|
||||
"APP-Code": this.options.appCode,
|
||||
Authorization: `Bearer ${this.options.apiKey ?? ""}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Gemini client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.geminiClient
|
||||
}
|
||||
|
||||
private routeModel(modelName: string): "anthropic" | "openai" | "gemini" | "openai-response" {
|
||||
const id = modelName || ""
|
||||
if (id.startsWith("claude")) {
|
||||
return "anthropic"
|
||||
}
|
||||
if (id.startsWith("gemini") && !id.endsWith("-nothink") && !id.endsWith("-search")) {
|
||||
return "gemini"
|
||||
}
|
||||
if (id === "gpt-5-pro" || id === "gpt-5-codex") {
|
||||
return "openai-response"
|
||||
}
|
||||
return "openai"
|
||||
}
|
||||
|
||||
private fixToolChoice(requestBody: any): any {
|
||||
if (requestBody.tools?.length === 0 && requestBody.tool_choice) {
|
||||
delete requestBody.tool_choice
|
||||
}
|
||||
return requestBody
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: any[]): ApiStream {
|
||||
const modelId = this.options.modelId || ""
|
||||
const route = this.routeModel(modelId)
|
||||
|
||||
switch (route) {
|
||||
case "anthropic":
|
||||
yield* this.createAnthropicMessage(systemPrompt, messages)
|
||||
break
|
||||
case "gemini":
|
||||
yield* this.createGeminiMessage(systemPrompt, messages)
|
||||
break
|
||||
case "openai-response":
|
||||
yield* this.createOpenaiResponseMessage(systemPrompt, messages)
|
||||
break
|
||||
case "openai":
|
||||
yield* this.createOpenaiMessage(systemPrompt, messages)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported model route: ${route}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async *createAnthropicMessage(systemPrompt: string, messages: any[]): ApiStream {
|
||||
const client = this.ensureAnthropicClient()
|
||||
const modelId = this.options.modelId || "claude-3-5-sonnet-20241022"
|
||||
|
||||
const stream = await client.messages.create({
|
||||
model: modelId,
|
||||
temperature: 0,
|
||||
max_tokens: this.options.modelInfo?.maxTokens || 8192,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages,
|
||||
stream: true,
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
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":
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: 0,
|
||||
outputTokens: chunk.usage.output_tokens || 0,
|
||||
}
|
||||
break
|
||||
case "content_block_start":
|
||||
if (chunk.content_block.type === "text") {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.content_block.text,
|
||||
}
|
||||
}
|
||||
break
|
||||
case "content_block_delta":
|
||||
if (chunk.delta.type === "text_delta") {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.delta.text,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async *createOpenaiResponseMessage(systemPrompt: string, messages: any[]): ApiStream {
|
||||
const client = this.ensureOpenaiClient()
|
||||
const modelId = this.options.modelId || "gpt-4o-mini"
|
||||
|
||||
const input = (messages || []).map((m: any) => {
|
||||
const role = m.role || "user"
|
||||
const contentArray = Array.isArray(m.content) ? m.content : [{ type: "text", text: m.content }]
|
||||
const content = contentArray
|
||||
.filter((c: any) => c != null)
|
||||
.map((c: any) => {
|
||||
if (c.type === "image" || c.type === "input_image" || c.type === "image_url") {
|
||||
return { type: "input_image", image_url: c.image_url || c.url || c.source?.url }
|
||||
}
|
||||
const text = c.text ?? (typeof c === "string" ? c : "")
|
||||
return { type: role === "assistant" ? "output_text" : "input_text", text }
|
||||
})
|
||||
return { role, content }
|
||||
})
|
||||
|
||||
const stream = await (client as any).responses.stream({
|
||||
model: modelId,
|
||||
instructions: systemPrompt,
|
||||
input,
|
||||
})
|
||||
|
||||
for await (const event of stream as any) {
|
||||
if (event?.type === "response.output_text.delta") {
|
||||
yield { type: "text", text: event.delta || "" }
|
||||
continue
|
||||
}
|
||||
if (event?.type === "response.completed") {
|
||||
const usage = event.response?.usage || {}
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: usage.input_tokens || 0,
|
||||
outputTokens: usage.output_tokens || 0,
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (event?.type === "response.error") {
|
||||
throw new Error(event.error?.message || "responses error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async *createOpenaiMessage(systemPrompt: string, messages: any[]): ApiStream {
|
||||
const client = this.ensureOpenaiClient()
|
||||
const modelId = this.options.modelId || "gpt-4o-mini"
|
||||
|
||||
const openaiMessages = [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)]
|
||||
|
||||
const requestBody = {
|
||||
model: modelId,
|
||||
messages: openaiMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
}
|
||||
|
||||
const fixedRequestBody = this.fixToolChoice(requestBody)
|
||||
|
||||
const stream = await client.chat.completions.create(fixedRequestBody)
|
||||
|
||||
for await (const chunk of stream as any) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async *createGeminiMessage(systemPrompt: string, messages: any[]): ApiStream {
|
||||
const client = this.ensureGeminiClient()
|
||||
const modelId = this.options.modelId || "gemini-2.0-flash-exp"
|
||||
|
||||
const contents = messages.map(convertAnthropicMessageToGemini)
|
||||
|
||||
const requestConfig: GenerateContentConfig = {
|
||||
systemInstruction: systemPrompt,
|
||||
temperature: 0,
|
||||
}
|
||||
|
||||
if (this.options.thinkingBudgetTokens) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: this.options.thinkingBudgetTokens,
|
||||
includeThoughts: true,
|
||||
}
|
||||
}
|
||||
|
||||
const stream = await client.models.generateContentStream({
|
||||
model: modelId,
|
||||
contents,
|
||||
config: requestConfig,
|
||||
})
|
||||
|
||||
for await (const chunk of stream as any) {
|
||||
if (chunk?.text) {
|
||||
yield { type: "text", text: chunk.text }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
return {
|
||||
id: this.options.modelId || "gpt-4o-mini",
|
||||
info: this.options.modelInfo || {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
description: "AIhubmix unified model provider",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Tool as AnthropicTool, MessageParam } 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 { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AnthropicHandlerOptions extends CommonApiHandlerOptions {
|
||||
@@ -33,7 +31,6 @@ export class AnthropicHandler implements ApiHandler {
|
||||
this.client = new Anthropic({
|
||||
apiKey: this.options.apiKey,
|
||||
baseURL: this.options.anthropicBaseUrl || undefined,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Anthropic client: ${error.message}`)
|
||||
@@ -64,7 +61,6 @@ export class AnthropicHandler implements ApiHandler {
|
||||
switch (modelId) {
|
||||
// 'latest' alias does not support cache_control
|
||||
case "claude-haiku-4-5-20251001":
|
||||
case "claude-sonnet-4-5-20250929:1m":
|
||||
case "claude-sonnet-4-5-20250929":
|
||||
case "claude-sonnet-4-20250514":
|
||||
case "claude-3-7-sonnet-20250219":
|
||||
@@ -86,7 +82,35 @@ export class AnthropicHandler implements ApiHandler {
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex)
|
||||
const anthropicMessages: Array<MessageParam> = messages.map((message, index) => {
|
||||
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
}
|
||||
}
|
||||
return message
|
||||
})
|
||||
|
||||
stream = await client.messages.create(
|
||||
{
|
||||
@@ -135,7 +159,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
messages,
|
||||
// tools,
|
||||
// tool_choice: { type: "auto" },
|
||||
stream: true,
|
||||
@@ -144,21 +168,20 @@ export class AnthropicHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
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":
|
||||
@@ -179,7 +202,15 @@ export class AnthropicHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
signature: chunk.content_block.signature,
|
||||
}
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
break
|
||||
case "redacted_thinking":
|
||||
@@ -187,7 +218,10 @@ export class AnthropicHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
redacted_data: chunk.content_block.data,
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
@@ -216,19 +250,20 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking 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 (chunk.delta.signature) {
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "", // reasoning text is already sent via thinking_delta
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -32,7 +31,6 @@ export class BasetenHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://inference.baseten.co/v1",
|
||||
apiKey: this.options.basetenApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Baseten client: ${error.message}`)
|
||||
|
||||
@@ -270,12 +270,8 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add proxy support for AWS SDK
|
||||
// AWS SDK uses a different architecture than fetch-based SDKs.
|
||||
// To add proxy support, we need to provide a custom requestHandler.
|
||||
return new BedrockRuntimeClient({
|
||||
userAgentAppId: `cline#${ExtensionRegistryInfo.version}`,
|
||||
defaultUserAgentProvider: () => Promise.resolve([["cline", ExtensionRegistryInfo.version]]),
|
||||
region: this.getRegion(),
|
||||
...auth,
|
||||
...(this.options.awsBedrockEndpoint && { endpoint: this.options.awsBedrockEndpoint }),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { CerebrasModelId, cerebrasDefaultModelId, cerebrasModels, ModelInfo } from "@shared/api"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -32,7 +31,6 @@ export class CerebrasHandler implements ApiHandler {
|
||||
this.client = new Cerebras({
|
||||
apiKey: cleanApiKey,
|
||||
timeout: 30000, // 30 second timeout
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Cerebras client: ${error.message}`)
|
||||
|
||||
@@ -113,18 +113,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
// 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
@@ -165,7 +164,8 @@ export class ClineHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +184,8 @@ export class ClineHandler implements ApiHandler {
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
details: delta.reasoning_details,
|
||||
type: "reasoning_details",
|
||||
reasoning_details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +193,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" || this.getModel().id === "minimax/minimax-m2") {
|
||||
if (this.getModel().id === "x-ai/grok-code-fast-1") {
|
||||
totalCost = 0
|
||||
}
|
||||
|
||||
@@ -240,7 +239,6 @@ export class ClineHandler implements ApiHandler {
|
||||
const response = await axios.get(`${this.clineAccountService.baseUrl}/generation?id=${this.lastGenerationId}`, {
|
||||
headers,
|
||||
timeout: 15_000, // this request hangs sometimes
|
||||
...getAxiosSettings(),
|
||||
})
|
||||
|
||||
const generation = response.data
|
||||
|
||||
@@ -3,7 +3,6 @@ import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } fr
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -33,7 +32,6 @@ export class DeepSeekHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.deepseek.com/v1",
|
||||
apiKey: this.options.deepSeekApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating DeepSeek client: ${error.message}`)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DoubaoModelId, doubaoDefaultModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -28,7 +27,6 @@ export class DoubaoHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://ark.cn-beijing.volces.com/api/v3/",
|
||||
apiKey: this.options.doubaoApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Doubao client: ${error.message}`)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { FireworksModelId, fireworksDefaultModelId, fireworksModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -31,7 +30,6 @@ export class FireworksHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.fireworks.ai/inference/v1",
|
||||
apiKey: this.options.fireworksApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Fireworks client: ${error.message}`)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { GroqModelId, groqDefaultModelId, groqModels, ModelInfo } from "@shared/
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -103,7 +102,6 @@ export class GroqHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.groq.com/openai/v1",
|
||||
apiKey: this.options.groqApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Groq client: ${error.message}`)
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -31,7 +30,6 @@ export class HuaweiCloudMaaSHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.modelarts-maas.com/v1/",
|
||||
apiKey: this.options.huaweiCloudMaasApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Huawei Cloud MaaS client: ${error.message}`)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { HuggingFaceModelId, huggingFaceDefaultModelId, huggingFaceModels, Model
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -38,7 +37,6 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
defaultHeaders: {
|
||||
"User-Agent": "Cline/1.0",
|
||||
},
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Hugging Face client: ${error.message}`)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { isAnthropicModelId } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -56,7 +55,6 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: this.options.liteLlmBaseUrl || "http://localhost:4000",
|
||||
apiKey: this.options.liteLlmApiKey || "noop",
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating LiteLLM client: ${error.message}`)
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 { fetch } from "@/shared/net"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -30,7 +29,6 @@ export class LmStudioHandler implements ApiHandler {
|
||||
// Docs on the new v0 api endpoint: https://lmstudio.ai/docs/app/api/endpoints/rest
|
||||
baseURL: new URL("api/v0", this.options.lmStudioBaseUrl || "http://localhost:1234").toString(),
|
||||
apiKey: "noop",
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating LM Studio client: ${error.message}`)
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -35,7 +34,6 @@ export class MinimaxHandler implements ApiHandler {
|
||||
this.options.minimaxApiLine === "china"
|
||||
? "https://api.minimaxi.com/anthropic"
|
||||
: "https://api.minimax.io/anthropic",
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating MiniMax client: ${error.message}`)
|
||||
@@ -66,11 +64,12 @@ 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 {
|
||||
@@ -81,7 +80,6 @@ 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,11 +98,13 @@ export class MinimaxHandler implements ApiHandler {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
}
|
||||
if (chunk.content_block.thinking && chunk.content_block.signature) {
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking,
|
||||
signature: chunk.content_block.signature,
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -113,7 +113,10 @@ export class MinimaxHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
redacted_data: chunk.content_block.data,
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
@@ -142,19 +145,20 @@ export class MinimaxHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking 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 (chunk.delta.signature) {
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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 { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToMistralMessages } from "../transform/mistral-format"
|
||||
@@ -29,16 +27,8 @@ export class MistralHandler implements ApiHandler {
|
||||
throw new Error("Mistral API key is required")
|
||||
}
|
||||
try {
|
||||
// Create HTTP client with custom fetch for proxy support
|
||||
const httpClient = new HTTPClient({
|
||||
fetcher: (request) => {
|
||||
return fetch(request)
|
||||
},
|
||||
})
|
||||
|
||||
this.client = new Mistral({
|
||||
apiKey: this.options.mistralApiKey,
|
||||
httpClient,
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Mistral client: ${error.message}`)
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -30,7 +29,6 @@ export class MoonshotHandler implements ApiHandler {
|
||||
baseURL:
|
||||
this.options.moonshotApiLine === "china" ? "https://api.moonshot.cn/v1" : "https://api.moonshot.ai/v1",
|
||||
apiKey: this.options.moonshotApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Moonshot client: ${error.message}`)
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -29,7 +28,6 @@ export class NebiusHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.studio.nebius.ai/v1",
|
||||
apiKey: this.options.nebiusApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating Nebius client: ${error.message}`)
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, NousResearchModelId, nousResearchDefaultModelId, nousResearchModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
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: Anthropic.Messages.MessageParam[]): 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] }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "@/services/auth/oca/utils/constants"
|
||||
import { createOcaHeaders } from "@/services/auth/oca/utils/utils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, type CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -82,7 +81,6 @@ export class OcaHandler implements ApiHandler {
|
||||
options.ocaBaseUrl ||
|
||||
(options.ocaMode === "internal" ? DEFAULT_INTERNAL_OCA_BASE_URL : DEFAULT_EXTERNAL_OCA_BASE_URL),
|
||||
apiKey: "noop",
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ModelInfo, OpenAiNativeModelId, openAiNativeDefaultModelId, openAiNativ
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -32,7 +31,6 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
apiKey: this.options.openAiNativeApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating OpenAI client: ${error.message}`)
|
||||
@@ -115,9 +113,6 @@ 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,
|
||||
@@ -151,7 +146,6 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -47,14 +46,12 @@ export class OpenAiHandler implements ApiHandler {
|
||||
apiKey: this.options.openAiApiKey,
|
||||
apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion,
|
||||
defaultHeaders: this.options.openAiHeaders,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} else {
|
||||
this.client = new OpenAI({
|
||||
baseURL: this.options.openAiBaseUrl,
|
||||
apiKey: this.options.openAiApiKey,
|
||||
defaultHeaders: this.options.openAiHeaders,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -81,13 +78,7 @@ export class OpenAiHandler implements ApiHandler {
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
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 temperature: number | undefined = this.options.openAiModelInfo?.temperature ?? openAiModelInfoSaneDefaults.temperature
|
||||
let reasoningEffort: ChatCompletionReasoningEffort | undefined
|
||||
let maxTokens: number | undefined
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ 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 { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
@@ -44,7 +43,6 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
"HTTP-Referer": "https://cline.bot", // Optional, for including your app on openrouter.ai rankings.
|
||||
"X-Title": "Cline", // Optional. Shows in rankings on openrouter.ai.
|
||||
},
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating OpenRouter client: ${error.message}`)
|
||||
@@ -127,7 +125,8 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +140,8 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
details: delta.reasoning_details,
|
||||
type: "reasoning_details",
|
||||
reasoning_details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +201,6 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
Authorization: `Bearer ${this.options.openRouterApiKey}`,
|
||||
},
|
||||
timeout: 15_000, // this request hangs sometimes
|
||||
...getAxiosSettings(),
|
||||
})
|
||||
yield response.data?.data
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -53,7 +52,6 @@ export class QwenHandler implements ApiHandler {
|
||||
? "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
apiKey: this.options.qwenApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Alibaba client: ${error.message}`)
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import { toRequestyServiceStringUrl } from "@/shared/clients/requesty"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { toRequestyServiceStringUrl } from "@/shared/providers/requesty"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -49,7 +48,6 @@ export class RequestyHandler implements ApiHandler {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
"X-Title": "Cline",
|
||||
},
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Requesty client: ${error.message}`)
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -32,7 +31,6 @@ export class SambanovaHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.sambanova.ai/v1",
|
||||
apiKey: this.options.sambanovaApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating SambaNova client: ${error.message}`)
|
||||
|
||||
@@ -8,7 +8,6 @@ import { ChatMessages, LlmModuleConfig, OrchestrationClient, TemplatingModuleCon
|
||||
import { ModelInfo, SapAiCoreModelId, sapAiCoreDefaultModelId, sapAiCoreModels } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -385,7 +384,6 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
const tokenUrl = this.options.sapAiCoreTokenUrl!.replace(/\/+$/, "") + "/oauth/token"
|
||||
const response = await axios.post(tokenUrl, payload, {
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
...getAxiosSettings(),
|
||||
})
|
||||
const token = response.data as Token
|
||||
token.expires_at = Date.now() + token.expires_in * 1000
|
||||
@@ -412,7 +410,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
const url = `${this.options.sapAiCoreBaseUrl}/v2/lm/deployments?$top=10000&$skip=0`
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, { headers, ...getAxiosSettings() })
|
||||
const response = await axios.get(url, { headers })
|
||||
const deployments = response.data.resources
|
||||
|
||||
return deployments
|
||||
@@ -681,11 +679,10 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
const response = await axios.post(url, JSON.stringify(payload, null, 2), {
|
||||
headers,
|
||||
responseType: "stream",
|
||||
...getAxiosSettings(),
|
||||
})
|
||||
|
||||
if (model.id === "o3-mini") {
|
||||
const response = await axios.post(url, JSON.stringify(payload, null, 2), { headers, ...getAxiosSettings() })
|
||||
const response = await axios.post(url, JSON.stringify(payload, null, 2), { headers })
|
||||
|
||||
// Yield the usage information
|
||||
if (response.data.usage) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -32,7 +31,6 @@ export class TogetherHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.together.xyz/v1",
|
||||
apiKey: this.options.togetherApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Together client: ${error.message}`)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { ModelInfo, vercelAiGatewayDefaultModelId, vercelAiGatewayDefaultModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -11,9 +10,8 @@ import { createVercelAIGatewayStream } from "../transform/vercel-ai-gateway-stre
|
||||
|
||||
interface VercelAIGatewayHandlerOptions extends CommonApiHandlerOptions {
|
||||
vercelAiGatewayApiKey?: string
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
thinkingBudgetTokens?: number
|
||||
vercelAiGatewayModelId?: string
|
||||
vercelAiGatewayModelInfo?: ModelInfo
|
||||
}
|
||||
|
||||
export class VercelAIGatewayHandler implements ApiHandler {
|
||||
@@ -37,7 +35,6 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
"http-referer": "https://cline.bot",
|
||||
"x-title": "Cline",
|
||||
},
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Vercel AI Gateway client: ${error.message}`)
|
||||
@@ -58,7 +55,6 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
systemPrompt,
|
||||
messages,
|
||||
{ id: modelId, info: modelInfo },
|
||||
this.options.thinkingBudgetTokens,
|
||||
tools,
|
||||
)
|
||||
let didOutputUsage: boolean = false
|
||||
@@ -78,29 +74,6 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
// Reasoning tokens are returned separately from the content
|
||||
if ("reasoning" in delta && delta.reasoning) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
// Reasoning details that can be passed back in API requests to preserve reasoning traces
|
||||
if (
|
||||
"reasoning_details" in delta &&
|
||||
delta.reasoning_details &&
|
||||
// @ts-ignore-next-line
|
||||
delta.reasoning_details.length // exists and non-0
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
const inputTokens = chunk.usage.prompt_tokens || 0
|
||||
const outputTokens =
|
||||
@@ -134,11 +107,11 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.openRouterModelId
|
||||
const modelInfo = this.options.openRouterModelInfo
|
||||
const modelId = this.options.vercelAiGatewayModelId
|
||||
const modelInfo = this.options.vercelAiGatewayModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
}
|
||||
return { id: openRouterDefaultModelId, info: openRouterDefaultModelInfo }
|
||||
return { id: vercelAiGatewayDefaultModelId, info: vercelAiGatewayDefaultModelInfo }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ModelInfo, VertexModelId, vertexDefaultModelId, vertexModels } from "@s
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { GeminiHandler } from "./gemini"
|
||||
|
||||
@@ -123,7 +122,46 @@ export class VertexHandler implements ApiHandler {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
messages: sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex),
|
||||
messages: messages.map((message, index) => {
|
||||
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
}
|
||||
}
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
}
|
||||
}),
|
||||
stream: true,
|
||||
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
@@ -149,7 +187,18 @@ export class VertexHandler implements ApiHandler {
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
messages: messages.map((message) => ({
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
})),
|
||||
stream: true,
|
||||
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
@@ -166,7 +215,7 @@ export class VertexHandler implements ApiHandler {
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start": {
|
||||
case "message_start":
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
type: "usage",
|
||||
@@ -176,7 +225,6 @@ export class VertexHandler implements ApiHandler {
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
break
|
||||
}
|
||||
case "message_delta":
|
||||
yield {
|
||||
type: "usage",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -34,7 +33,6 @@ export class XAIHandler implements ApiHandler {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://api.x.ai/v1",
|
||||
apiKey: this.options.xaiApiKey,
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating xAI client: ${error.message}`)
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { version as extensionVersion } from "../../../../package.json"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -49,7 +48,6 @@ export class ZAiHandler implements ApiHandler {
|
||||
"X-Title": "Cline",
|
||||
"X-Cline-Version": extensionVersion,
|
||||
},
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating Z AI client: ${error.message}`)
|
||||
|
||||
@@ -1,76 +1,19 @@
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
|
||||
/**
|
||||
* Sanitize Anthropic messages by removing reasoning details and adding ephemeral cache control
|
||||
* to the last two user messages to prevent them from being stored in Anthropic's cache.
|
||||
* Converts an OpenAI ChatCompletionTool into an Anthropic Tool definition
|
||||
*/
|
||||
export function sanitizeAnthropicMessages(
|
||||
messages: Array<ClineStorageMessage>,
|
||||
lastUserMsgIndex?: number,
|
||||
secondLastMsgUserIndex?: number,
|
||||
): Array<Anthropic.Messages.MessageParam> {
|
||||
return messages.map((_message, index) => {
|
||||
const message = removeUnknownParams(_message)
|
||||
const addCacheControl = lastUserMsgIndex !== undefined && secondLastMsgUserIndex !== undefined
|
||||
// Construct message
|
||||
if (addCacheControl && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
}
|
||||
}
|
||||
export function openAIToolToAnthropic(openAITool: OpenAITool): AnthropicTool {
|
||||
const func = openAITool.function
|
||||
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove reasoning details and other known params that are not Anthropic specific.
|
||||
*/
|
||||
function removeUnknownParams(param: ClineStorageMessage): Anthropic.Messages.MessageParam {
|
||||
// Construct new content array with known Anthropic content blocks only.
|
||||
return {
|
||||
role: param.role === "user" ? "user" : "assistant",
|
||||
content: Array.isArray(param.content)
|
||||
? param.content.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// Ensure reasoning_details is removed
|
||||
reasoning_details: undefined,
|
||||
}
|
||||
})
|
||||
: param.content, // String content remains unchanged
|
||||
name: func.name,
|
||||
description: func.description || "",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: func.parameters?.properties || {},
|
||||
required: func.parameters?.required || [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,44 +5,40 @@ export function convertAnthropicContentToGemini(content: string | Anthropic.Cont
|
||||
if (typeof content === "string") {
|
||||
return [{ text: content }]
|
||||
}
|
||||
return content
|
||||
.flatMap((block): Part | undefined => {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { text: block.text }
|
||||
case "image":
|
||||
if (block.source.type !== "base64") {
|
||||
throw new Error("Unsupported image source type")
|
||||
}
|
||||
return {
|
||||
inlineData: {
|
||||
data: block.source.data,
|
||||
mimeType: block.source.media_type,
|
||||
return content.flatMap((block): Part => {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { text: block.text }
|
||||
case "image":
|
||||
if (block.source.type !== "base64") {
|
||||
throw new Error("Unsupported image source type")
|
||||
}
|
||||
return {
|
||||
inlineData: {
|
||||
data: block.source.data,
|
||||
mimeType: block.source.media_type,
|
||||
},
|
||||
}
|
||||
case "tool_use":
|
||||
return {
|
||||
functionCall: {
|
||||
name: block.name,
|
||||
args: block.input as Record<string, unknown>,
|
||||
},
|
||||
}
|
||||
case "tool_result":
|
||||
return {
|
||||
functionResponse: {
|
||||
name: block.tool_use_id,
|
||||
response: {
|
||||
result: block.content,
|
||||
},
|
||||
}
|
||||
case "tool_use":
|
||||
return {
|
||||
functionCall: {
|
||||
name: block.name,
|
||||
args: block.input as Record<string, unknown>,
|
||||
},
|
||||
}
|
||||
case "tool_result":
|
||||
return {
|
||||
functionResponse: {
|
||||
name: block.tool_use_id,
|
||||
response: {
|
||||
result: block.content,
|
||||
},
|
||||
},
|
||||
}
|
||||
case "thinking":
|
||||
return { text: block.thinking, thought: true, thoughtSignature: block.signature }
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
.filter((part): part is Part => part !== undefined) // Filter out unsupported blocks
|
||||
},
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported content block type: ${block.type}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function convertAnthropicMessageToGemini(message: Anthropic.Messages.MessageParam): Content {
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Message } from "ollama"
|
||||
import {
|
||||
ClineAssistantToolUseBlock,
|
||||
ClineImageContentBlock,
|
||||
ClineStorageMessage,
|
||||
ClineTextContentBlock,
|
||||
ClineUserToolResultContentBlock,
|
||||
} from "@/shared/messages/content"
|
||||
|
||||
export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[]): Message[] {
|
||||
export function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
const ollamaMessages: Message[] = []
|
||||
|
||||
for (const anthropicMessage of anthropicMessages) {
|
||||
@@ -19,8 +13,8 @@ export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMess
|
||||
} else {
|
||||
if (anthropicMessage.role === "user") {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
|
||||
toolMessages: ClineUserToolResultContentBlock[]
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolResultBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_result") {
|
||||
@@ -76,8 +70,8 @@ export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMess
|
||||
}
|
||||
} else if (anthropicMessage.role === "assistant") {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
|
||||
toolMessages: ClineAssistantToolUseBlock[]
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolUseBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_use") {
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import {
|
||||
ClineAssistantRedactedThinkingBlock,
|
||||
ClineAssistantThinkingBlock,
|
||||
ClineAssistantToolUseBlock,
|
||||
ClineImageContentBlock,
|
||||
ClineStorageMessage,
|
||||
ClineTextContentBlock,
|
||||
ClineUserToolResultContentBlock,
|
||||
} from "@/shared/messages/content"
|
||||
|
||||
export function convertToOpenAiMessages(
|
||||
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
|
||||
anthropicMessages: Anthropic.Messages.MessageParam[],
|
||||
): OpenAI.Chat.ChatCompletionMessageParam[] {
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = []
|
||||
|
||||
@@ -32,8 +23,8 @@ export function convertToOpenAiMessages(
|
||||
*/
|
||||
if (anthropicMessage.role === "user") {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
|
||||
toolMessages: ClineUserToolResultContentBlock[]
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolResultBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_result") {
|
||||
@@ -47,7 +38,7 @@ export function convertToOpenAiMessages(
|
||||
)
|
||||
|
||||
// Process tool result messages FIRST since they must follow the tool use messages
|
||||
const toolResultImages: ClineImageContentBlock[] = []
|
||||
const toolResultImages: Anthropic.Messages.ImageBlockParam[] = []
|
||||
toolMessages.forEach((toolMessage) => {
|
||||
// The Anthropic SDK allows tool results to be a string or an array of text and image blocks, enabling rich and structured content. In contrast, the OpenAI SDK only supports tool results as a single string, so we map the Anthropic tool result parts into one concatenated string to maintain compatibility.
|
||||
let content: string
|
||||
@@ -111,13 +102,8 @@ export function convertToOpenAiMessages(
|
||||
}
|
||||
} else if (anthropicMessage.role === "assistant") {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (
|
||||
| ClineTextContentBlock
|
||||
| ClineImageContentBlock
|
||||
| ClineAssistantThinkingBlock
|
||||
| ClineAssistantRedactedThinkingBlock
|
||||
)[]
|
||||
toolMessages: ClineAssistantToolUseBlock[]
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolUseBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_use") {
|
||||
@@ -133,7 +119,6 @@ export function convertToOpenAiMessages(
|
||||
// Process non-tool messages
|
||||
let content: string | undefined
|
||||
const reasoningDetails: any[] = []
|
||||
const thinkingBlock = []
|
||||
if (nonToolMessages.length > 0) {
|
||||
nonToolMessages.forEach((part) => {
|
||||
// @ts-ignore-next-line
|
||||
@@ -149,16 +134,13 @@ export function convertToOpenAiMessages(
|
||||
// @ts-ignore-next-line
|
||||
// delete part.reasoning_details
|
||||
}
|
||||
if (part.type === "thinking" && part.thinking) {
|
||||
thinkingBlock.push(part)
|
||||
}
|
||||
})
|
||||
content = nonToolMessages
|
||||
.map((part) => {
|
||||
if (part.type === "text" && part.text) {
|
||||
return part.text
|
||||
if (part.type === "image") {
|
||||
return "" // impossible as the assistant cannot send images
|
||||
}
|
||||
return ""
|
||||
return part.text
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk>
|
||||
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamThinkingChunk | ApiStreamUsageChunk | ApiStreamToolCallsChunk
|
||||
export type ApiStreamChunk =
|
||||
| ApiStreamTextChunk
|
||||
| ApiStreamReasoningChunk
|
||||
| ApiStreamReasoningDetailsChunk
|
||||
| ApiStreamAnthropicThinkingChunk
|
||||
| ApiStreamAnthropicRedactedThinkingChunk
|
||||
| ApiStreamUsageChunk
|
||||
| ApiStreamToolCallsChunk
|
||||
|
||||
export interface ApiStreamTextChunk {
|
||||
type: "text"
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface ApiStreamReasoningChunk {
|
||||
type: "reasoning"
|
||||
reasoning: string
|
||||
}
|
||||
|
||||
export interface ApiStreamReasoningDetailsChunk {
|
||||
type: "reasoning_details"
|
||||
reasoning_details: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
}
|
||||
|
||||
export interface ApiStreamAnthropicThinkingChunk {
|
||||
type: "ant_thinking"
|
||||
thinking: string
|
||||
signature: string
|
||||
}
|
||||
|
||||
export interface ApiStreamAnthropicRedactedThinkingChunk {
|
||||
type: "ant_redacted_thinking"
|
||||
data: string
|
||||
}
|
||||
|
||||
export interface ApiStreamUsageChunk {
|
||||
type: "usage"
|
||||
inputTokens: number
|
||||
@@ -30,11 +58,3 @@ export interface ApiStreamToolCall {
|
||||
arguments?: any
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApiStreamThinkingChunk {
|
||||
type: "reasoning"
|
||||
reasoning: string
|
||||
details?: unknown // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
signature?: string
|
||||
redacted_data?: string
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export function getOpenAIToolParams(tools?: OpenAITool[]) {
|
||||
? {
|
||||
tools,
|
||||
tool_choice: tools ? ("auto" as ChatCompletionToolChoiceOption) : undefined,
|
||||
parallel_tool_calls: tools ? false : undefined, // Set to false to force single tool calls
|
||||
parallel_tool_calls: tools ? true : undefined,
|
||||
}
|
||||
: {
|
||||
tools: undefined,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import type { ToolUse } from "@core/assistant-message"
|
||||
import { JSONParser } from "@streamparser/json"
|
||||
import { McpHub } from "@/services/mcp/McpHub"
|
||||
import { CLINE_MCP_TOOL_IDENTIFIER } from "@/shared/mcp"
|
||||
import { ClineAssistantToolUseBlock } from "@/shared/messages/content"
|
||||
import { ClineDefaultTool } from "@/shared/tools"
|
||||
|
||||
export interface PendingToolUse {
|
||||
@@ -32,7 +32,7 @@ const ESCAPE_MAP: Record<string, string> = {
|
||||
const ESCAPE_PATTERN = /\\[ntr"\\]/g
|
||||
|
||||
/**
|
||||
* Handles streaming native tool use blocks and converts them to ClineAssistantToolUseBlock format
|
||||
* Handles streaming tool use blocks and converts them to Anthropic.ToolUseBlockParam format
|
||||
*/
|
||||
export class ToolUseHandler {
|
||||
private pendingToolUses = new Map<string, PendingToolUse>()
|
||||
@@ -60,7 +60,7 @@ export class ToolUseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getFinalizedToolUse(id: string): ClineAssistantToolUseBlock | undefined {
|
||||
getFinalizedToolUse(id: string): Anthropic.ToolUseBlockParam | undefined {
|
||||
const pending = this.pendingToolUses.get(id)
|
||||
if (!pending?.name) {
|
||||
return undefined
|
||||
@@ -85,8 +85,8 @@ export class ToolUseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getAllFinalizedToolUses(): ClineAssistantToolUseBlock[] {
|
||||
const results: ClineAssistantToolUseBlock[] = []
|
||||
getAllFinalizedToolUses(): Anthropic.ToolUseBlockParam[] {
|
||||
const results: Anthropic.ToolUseBlockParam[] = []
|
||||
for (const id of this.pendingToolUses.keys()) {
|
||||
const toolUse = this.getFinalizedToolUse(id)
|
||||
if (toolUse) {
|
||||
@@ -130,7 +130,6 @@ export class ToolUseHandler {
|
||||
arguments: JSON.stringify(input),
|
||||
},
|
||||
partial: true,
|
||||
isNativeToolCall: true,
|
||||
})
|
||||
} else {
|
||||
const params: Record<string, string> = {}
|
||||
@@ -144,7 +143,6 @@ export class ToolUseHandler {
|
||||
name: pending.name as ClineDefaultTool,
|
||||
params: params as any,
|
||||
partial: true,
|
||||
isNativeToolCall: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import {
|
||||
CLAUDE_SONNET_1M_SUFFIX,
|
||||
ModelInfo,
|
||||
openRouterClaudeSonnet41mModelId,
|
||||
openRouterClaudeSonnet451mModelId,
|
||||
} from "@shared/api"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -15,7 +10,6 @@ export async function createVercelAIGatewayStream(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
model: { id: string; info: ModelInfo },
|
||||
thinkingBudgetTokens?: number,
|
||||
tools?: OpenAITool[],
|
||||
) {
|
||||
// Convert Anthropic messages to OpenAI format
|
||||
@@ -24,15 +18,9 @@ export async function createVercelAIGatewayStream(
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const isClaudeSonnet1m = model.id === openRouterClaudeSonnet41mModelId || model.id === openRouterClaudeSonnet451mModelId
|
||||
if (isClaudeSonnet1m) {
|
||||
// remove the custom :1m suffix, to create the model id openrouter API expects
|
||||
model.id = model.id.slice(0, -CLAUDE_SONNET_1M_SUFFIX.length)
|
||||
}
|
||||
|
||||
const isAnthropicModel = model.id.startsWith("anthropic/")
|
||||
|
||||
if (isAnthropicModel) {
|
||||
if (isAnthropicModel && model.info.supportsPromptCache) {
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
@@ -58,32 +46,12 @@ export async function createVercelAIGatewayStream(
|
||||
})
|
||||
}
|
||||
|
||||
// Configure reasoning parameters similar to OpenRouter
|
||||
let temperature: number | undefined = 0
|
||||
let reasoning: { max_tokens: number } | undefined
|
||||
|
||||
if (isAnthropicModel) {
|
||||
const budget_tokens = thinkingBudgetTokens || 0
|
||||
const reasoningOn = budget_tokens !== 0
|
||||
if (reasoningOn) {
|
||||
temperature = undefined // extended thinking does not support non-1 temperature
|
||||
reasoning = { max_tokens: budget_tokens }
|
||||
}
|
||||
} else if (thinkingBudgetTokens && model.info?.thinkingConfig && thinkingBudgetTokens > 0) {
|
||||
temperature = undefined // extended thinking does not support non-1 temperature
|
||||
reasoning = { max_tokens: thinkingBudgetTokens }
|
||||
}
|
||||
|
||||
// @ts-ignore-next-line
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens,
|
||||
temperature: temperature,
|
||||
temperature: 0.7,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
include_reasoning: true,
|
||||
...(reasoning ? { reasoning } : {}),
|
||||
...getOpenAIToolParams(tools),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { ClineDefaultTool } from "@shared/tools"
|
||||
|
||||
export type AssistantMessageContent = TextStreamContent | ToolUse | ReasoningStreamContent
|
||||
export type AssistantMessageContent = TextContent | ToolUse
|
||||
|
||||
export { parseAssistantMessageV2 } from "./parse-assistant-message"
|
||||
|
||||
export interface TextStreamContent {
|
||||
export interface TextContent {
|
||||
type: "text"
|
||||
content: string
|
||||
partial: boolean
|
||||
@@ -52,16 +51,4 @@ export interface ToolUse {
|
||||
// params is a partial record, allowing only some or none of the possible parameters to be used
|
||||
params: Partial<Record<ToolParamName, string>>
|
||||
partial: boolean
|
||||
// Whether this tool use was initiated by a native tool call
|
||||
isNativeToolCall?: boolean
|
||||
}
|
||||
|
||||
export interface ReasoningStreamContent {
|
||||
type: "reasoning"
|
||||
reasoning: string
|
||||
details?: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
signature?: string
|
||||
redacted?: boolean // whether this reasoning block has been redacted
|
||||
data?: string // redacted data
|
||||
partial: boolean
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ClineDefaultTool, toolUseNames } from "@shared/tools"
|
||||
import { AssistantMessageContent, TextStreamContent, ToolParamName, ToolUse, toolParamNames } from "." // Assuming types are defined in index.ts or a similar file
|
||||
import { AssistantMessageContent, TextContent, ToolParamName, ToolUse, toolParamNames } from "." // Assuming types are defined in index.ts or a similar file
|
||||
|
||||
// parseAssistantmessageV1 removed in https://github.com/cline/cline/pull/5425
|
||||
|
||||
@@ -27,7 +27,7 @@ import { AssistantMessageContent, TextStreamContent, ToolParamName, ToolUse, too
|
||||
export function parseAssistantMessageV2(assistantMessage: string): AssistantMessageContent[] {
|
||||
const contentBlocks: AssistantMessageContent[] = []
|
||||
let currentTextContentStart = 0 // Index where the current text block started
|
||||
let currentTextContent: TextStreamContent | undefined
|
||||
let currentTextContent: TextContent | undefined
|
||||
let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use
|
||||
let currentToolUse: ToolUse | undefined
|
||||
let currentParamValueStart = 0 // Index *after* the opening tag of the current param
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getRuleFilesTotalContent, synchronizeRuleToggles } from "@core/context/instructions/user-instructions/rule-helpers"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { ensureRulesDirectoryExists, GlobalFileNames } from "@core/storage/disk"
|
||||
import { StateManager } from "@core/storage/StateManager"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
@@ -9,48 +8,27 @@ import path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
|
||||
export const getGlobalClineRules = async (globalClineRulesFilePath: string, toggles: ClineRulesToggles) => {
|
||||
let combinedContent = ""
|
||||
|
||||
// 1. Get file-based rules
|
||||
if (await fileExistsAtPath(globalClineRulesFilePath)) {
|
||||
if (await isDirectory(globalClineRulesFilePath)) {
|
||||
try {
|
||||
const rulesFilePaths = await readDirectory(globalClineRulesFilePath)
|
||||
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, globalClineRulesFilePath, toggles)
|
||||
if (rulesFilesTotalContent) {
|
||||
combinedContent = rulesFilesTotalContent
|
||||
const clineRulesFileInstructions = formatResponse.clineRulesGlobalDirectoryInstructions(
|
||||
globalClineRulesFilePath,
|
||||
rulesFilesTotalContent,
|
||||
)
|
||||
return clineRulesFileInstructions
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .clinerules directory at ${globalClineRulesFilePath}`)
|
||||
}
|
||||
} else {
|
||||
console.error(`${globalClineRulesFilePath} is not a directory`)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Append remote config rules
|
||||
const stateManager = StateManager.get()
|
||||
const remoteConfigSettings = stateManager.getRemoteConfigSettings()
|
||||
const remoteRules = remoteConfigSettings.remoteGlobalRules || []
|
||||
const remoteToggles = stateManager.getGlobalStateKey("remoteRulesToggles") || {}
|
||||
|
||||
for (const rule of remoteRules) {
|
||||
// If alwaysEnabled, always include; otherwise check toggle
|
||||
const isEnabled = rule.alwaysEnabled || remoteToggles[rule.name] !== false
|
||||
|
||||
if (isEnabled) {
|
||||
if (combinedContent) {
|
||||
combinedContent += "\n\n"
|
||||
}
|
||||
combinedContent += `${rule.name}\n${rule.contents}`
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return formatted instructions
|
||||
if (combinedContent) {
|
||||
return formatResponse.clineRulesGlobalDirectoryInstructions(globalClineRulesFilePath, combinedContent)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from "@core/context/instructions/user-instructions/rule-helpers"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { listFiles } from "@services/glob/list-files"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { fileExistsAtPath, isDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
@@ -14,7 +13,7 @@ import path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
|
||||
/**
|
||||
* Refreshes the toggles for windsurf, cursor, and agents rules
|
||||
* Refreshes the toggles for windsurf and cursor rules
|
||||
*/
|
||||
export async function refreshExternalRulesToggles(
|
||||
controller: Controller,
|
||||
@@ -22,7 +21,6 @@ export async function refreshExternalRulesToggles(
|
||||
): Promise<{
|
||||
windsurfLocalToggles: ClineRulesToggles
|
||||
cursorLocalToggles: ClineRulesToggles
|
||||
agentsLocalToggles: ClineRulesToggles
|
||||
}> {
|
||||
// local windsurf toggles
|
||||
const localWindsurfRulesToggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
|
||||
@@ -44,16 +42,9 @@ export async function refreshExternalRulesToggles(
|
||||
const updatedLocalCursorToggles = combineRuleToggles(updatedLocalCursorToggles1, updatedLocalCursorToggles2)
|
||||
controller.stateManager.setWorkspaceState("localCursorRulesToggles", updatedLocalCursorToggles)
|
||||
|
||||
// local agents toggles
|
||||
const localAgentsRulesToggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
const localAgentsRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.agentsRulesFile)
|
||||
const updatedLocalAgentsToggles = await synchronizeRuleToggles(localAgentsRulesFilePath, localAgentsRulesToggles)
|
||||
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", updatedLocalAgentsToggles)
|
||||
|
||||
return {
|
||||
windsurfLocalToggles: updatedLocalWindsurfToggles,
|
||||
cursorLocalToggles: updatedLocalCursorToggles,
|
||||
agentsLocalToggles: updatedLocalAgentsToggles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,76 +117,3 @@ export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
|
||||
return [cursorRulesFileInstructions, cursorRulesDirInstructions]
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to find all agents.md files recursively (case-insensitive)
|
||||
* Only searches if a top-level agents.md file exists
|
||||
*/
|
||||
async function findAgentsMdFiles(cwd: string): Promise<string[]> {
|
||||
try {
|
||||
// First check if top-level agents.md exists
|
||||
const topLevelAgentsPath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
const topLevelExists = await fileExistsAtPath(topLevelAgentsPath)
|
||||
|
||||
// Only search recursively if top-level agents.md exists
|
||||
if (!topLevelExists) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Search recursively for all agents.md files
|
||||
const [allFiles] = await listFiles(cwd, true, 500)
|
||||
return allFiles.filter((filePath) => {
|
||||
const basename = path.basename(filePath).toLowerCase()
|
||||
return basename === GlobalFileNames.agentsRulesFile.toLowerCase()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to find agents.md files in ${cwd}:`, error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather formatted agents rules - searches recursively and combines all agents.md files
|
||||
*/
|
||||
export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
const agentsRulesFilePath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
|
||||
// Check if the top-level agents.md file is enabled
|
||||
if (agentsRulesFilePath in toggles && toggles[agentsRulesFilePath] === false) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const agentsMdFiles = await findAgentsMdFiles(cwd)
|
||||
|
||||
if (agentsMdFiles.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Read and combine all agents.md files
|
||||
const combinedContent = await Promise.all(
|
||||
agentsMdFiles.map(async (filePath) => {
|
||||
try {
|
||||
const fullPath = path.resolve(cwd, filePath)
|
||||
const content = (await fs.readFile(fullPath, "utf8")).trim()
|
||||
if (content) {
|
||||
const relativePath = path.relative(cwd, fullPath)
|
||||
return `## ${relativePath}\n\n${content}`
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error(`Failed to read agents.md file at ${filePath}:`, error)
|
||||
return null
|
||||
}
|
||||
}),
|
||||
).then((contents) => contents.filter(Boolean).join("\n\n"))
|
||||
|
||||
if (combinedContent) {
|
||||
return formatResponse.agentsRulesLocalFileInstructions(cwd, combinedContent)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to read agents.md files:", error)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ensureRulesDirectoryExists, ensureWorkflowsDirectoryExists, GlobalFileNames } from "@core/storage/disk"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { GlobalInstructionsFile } from "@shared/remote-config/schema"
|
||||
import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
@@ -102,36 +101,6 @@ export async function synchronizeRuleToggles(
|
||||
return updatedToggles
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes remote rule toggles with current remote config
|
||||
* Removes toggles for rules that no longer exist, adds defaults for new rules
|
||||
*/
|
||||
export function synchronizeRemoteRuleToggles(
|
||||
remoteRules: GlobalInstructionsFile[],
|
||||
currentToggles: ClineRulesToggles,
|
||||
): ClineRulesToggles {
|
||||
const updatedToggles: ClineRulesToggles = {}
|
||||
|
||||
// Create set of current remote rule names
|
||||
const existingRuleNames = new Set(remoteRules.map((rule) => rule.name))
|
||||
|
||||
// Keep toggles only for rules that still exist
|
||||
for (const [ruleName, enabled] of Object.entries(currentToggles)) {
|
||||
if (existingRuleNames.has(ruleName)) {
|
||||
updatedToggles[ruleName] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// Add default toggles for new rules (default to enabled)
|
||||
for (const rule of remoteRules) {
|
||||
if (!(rule.name in updatedToggles)) {
|
||||
updatedToggles[rule.name] = true
|
||||
}
|
||||
}
|
||||
|
||||
return updatedToggles
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain project rules have more than a single location where rules are allowed to be stored
|
||||
*/
|
||||
@@ -299,10 +268,6 @@ export async function deleteRuleFile(
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
|
||||
delete toggles[rulePath]
|
||||
controller.stateManager.setWorkspaceState("localWindsurfRulesToggles", toggles)
|
||||
} else if (type === "agents") {
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
delete toggles[rulePath]
|
||||
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", toggles)
|
||||
} else {
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localClineRulesToggles")
|
||||
delete toggles[rulePath]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Empty, StringRequest } from "@shared/proto/cline/common"
|
||||
import { toRequestyServiceUrl } from "@shared/providers/requesty"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { toRequestyServiceUrl } from "@/shared/clients/requesty"
|
||||
import { openExternal } from "@/utils/env"
|
||||
import { Controller } from ".."
|
||||
|
||||
|
||||
@@ -16,10 +16,7 @@ export async function refreshRules(controller: Controller, _request: EmptyReques
|
||||
try {
|
||||
const cwd = await getCwd(getDesktopDir())
|
||||
const { globalToggles, localToggles } = await refreshClineRulesToggles(controller, cwd)
|
||||
const { cursorLocalToggles, windsurfLocalToggles, agentsLocalToggles } = await refreshExternalRulesToggles(
|
||||
controller,
|
||||
cwd,
|
||||
)
|
||||
const { cursorLocalToggles, windsurfLocalToggles } = await refreshExternalRulesToggles(controller, cwd)
|
||||
const { localWorkflowToggles, globalWorkflowToggles } = await refreshWorkflowToggles(controller, cwd)
|
||||
|
||||
return RefreshedRules.create({
|
||||
@@ -27,7 +24,6 @@ export async function refreshRules(controller: Controller, _request: EmptyReques
|
||||
localClineRulesToggles: { toggles: localToggles },
|
||||
localCursorRulesToggles: { toggles: cursorLocalToggles },
|
||||
localWindsurfRulesToggles: { toggles: windsurfLocalToggles },
|
||||
localAgentsRulesToggles: { toggles: agentsLocalToggles },
|
||||
localWorkflowToggles: { toggles: localWorkflowToggles },
|
||||
globalWorkflowToggles: { toggles: globalWorkflowToggles },
|
||||
})
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { ToggleAgentsRuleRequest } from "@shared/proto/cline/file"
|
||||
import { ClineRulesToggles } from "@shared/proto/cline/file"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
* Toggles an Agents rule (enable or disable)
|
||||
* @param controller The controller instance
|
||||
* @param request The toggle request
|
||||
* @returns The updated Agents rule toggles
|
||||
*/
|
||||
export async function toggleAgentsRule(controller: Controller, request: ToggleAgentsRuleRequest): Promise<ClineRulesToggles> {
|
||||
const { rulePath, enabled } = request
|
||||
|
||||
if (!rulePath || typeof enabled !== "boolean") {
|
||||
console.error("toggleAgentsRule: Missing or invalid parameters", {
|
||||
rulePath,
|
||||
enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`,
|
||||
})
|
||||
throw new Error("Missing or invalid parameters for toggleAgentsRule")
|
||||
}
|
||||
|
||||
// Update the toggle in workspace state
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
toggles[rulePath] = enabled
|
||||
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", toggles)
|
||||
|
||||
// Get the current state to return in the response
|
||||
const agentsToggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
|
||||
return ClineRulesToggles.create({
|
||||
toggles: agentsToggles,
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user