mirror of
https://github.com/cline/cline.git
synced 2026-09-06 12:28:08 +08:00
Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9faa5f0dc4 | |||
| 4d395deefd | |||
| 834a5b1df2 | |||
| 3089233298 | |||
| f2b7347a5c | |||
| 04bfef75cf | |||
| 716e8f236b | |||
| f2ddab71f1 | |||
| 0b56a45a65 | |||
| d6ebd2438a | |||
| 4c07c7e5c5 | |||
| ab66a5fd93 | |||
| bd1d6159fc | |||
| 7f2d28716f | |||
| 653727db2a | |||
| d3c2f1878d | |||
| ba92be9401 | |||
| 66eb5a62ba | |||
| 499ee22b3b | |||
| 7abeae5019 | |||
| accf47cb52 | |||
| 556d3e6f79 | |||
| 9e35048db2 | |||
| 9b0f2b82ef | |||
| c2c23054b9 | |||
| 3baaa5c8b4 | |||
| abafcc7290 | |||
| d82aa0add9 | |||
| abe4721a0b | |||
| 60d55b69a8 | |||
| d18e0271d3 | |||
| af71f9da90 | |||
| 2a1c8826aa | |||
| 9a54f2d246 | |||
| d928d58a40 | |||
| 31859b5fda | |||
| 0d5d89e8c7 | |||
| af34451eec | |||
| 027a4f6386 | |||
| 49642882c5 | |||
| 21ed6bc432 | |||
| da2689f885 | |||
| 5049326f02 | |||
| b02ce46a57 | |||
| de974737c8 | |||
| d072156e9a | |||
| 4939309a09 | |||
| 1a07ca7906 | |||
| c1eefbad3f | |||
| 1bfdce9b84 | |||
| b002cdacdb | |||
| cf4005b25e | |||
| 1494d145d5 | |||
| 1ab4b3cc24 | |||
| 535b653228 | |||
| 1335fa5452 | |||
| b2a4395f71 | |||
| ae34a3a8c5 | |||
| 0fb4a6c7e9 | |||
| 855db7d8d8 | |||
| bb375b78ca | |||
| 31af254f0a | |||
| 2745cdd54b | |||
| c94c6fd8f1 | |||
| a87664318e | |||
| ba70718c83 | |||
| cf8dd1c150 | |||
| e714c74ec4 | |||
| a6f8b33895 | |||
| 02abbcf045 | |||
| 8ca2706cca | |||
| e747d211e6 | |||
| 19aa81a1cc | |||
| f5f3654966 | |||
| 344b99988b | |||
| 7d3c39bcdb | |||
| 33c1692f8a | |||
| 3be3ffae4c | |||
| 87c6708fbe | |||
| b296706bc0 | |||
| 7fdc82ae23 | |||
| 246ed87350 | |||
| a28465151f | |||
| f1967bc3a1 | |||
| ccc83d6728 | |||
| c151d719c8 | |||
| cc25833963 | |||
| 2ef3b7cf0f | |||
| 19d7bd5198 | |||
| 121066f938 | |||
| f9b88318ea | |||
| 1d64f64f43 | |||
| 766e2e6a25 | |||
| 8bd7260350 | |||
| a1f4e8b9d4 | |||
| aa3e0860cd | |||
| 67bc9932e5 | |||
| ba98b44504 | |||
| fdeb639f75 | |||
| 76410b42b0 | |||
| e4e347a8a4 | |||
| 59e7c2b7b9 | |||
| 5371377b21 | |||
| 5a444dc30a | |||
| 34c48264c0 | |||
| daf14ef181 | |||
| f1bf9b3f90 | |||
| 6e20047c3e | |||
| 1d4fe88bec | |||
| 74dbbb9d6b | |||
| 3073cf40be | |||
| 50056768d6 | |||
| e7cd32b830 | |||
| d983b451e1 | |||
| bb32aac8e7 | |||
| e6d5f55fb2 | |||
| 93f60ebe5b | |||
| a3e000d5dd | |||
| 0076be531d | |||
| 9de4fc2a2f | |||
| 8a4b87a133 | |||
| 571480e3b6 | |||
| 5c2c264a1d | |||
| 04549cf1e3 | |||
| f2a93b6201 | |||
| 2f09b994dd | |||
| 8aac67bf75 | |||
| 10422dd3fc | |||
| 6191c681ce | |||
| 9ab7b41c24 | |||
| bddfb93b38 | |||
| a219eb8000 | |||
| 515b4e5d6a | |||
| aaa3846b82 | |||
| 8229dcfe88 | |||
| b4ea1a17a5 | |||
| e263c74325 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix: do not retry request automatically on auth failure.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixing the logic for Oca Token Refresh
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix task timeline display height.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Ensure tool arguments are streamed during file operations when native tool calling is enabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
add new provider AIhubmix
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added thinking level setting for Gemini 3.0 Pro
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: add thought signature support for Gemini SDK
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: Enable native tool calling for Baseten and Kimi K2 models
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix issues where assistant message with empty content is added to conversation history
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixes a bug where the checkbox shows in the model selector dropdown.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Replaces generic robot icon with Cline logo across VS Code UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add Kimi K2 Thinking to Baseten Provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Skip MCP tool with invalid name (e.g. name too long) when native tool calling is enabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
switch from defaultUserAgentProvider to customUserAgent for Bedrock
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Anthropic provider missing signature param when thinking is enabled.
|
||||
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in {1..100}; do
|
||||
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "PostToolUse hook custom errorMessage: foo"
|
||||
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PostToolUse hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in {1..100}; do
|
||||
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"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"
|
||||
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PreToolUse hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
+26
-59
@@ -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 specific workspace)
|
||||
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to the workspace the repo is part of)
|
||||
|
||||
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` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskStart` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskStart`
|
||||
|
||||
### 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` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskResume` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskResume`
|
||||
|
||||
### TaskCancel Hook
|
||||
- **When**: Runs when a task is cancelled by the user (only if there's actual active work or work was started)
|
||||
- **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)
|
||||
- **Purpose**: Clean up resources, log cancellation, save state
|
||||
- **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
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskCancel`
|
||||
- **Note**: This hook is NOT cancellable
|
||||
|
||||
### TaskComplete Hook
|
||||
### TaskComplete Hook (coming soon!)
|
||||
- **When**: Runs when a task is marked as complete
|
||||
- **Purpose**: Log completion status, perform final cleanup, generate reports
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskComplete` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskComplete`
|
||||
|
||||
### 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` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit`
|
||||
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit`
|
||||
|
||||
### PreToolUse Hook
|
||||
- **When**: Runs BEFORE a tool is executed
|
||||
- **Purpose**: Validate parameters, block execution, or add context
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PreToolUse` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse`
|
||||
- **Workspace Location**: `.clinerules/hooks/PreToolUse`
|
||||
|
||||
### PostToolUse Hook
|
||||
- **When**: Runs AFTER a tool completes
|
||||
- **Purpose**: Observe results, track patterns, or add context
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PostToolUse` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse`
|
||||
- **Workspace Location**: `.clinerules/hooks/PostToolUse`
|
||||
|
||||
### PreCompact Hook
|
||||
### PreCompact Hook (coming soon!)
|
||||
- **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` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PreCompact` (all platforms)
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact`
|
||||
- **Workspace Location**: `.clinerules/hooks/PreCompact`
|
||||
|
||||
## Cross-Platform Hook Format
|
||||
|
||||
@@ -74,13 +74,12 @@ 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**: No special permissions needed - hooks are executed through the shell
|
||||
- **Windows**: Not currently supported.
|
||||
|
||||
### 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
|
||||
@@ -98,12 +97,6 @@ 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.
|
||||
@@ -218,27 +211,6 @@ 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`)
|
||||
@@ -260,7 +232,7 @@ if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
|
||||
{
|
||||
"cancel": true,
|
||||
"errorMessage": "Cannot create .js files in TypeScript project",
|
||||
"contextModification": "WORKSPACE_RULES: Use .ts/.tsx extensions only"
|
||||
"contextModification": "Use .ts/.tsx extensions only"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
@@ -282,7 +254,7 @@ if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "FILE_OPERATIONS: Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
@@ -302,7 +274,7 @@ if [[ "$execution_time" -gt 5000 ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
@@ -328,7 +300,7 @@ echo '{"cancel": false}'
|
||||
Cline supports two levels of hooks:
|
||||
|
||||
### Global Hooks
|
||||
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux) or `%USERPROFILE%\Documents\Cline\Hooks\` (Windows)
|
||||
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
|
||||
- **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
|
||||
@@ -356,16 +328,12 @@ 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
|
||||
@@ -433,7 +401,6 @@ 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,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in {1..100}; do
|
||||
echo "TaskCancel running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel", false,
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "TaskCancel hook custom errorMessage: foo"
|
||||
"cancel": false,
|
||||
"contextModification": "TaskCancel response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskCancel hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in {1..100}; do
|
||||
echo "TaskResume running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "TaskResume hook custom errorMessage: foo"
|
||||
"contextModification": "TaskResume response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskResume hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in {1..100}; do
|
||||
echo "TaskStart running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "TaskStart hook custom errorMessage: foo"
|
||||
"contextModification": "TaskStart response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskStart hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in {1..100}; do
|
||||
echo "UserPromptSubmit running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
sleep 3
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
|
||||
"errorMessage": "UserPromptSubmit hook custom errorMessage: foo"
|
||||
"contextModification": "UserPromptSubmit response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "UserPromptSubmit hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
Vendored
+21
@@ -165,6 +165,27 @@
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "Open Storybook",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"storybook"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/webview-ui",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"serverReadyAction": {
|
||||
"pattern": "Local:.*http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
},
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+20
@@ -263,6 +263,26 @@
|
||||
"watch"
|
||||
],
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "storybook",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"label": "npm: storybook",
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -20,6 +20,9 @@ eslint-rules/**
|
||||
.husky/**
|
||||
.env
|
||||
|
||||
# cli
|
||||
cli/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
.nvmrc
|
||||
|
||||
@@ -1,5 +1,70 @@
|
||||
# Changelog
|
||||
|
||||
## [3.38.1]
|
||||
|
||||
### Fixed
|
||||
- Fixed handling of 'signature' field in sanitizeAnthropicContentBlock to properly preserve it when thinking is enabled, as required by Anthropic's API.
|
||||
|
||||
## [3.38.0]
|
||||
|
||||
### Added
|
||||
- Gemini 3 Pro Preview model
|
||||
- AquaVoice Avalon model for voice-to-text dictation
|
||||
|
||||
### Fixed
|
||||
- Automatic context truncation when AWS Bedrock token usage rate limits are exceeded
|
||||
- Removed new_task tool from system prompts, updated slash command prompts, and added helper function for native tool calling validation
|
||||
|
||||
|
||||
## [3.37.1]
|
||||
|
||||
- Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
|
||||
- Add AGENTS.md support
|
||||
- feat(models): Add free minimax/mimax-m2 model to the model picker
|
||||
|
||||
## [3.37.0]
|
||||
|
||||
### Added
|
||||
|
||||
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
|
||||
- Nous Research provider with Hermes 4 model family and custom system prompts
|
||||
- Switched to Aqua Voice's Avalon model in speech to text transcription
|
||||
- Added Linux support for speech to text
|
||||
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
|
||||
- Expanded HTTP proxy support throughout the codebase
|
||||
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Duplicate tool results prevention through existence checking
|
||||
- XML entity escaping in model content processor
|
||||
- Commit message generation in command palette
|
||||
- OpenAI Compatible provider temperature parameter type conversion
|
||||
|
||||
## Documentation
|
||||
|
||||
- Added missing proto generation step in CONTRIBUTING.md
|
||||
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)
|
||||
|
||||
## [3.36.1]
|
||||
|
||||
- fix: remove native tool calling support from Gemini and XAI provider due to invalid tool names issues
|
||||
- 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
|
||||
|
||||
+7
-1
@@ -46,7 +46,11 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
```bash
|
||||
npm run install:all
|
||||
```
|
||||
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
|
||||
4. Generate Protocol Buffer files (required before first build):
|
||||
```bash
|
||||
npm run protos
|
||||
```
|
||||
5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
|
||||
|
||||
|
||||
|
||||
@@ -85,8 +89,10 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
|
||||
2. **Local Development**
|
||||
- Run `npm run install:all` to install dependencies
|
||||
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
|
||||
- Run `npm run test` to run tests locally
|
||||
- Run → Start Debugging or `>Debug: Select and Start Debugging` and wait for a new VS Code instance to open
|
||||
- **Terminal Workflow**: Use `npm run dev` (generates protos + runs watch mode) or `npm run watch` (if protos already generated)
|
||||
- Before submitting PR, run `npm run format:fix` to format your code
|
||||
|
||||
3. **Linux-specific Setup**
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
<json>
|
||||
<![CDATA[
|
||||
{
|
||||
"fontFamily": "cline-bot",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0,
|
||||
"fontURL": "https://cline.bot",
|
||||
"designerURL": "https://cline.bot",
|
||||
"licenseURL": "https://cline.bot",
|
||||
"version": "Version 1.0",
|
||||
"fontId": "cline-bot",
|
||||
"psName": "cline-bot",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "cline-bot",
|
||||
"description": "Font generated by IcoMoon."
|
||||
}
|
||||
]]>
|
||||
</json>
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="cline-bot" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="cline" data-tags="cline" horiz-adv-x="977" d="M964.553 383.11l-60.285 121.406v69.495c0 115.545-92.939 209.321-207.647 209.321h-102.986c7.536 15.071 11.722 32.654 11.722 51.074 0 64.471-51.912 116.383-115.545 116.383s-115.545-51.912-115.545-116.383 4.186-35.166 11.722-51.074h-102.986c-114.708 0-207.647-93.776-207.647-209.321v-69.495l-61.959-121.406c-5.861-11.722-5.861-26.793 0-38.515l61.959-119.732v-69.495c0-115.545 92.939-209.321 207.647-209.321h415.294c114.708 0 207.647 93.776 207.647 209.321v69.495l60.285 119.732c5.861 11.722 5.861 25.956 0 38.515v0zM426.178 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132zM731.787 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132z" />
|
||||
</font></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Binary file not shown.
+66
-60
@@ -1,62 +1,68 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@ 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)
|
||||
@@ -170,6 +176,7 @@ func validateQuickSetupProvider(providerID string) (cline.ApiProvider, error) {
|
||||
cline.ApiProvider_XAI: true,
|
||||
cline.ApiProvider_CEREBRAS: true,
|
||||
cline.ApiProvider_OLLAMA: true,
|
||||
cline.ApiProvider_NOUSRESEARCH: true,
|
||||
}
|
||||
|
||||
if !supportedProviders[provider] {
|
||||
|
||||
@@ -26,6 +26,7 @@ func GetBYOProviderList() []BYOProviderOption {
|
||||
{Name: "Google Gemini", Provider: cline.ApiProvider_GEMINI},
|
||||
{Name: "Ollama", Provider: cline.ApiProvider_OLLAMA},
|
||||
{Name: "Cerebras", Provider: cline.ApiProvider_CEREBRAS},
|
||||
{Name: "NousResearch", Provider: cline.ApiProvider_NOUSRESEARCH},
|
||||
{Name: "Oracle Code Assist", Provider: cline.ApiProvider_OCA},
|
||||
}
|
||||
}
|
||||
@@ -100,6 +101,8 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
|
||||
return "e.g., qwen3-coder:30b"
|
||||
case cline.ApiProvider_CEREBRAS:
|
||||
return "e.g., gpt-oss-120b"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "e.g., Hermes-4-405B"
|
||||
case cline.ApiProvider_OCA:
|
||||
return "e.g., oca/llama4"
|
||||
default:
|
||||
|
||||
@@ -111,6 +111,7 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
cline.ApiProvider_GEMINI,
|
||||
cline.ApiProvider_OLLAMA,
|
||||
cline.ApiProvider_CEREBRAS,
|
||||
cline.ApiProvider_NOUSRESEARCH,
|
||||
cline.ApiProvider_OCA,
|
||||
cline.ApiProvider_HICAP,
|
||||
}
|
||||
@@ -213,13 +214,15 @@ 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 providerStr {
|
||||
switch normalizedStr {
|
||||
case "anthropic":
|
||||
return cline.ApiProvider_ANTHROPIC, true
|
||||
case "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
|
||||
case "openai", "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
|
||||
return cline.ApiProvider_OPENAI, true
|
||||
case "openai", "openai-native": // This is the native, official Open AI provider
|
||||
case "openai-native": // This is the native, official Open AI provider
|
||||
return cline.ApiProvider_OPENAI_NATIVE, true
|
||||
case "openrouter":
|
||||
return cline.ApiProvider_OPENROUTER, true
|
||||
@@ -239,6 +242,8 @@ func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
|
||||
return cline.ApiProvider_OCA, true
|
||||
case "hicap":
|
||||
return cline.ApiProvider_HICAP, true
|
||||
case "nousResearch":
|
||||
return cline.ApiProvider_NOUSRESEARCH, true
|
||||
default:
|
||||
return cline.ApiProvider_ANTHROPIC, false // Return 0 value with false
|
||||
}
|
||||
@@ -272,6 +277,8 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
|
||||
return "oca"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "nousResearch"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -351,6 +358,8 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
|
||||
return "Oracle Code Assist"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "Hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "NousResearch"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -473,6 +482,7 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
{cline.ApiProvider_OLLAMA, "ollamaBaseUrl"}, // Ollama uses baseUrl instead of API key
|
||||
{cline.ApiProvider_CEREBRAS, "cerebrasApiKey"},
|
||||
{cline.ApiProvider_HICAP, "hicapApiKey"},
|
||||
{cline.ApiProvider_NOUSRESEARCH, "nousResearchApiKey"},
|
||||
}
|
||||
|
||||
for _, providerCheck := range providersToCheck {
|
||||
|
||||
@@ -163,6 +163,15 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
|
||||
ActModeProviderSpecificModelIDField: "actModeHicapModelId",
|
||||
}, nil
|
||||
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return ProviderFields{
|
||||
APIKeyField: "nousResearchApiKey",
|
||||
PlanModeModelIDField: "planModeApiModelId",
|
||||
ActModeModelIDField: "actModeApiModelId",
|
||||
PlanModeProviderSpecificModelIDField: "planModeNousResearchModelId",
|
||||
ActModeProviderSpecificModelIDField: "actModeNousResearchModelId",
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return ProviderFields{}, fmt.Errorf("unsupported provider: %v", provider)
|
||||
}
|
||||
@@ -278,6 +287,8 @@ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, v
|
||||
apiConfig.OcaApiKey = value
|
||||
case "hicapApiKey":
|
||||
apiConfig.HicapApiKey = value
|
||||
case "nousResearchApiKey":
|
||||
apiConfig.NousResearchApiKey = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +313,9 @@ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldNa
|
||||
case "planModeHicapModelId":
|
||||
apiConfig.PlanModeHicapModelId = value
|
||||
apiConfig.ActModeHicapModelId = value
|
||||
case "planModeNousResearchModelId":
|
||||
apiConfig.PlanModeNousResearchModelId = value
|
||||
apiConfig.ActModeNousResearchModelId = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -517,7 +517,7 @@ func (pw *ProviderWizard) applyModelChange(provider cline.ApiProvider, modelID s
|
||||
ModelInfo: modelInfo,
|
||||
}
|
||||
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, false)
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, true)
|
||||
}
|
||||
|
||||
// SwitchToBYOProvider switches to a BYO provider that's already configured.
|
||||
|
||||
@@ -106,6 +106,14 @@ 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"
|
||||
@@ -199,7 +207,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):
|
||||
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch), string(types.ToolTypeFileDeleted):
|
||||
// No preview for read/fetch operations
|
||||
return ""
|
||||
|
||||
@@ -226,7 +234,8 @@ func (tr *ToolRenderer) GenerateToolContentBody(tool *types.ToolMessage) string
|
||||
toolParser := NewToolResultParser(tr.mdRenderer)
|
||||
|
||||
switch tool.Tool {
|
||||
case string(types.ToolTypeReadFile):
|
||||
case string(types.ToolTypeReadFile),
|
||||
string(types.ToolTypeFileDeleted):
|
||||
// readFile: show header only, no body
|
||||
return ""
|
||||
|
||||
|
||||
@@ -221,83 +221,7 @@ func (p *ToolResultParser) ParseCodeDefinitions(content string) string {
|
||||
|
||||
// ParseWebFetch formats webFetch tool results with content preview
|
||||
func (p *ToolResultParser) ParseWebFetch(content, url string) string {
|
||||
if content == "" {
|
||||
return fmt.Sprintf("*Fetched content from %s (empty response)*", url)
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
var result strings.Builder
|
||||
|
||||
// Try to extract title
|
||||
var title string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "##") {
|
||||
title = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if title != "" {
|
||||
result.WriteString(fmt.Sprintf("**Title:** %s\n\n", title))
|
||||
}
|
||||
|
||||
// Show preview of content
|
||||
result.WriteString("**Preview:**\n")
|
||||
|
||||
charCount := 0
|
||||
maxChars := 500
|
||||
previewLines := []string{}
|
||||
|
||||
for _, line := range lines {
|
||||
// Skip markdown headers
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if charCount+len(trimmed) > maxChars {
|
||||
break
|
||||
}
|
||||
|
||||
previewLines = append(previewLines, trimmed)
|
||||
charCount += len(trimmed)
|
||||
}
|
||||
|
||||
result.WriteString(strings.Join(previewLines, " "))
|
||||
result.WriteString("...\n\n")
|
||||
|
||||
// Extract sections
|
||||
sections := []string{}
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "##") {
|
||||
section := strings.TrimSpace(strings.TrimPrefix(trimmed, "##"))
|
||||
sections = append(sections, section)
|
||||
if len(sections) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sections) > 0 {
|
||||
result.WriteString("**Sections Found:**\n")
|
||||
for _, section := range sections {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", section))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// Word count estimate
|
||||
wordCount := len(strings.Fields(content))
|
||||
result.WriteString(fmt.Sprintf("*[Full content: ~%s]*", p.formatWordCount(wordCount)))
|
||||
|
||||
return result.String()
|
||||
return ""
|
||||
}
|
||||
|
||||
// detectLanguage returns syntax highlighting language based on file extension
|
||||
|
||||
@@ -256,6 +256,8 @@ 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,8 +180,6 @@ 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":
|
||||
@@ -218,8 +216,6 @@ 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":
|
||||
@@ -736,8 +732,6 @@ 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,6 +107,7 @@ const (
|
||||
ToolTypeEditedExistingFile ToolType = "editedExistingFile"
|
||||
ToolTypeNewFileCreated ToolType = "newFileCreated"
|
||||
ToolTypeReadFile ToolType = "readFile"
|
||||
ToolTypeFileDeleted ToolType = "fileDeleted"
|
||||
ToolTypeListFilesTopLevel ToolType = "listFilesTopLevel"
|
||||
ToolTypeListFilesRecursive ToolType = "listFilesRecursive"
|
||||
ToolTypeListCodeDefinitionNames ToolType = "listCodeDefinitionNames"
|
||||
|
||||
@@ -145,6 +145,7 @@ const (
|
||||
XAI = "xai"
|
||||
CEREBRAS = "cerebras"
|
||||
OCA = "oca"
|
||||
NOUSRESEARCH = "nousResearch"
|
||||
)
|
||||
|
||||
// AllProviders returns a slice of enabled provider IDs for the CLI build.
|
||||
@@ -161,6 +162,7 @@ var AllProviders = []string{
|
||||
"xai",
|
||||
"cerebras",
|
||||
"oca",
|
||||
"nousResearch",
|
||||
}
|
||||
|
||||
// ConfigField represents a configuration field requirement
|
||||
@@ -318,6 +320,15 @@ var rawConfigFields = ` [
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"name": "nousResearchApiKey",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "nousResearch",
|
||||
"required": true,
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"name": "ulid",
|
||||
"type": "string",
|
||||
@@ -435,6 +446,15 @@ var rawConfigFields = ` [
|
||||
"fieldType": "url",
|
||||
"placeholder": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"name": "minimaxApiLine",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "general",
|
||||
"required": false,
|
||||
"fieldType": "string",
|
||||
"placeholder": ""
|
||||
},
|
||||
{
|
||||
"name": "ocaMode",
|
||||
"type": "string",
|
||||
@@ -775,6 +795,24 @@ var rawModelDefinitions = ` {
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "A compact 20B open-weight Mixture-of-Experts language model designed for strong reasoning and tool use, ideal for edge devices and local inference."
|
||||
},
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 262144,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "Qwen3 Coder 30B MoE model with 3.3B activated parameters, optimized for code generation and analysis with 256K context window."
|
||||
},
|
||||
"qwen.qwen3-coder-480b-a35b-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 262144,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 1,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "Qwen3 Coder 480B flagship MoE model with 35B activated parameters, designed for complex coding tasks with advanced reasoning capabilities and 256K context window."
|
||||
}
|
||||
},
|
||||
"gemini": {
|
||||
@@ -1263,6 +1301,26 @@ var rawModelDefinitions = ` {
|
||||
"supportsPromptCache": false,
|
||||
"description": "SOTA performance with ~1500 tokens/s"
|
||||
}
|
||||
},
|
||||
"nousResearch": {
|
||||
"Hermes-4-405B": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 128000,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "This is the largest model in the Hermes 4 family, and it is the fullest expression of our design, focused on advanced reasoning and creative depth rather than optimizing inference speed or cost."
|
||||
},
|
||||
"Hermes-4-70B": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 128000,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "This incarnation of Hermes 4 balances scale and size. It handles complex reasoning tasks, while staying fast and cost effective. A versatile choice for many use cases."
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -1432,6 +1490,18 @@ func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Configure Oca API credentials`,
|
||||
}
|
||||
|
||||
// NousResearch
|
||||
definitions["nousResearch"] = ProviderDefinition{
|
||||
ID: "nousResearch",
|
||||
Name: "NousResearch",
|
||||
RequiredFields: getFieldsByProvider("nousResearch", configFields, true),
|
||||
OptionalFields: getFieldsByProvider("nousResearch", configFields, false),
|
||||
Models: modelDefinitions["nousResearch"],
|
||||
DefaultModelID: "Hermes-4-405B",
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Configure NousResearch API credentials`,
|
||||
}
|
||||
|
||||
return definitions, nil
|
||||
}
|
||||
@@ -1459,6 +1529,7 @@ func GetProviderDisplayName(providerID string) string {
|
||||
"xai": "X AI (Grok)",
|
||||
"cerebras": "Cerebras",
|
||||
"oca": "Oca",
|
||||
"nousResearch": "NousResearch",
|
||||
}
|
||||
|
||||
if name, exists := displayNames[providerID]; exists {
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
---
|
||||
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
|
||||
@@ -0,0 +1,383 @@
|
||||
---
|
||||
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)
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
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)
|
||||
+15
-2
@@ -88,6 +88,14 @@
|
||||
"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"
|
||||
]
|
||||
},
|
||||
@@ -130,6 +138,7 @@
|
||||
"features/drag-and-drop",
|
||||
"features/editing-messages",
|
||||
"features/focus-chain",
|
||||
"features/hooks",
|
||||
"features/multiroot-workspace",
|
||||
"features/plan-and-act",
|
||||
{
|
||||
@@ -180,6 +189,7 @@
|
||||
"provider-config/fireworks",
|
||||
"provider-config/zai",
|
||||
"provider-config/gcp-vertex-ai",
|
||||
"provider-config/baseten",
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
@@ -206,8 +216,7 @@
|
||||
"provider-config/vscode-language-model-api",
|
||||
"provider-config/sap-aicore",
|
||||
"provider-config/vercel-ai-gateway",
|
||||
"provider-config/requesty",
|
||||
"provider-config/baseten"
|
||||
"provider-config/requesty"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -315,6 +324,10 @@
|
||||
{
|
||||
"source": "/getting-started/your-first-task",
|
||||
"destination": "/getting-started/your-first-project"
|
||||
},
|
||||
{
|
||||
"source": "/cline-cli/samples",
|
||||
"destination": "/cline-cli/samples/overview"
|
||||
}
|
||||
],
|
||||
"search": {
|
||||
|
||||
@@ -81,6 +81,20 @@ your-project/
|
||||
|
||||
Cline's system prompt, on the other hand, is not user-editable ([here's where you can find it](https://github.com/cline/cline/blob/main/src/core/prompts/system.ts)). For a broader look at prompt engineering best practices, check out [this resource](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview).
|
||||
|
||||
### AGENTS.md Standard Support
|
||||
|
||||
Cline also supports the [AGENTS.md](https://agents.md/) standard as a fallback
|
||||
(in addition to Cline Rules) by automatically detecting `AGENTS.md` files in
|
||||
your workspace root. This allows you to use the same rules file across different AI
|
||||
coding tools.
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── AGENTS.md
|
||||
├── src/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Tips for Writing Effective Cline Rules
|
||||
|
||||
- Be Clear and Concise: Use simple language and avoid ambiguity.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Dictation"
|
||||
description: "Communicate with Cline using your voice for faster, more natural AI collaboration"
|
||||
---
|
||||
|
||||
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about unlocking the kind of fluid collaboration that typing can't match.
|
||||
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about enabling fluid collaboration that typing can't match.
|
||||
|
||||
## Why Voice Changes Everything
|
||||
|
||||
@@ -35,11 +35,14 @@ Dictation works with any AI model you've configured. The transcription happens t
|
||||
|
||||
## System Requirements
|
||||
|
||||
<Note>
|
||||
Dictation is currently not available on Windows. Support for Windows is planned for a future release.
|
||||
</Note>
|
||||
|
||||
Dictation uses FFmpeg to capture your voice across all platforms:
|
||||
|
||||
- **macOS**: FFmpeg (via Homebrew: `brew install ffmpeg`)
|
||||
- **Linux**: FFmpeg (via apt: `sudo apt-get install ffmpeg`)
|
||||
- **Windows**: FFmpeg (via winget: `winget install Gyan.FFmpeg`)
|
||||
|
||||
If you don't have FFmpeg installed, Cline will automatically detect this and prompt you to install it with a single click.
|
||||
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
---
|
||||
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,11 +29,33 @@ The "Remote Servers" tab allows you to connect to any MCP server that's accessib
|
||||
2. Fill in the required information:
|
||||
- **Server Name**: Provide a unique, descriptive name for the server
|
||||
- **Server URL**: Enter the complete URL endpoint of the MCP server (e.g., `https://example.com/mcp-sse`)
|
||||
- **Transport Type**: Select the connection protocol (Streamable HTTP is recommended for modern servers)
|
||||
3. Click "Add Server" to initiate the connection
|
||||
4. Cline will attempt to connect to the server and display the connection status
|
||||
|
||||
> **Note**: When connecting to a remote server, ensure you trust the source, as MCP servers can execute code in your environment.
|
||||
|
||||
#### Transport Types
|
||||
|
||||
Cline supports two transport protocols for remote MCP servers:
|
||||
|
||||
- **Streamable HTTP (Recommended)**: The modern MCP transport protocol with better performance, reliability, and full OAuth 2.1 authentication support. Use this for most remote servers.
|
||||
- **SSE (Legacy)**: Server-Sent Events transport. Use this only if the server specifically requires SSE or doesn't support Streamable HTTP.
|
||||
|
||||
#### OAuth Authentication
|
||||
|
||||
Some MCP servers (like Vercel's MCP) require OAuth authentication to access your data securely. When connecting to an OAuth-enabled server:
|
||||
|
||||
1. Add the server as usual with its URL
|
||||
2. If the server requires authentication, you'll see an error message asking to authenticate.
|
||||
3. Click the **"Authenticate"** button that appears
|
||||
4. Your browser will open to the server's authorization page
|
||||
5. Sign in and grant permission
|
||||
6. You'll be redirected back to Cline automatically
|
||||
7. The server will connect and show a green status dot
|
||||
|
||||
Once authenticated, your credentials are securely stored and the server will reconnect automatically when you reload Cline. You won't need to authenticate again unless you delete the server or your credentials expire.
|
||||
|
||||
### Remote Server Discovery
|
||||
|
||||
If you're looking for MCP servers to connect to, several third-party marketplaces provide directories of available servers with various capabilities.
|
||||
@@ -90,9 +112,20 @@ Toggle the switch next to each server to enable or disable it:
|
||||
If a server fails to connect:
|
||||
|
||||
1. An error message will be displayed with details about the failure
|
||||
2. Check that the server URL is correct and the server is running
|
||||
3. Use the "Restart Server" button to attempt reconnection
|
||||
4. If problems persist, you can delete the server and try adding it again
|
||||
2. **For OAuth errors**: Click the "Authenticate" button to complete the authorization flow
|
||||
3. Check that the server URL is correct and the server is running
|
||||
4. Try selecting a different transport type (Streamable HTTP vs SSE)
|
||||
5. Use the "Restart Server" button to attempt reconnection
|
||||
6. If problems persist, you can delete the server and try adding it again
|
||||
|
||||
#### OAuth-Specific Issues
|
||||
|
||||
If you're having trouble authenticating with an OAuth-enabled server:
|
||||
|
||||
- **"Authentication required" persists**: Make sure you completed the authorization flow in your browser and didn't cancel it
|
||||
- **Browser doesn't open**: Check your system's default browser settings and ensure external URLs can be opened
|
||||
- **Redirect errors**: Verify you're using the latest version of Cline - older versions may not support OAuth
|
||||
- **Reset authentication**: Delete the server and re-add it to start fresh with a new OAuth flow
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
@@ -105,10 +138,11 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
{
|
||||
"mcpServers": {
|
||||
"exampleServer": {
|
||||
"url": "https://example.com/mcp-sse",
|
||||
"url": "https://example.com/mcp-server",
|
||||
"type": "streamableHttp",
|
||||
"disabled": false,
|
||||
"autoApprove": ["tool1", "tool2"],
|
||||
"timeout": 30
|
||||
"timeout": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,9 +151,10 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
Key configuration options:
|
||||
|
||||
- **url**: The endpoint URL (for remote servers)
|
||||
- **type**: Transport protocol - `"streamableHttp"` (recommended) or `"sse"` (legacy)
|
||||
- **disabled**: Whether the server is currently enabled (true/false)
|
||||
- **autoApprove**: List of tool names that don't require confirmation
|
||||
- **timeout**: Maximum time in seconds to wait for server responses
|
||||
- **timeout**: Maximum time in seconds to wait for server responses (default: 60)
|
||||
|
||||
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
|
||||
|
||||
@@ -130,3 +165,20 @@ Once connected, Cline can use the tools and resources provided by the MCP server
|
||||
1. A tool approval prompt will appear (unless auto-approved)
|
||||
2. Review the tool details and parameters before approving
|
||||
3. The tool will execute and return results to Cline
|
||||
|
||||
### Example: Connecting to Vercel MCP
|
||||
|
||||
[Vercel MCP](https://vercel.com/docs/mcp/vercel-mcp) is an OAuth-enabled server that provides tools for managing your Vercel projects and deployments:
|
||||
|
||||
1. Click "Remote Servers" tab
|
||||
2. Enter:
|
||||
- **Server Name**: `vercel`
|
||||
- **Server URL**: `https://mcp.vercel.com`
|
||||
- **Transport Type**: Streamable HTTP (pre-selected)
|
||||
3. Click "Add Server"
|
||||
4. You'll see "Authentication required" - click the **"Authenticate"** button
|
||||
5. Sign in to Vercel in your browser and authorize Cline
|
||||
6. Return to Cline - the server will automatically connect
|
||||
7. Vercel's tools (deploy, logs, projects) are now available to Cline!
|
||||
|
||||
Your Vercel authentication persists across sessions, so you won't need to re-authenticate each time you use Cline.
|
||||
|
||||
Generated
+6
-34
@@ -5146,28 +5146,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -6490,9 +6468,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10235,12 +10213,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@@ -10609,9 +10581,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
|
||||
"integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
|
||||
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0",
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.23"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Baseten"
|
||||
description: "Learn how to configure and use Baseten's Model APIs with Cline. Access frontier open-source models with enterprise-grade performance, reliability, and competitive pricing."
|
||||
---
|
||||
|
||||
Baseten provides on-demand frontier model APIs designed for production applications, not just experimentation. Built on the Baseten Inference Stack, these APIs deliver enterprise-grade performance and reliability with optimized inference for leading open-source models from OpenAI, DeepSeek, Meta, Moonshot AI, and Alibaba Cloud.
|
||||
Baseten provides on-demand frontier model APIs designed for production applications, not just experimentation. Built on the Baseten Inference Stack, these APIs deliver optimized inference for leading open-source models from OpenAI, DeepSeek, Moonshot AI, and Alibaba Cloud.
|
||||
|
||||
**Website:** [https://www.baseten.co/products/model-apis/](https://www.baseten.co/products/model-apis/)
|
||||
|
||||
@@ -14,13 +14,21 @@ Baseten provides on-demand frontier model APIs designed for production applicati
|
||||
3. **Create a Key:** Generate a new API key. Give it a descriptive name (e.g., "Cline").
|
||||
4. **Copy the Key:** Copy the API key immediately and store it securely.
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "Baseten" from the "API Provider" dropdown.
|
||||
3. **Enter API Key:** Paste your Baseten API key into the "Baseten API Key" field.
|
||||
4. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
**IMPORTANT: For Kimi K2 Thinking:** To use the `moonshotai/Kimi-K2-Thinking` model, you must enable **Native Tool Call (Experimental)** in Cline settings. This setting allows Cline to call tools through their native tool processor and is required for this reasoning model to function properly.
|
||||
|
||||
### Supported Models
|
||||
|
||||
Cline supports all current models under Baseten Model APIs, including:
|
||||
For the most updated pricing, please visit: https://www.baseten.co/products/model-apis/
|
||||
Note: Kimi K2 0711, Llama 4 Maverick, and Llama 4 Scout Model APIs have been deprecated at 5pm PT on October 8th.
|
||||
https://www.baseten.co/resources/changelog/model-api-deprecation-notice-kimi-k2-0711-scout-maverick/
|
||||
|
||||
- `moonshotai/Kimi-K2-Thinking` (Moonshot AI) - Enhanced reasoning capabilities with step-by-step thought processes (262K context) - \$0.60/\$2.50 per 1M tokens
|
||||
- `zai-org/GLM-4.6` (Z AI) - Frontier open model with advanced agentic, reasoning and coding capabilities by Z AI (200k context) \$0.60/\$2.20 per 1M tokens
|
||||
- `moonshotai/Kimi-K2-Instruct-0905` (Moonshot AI) - September update with enhanced capabilities (262K context) - \$0.60/\$2.50 per 1M tokens
|
||||
- `openai/gpt-oss-120b` (OpenAI) - 120B MoE with strong reasoning capabilities (128K context) - \$0.10/\$0.50 per 1M tokens
|
||||
@@ -31,13 +39,6 @@ https://www.baseten.co/resources/changelog/model-api-deprecation-notice-kimi-k2-
|
||||
- `deepseek-ai/DeepSeek-V3.1` - Hybrid reasoning with advanced tool calling (163K context) - \$0.50/\$1.50 per 1M tokens
|
||||
- `deepseek-ai/DeepSeek-V3-0324` - Fast general-purpose with enhanced reasoning (163K context) - \$0.77/\$0.77 per 1M tokens
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "Baseten" from the "API Provider" dropdown.
|
||||
3. **Enter API Key:** Paste your Baseten API key into the "Baseten API Key" field.
|
||||
4. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
### Production-First Architecture
|
||||
|
||||
Baseten's Model APIs are built for production environments with several key advantages:
|
||||
@@ -59,47 +60,17 @@ Baseten's Model APIs are built for production environments with several key adva
|
||||
|
||||
#### Developer Experience
|
||||
- **OpenAI compatible API** - migrate by swapping a single URL
|
||||
- **Drop-in replacement** for closed models with comprehensive observability
|
||||
- **Drop-in replacement** for closed models with comprehensive observability and analytics
|
||||
- **Seamless scaling** from Model APIs to dedicated deployments
|
||||
|
||||
### Special Features
|
||||
|
||||
#### Function Calling & Tool Use
|
||||
All Baseten models support structured outputs, function calling, and tool use as part of the Baseten Inference Stack, making them ideal for agentic applications.
|
||||
|
||||
#### Reasoning Capabilities
|
||||
DeepSeek models offer enhanced reasoning with step-by-step thought processes, while maintaining production-ready performance.
|
||||
|
||||
#### Long Context Support
|
||||
- **Up to 1 million tokens** for Llama 4 models (Maverick and Scout)
|
||||
- **262K tokens** for Qwen3 models
|
||||
- **163K tokens** for DeepSeek models
|
||||
- **Perfect for code repositories** and complex multi-turn conversations
|
||||
|
||||
#### Quantization Optimizations
|
||||
Models are deployed with advanced quantization techniques (fp4, fp8, fp16) for optimal performance while maintaining quality.
|
||||
|
||||
### Migration from Other Providers
|
||||
|
||||
Baseten's OpenAI compatibility makes migration straightforward:
|
||||
|
||||
**From OpenAI:**
|
||||
- Swap `api.openai.com` with `inference.baseten.co/v1`
|
||||
- Keep existing request/response formats
|
||||
- Benefit from significant cost savings
|
||||
|
||||
**From Other Providers:**
|
||||
- Use standard OpenAI SDK format
|
||||
- Maintain existing prompting strategies
|
||||
- Access to newer open-source models
|
||||
All Baseten models support structured outputs, function calling, and tool use as part of the Baseten Inference Stack, making them ideal for agentic applications and coding workflows.
|
||||
|
||||
### Tips and Notes
|
||||
|
||||
- **Model Selection:** Choose models based on your specific use case - reasoning models for complex tasks, coding models for development work, and flagship models for general applications.
|
||||
- **Cost Optimization:** Baseten offers some of the most competitive pricing in the market, especially for open-source models.
|
||||
- **Context Windows:** Take advantage of large context windows (up to 1M tokens) for including substantial codebases and documentation.
|
||||
- **Enterprise Ready:** Baseten is designed for production use with enterprise-grade security, compliance, and reliability.
|
||||
- **Dynamic Model Updates:** Cline automatically fetches the latest model list from Baseten, ensuring access to new models as they're released.
|
||||
- **Dynamic Model Updates:** Cline automatically fetches the latest model list from Baseten, ensuring access to new models as they're released in real time.
|
||||
- **Multi-Cloud Capacity Management (MCM):** Baseten's multi-cloud infrastructure ensures high availability and low latency globally.
|
||||
- **Support:** Baseten provides dedicated support for production deployments and can work with you on dedicated resources as you scale.
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
|
||||
|
||||
Cline supports the following Cerebras models:
|
||||
|
||||
- `qwen-3-coder-480b-free` (Free tier) - High-performance coding model at no cost
|
||||
- `qwen-3-coder-480b` - Flagship 480B parameter coding model
|
||||
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
|
||||
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
|
||||
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
|
||||
- `llama-3.3-70b` - Meta's Llama 3.3 model optimized for speed
|
||||
|
||||
+5
-1
@@ -123,7 +123,11 @@ const copyWasmFiles = {
|
||||
},
|
||||
}
|
||||
|
||||
const buildEnvVars = { "import.meta.url": "_importMetaUrl" }
|
||||
const buildEnvVars = {
|
||||
"import.meta.url": "_importMetaUrl",
|
||||
"process.env.IS_STANDALONE": JSON.stringify(standalone),
|
||||
}
|
||||
|
||||
if (production) {
|
||||
// IS_DEV is always disable in production builds.
|
||||
buildEnvVars["process.env.IS_DEV"] = "false"
|
||||
|
||||
Generated
-1422
File diff suppressed because it is too large
Load Diff
+46
-45
@@ -1,47 +1,48 @@
|
||||
{
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1"
|
||||
}
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+468
-947
File diff suppressed because it is too large
Load Diff
+33
-17
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.35.1",
|
||||
"version": "3.38.1",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -46,6 +46,15 @@
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"icons": {
|
||||
"cline-icon": {
|
||||
"description": "cline",
|
||||
"default": {
|
||||
"fontPath": "assets/icons/cline-bot.woff",
|
||||
"fontCharacter": "\\e900"
|
||||
}
|
||||
}
|
||||
},
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "ClineWalkthrough",
|
||||
@@ -149,6 +158,12 @@
|
||||
"category": "Cline",
|
||||
"when": "cline.isDevMode"
|
||||
},
|
||||
{
|
||||
"command": "cline.dev.expireMcpOAuthTokens",
|
||||
"title": "Expire MCP OAuth Tokens (for testing)",
|
||||
"category": "Cline",
|
||||
"when": "cline.isDevMode"
|
||||
},
|
||||
{
|
||||
"command": "cline.addToChat",
|
||||
"title": "Add to Cline",
|
||||
@@ -168,10 +183,7 @@
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"title": "Generate Commit Message with Cline",
|
||||
"category": "Cline",
|
||||
"icon": {
|
||||
"light": "assets/icons/robot_panel_light.png",
|
||||
"dark": "assets/icons/robot_panel_dark.png"
|
||||
}
|
||||
"icon": "$(cline-icon)"
|
||||
},
|
||||
{
|
||||
"command": "cline.abortGitCommitMessage",
|
||||
@@ -278,11 +290,11 @@
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"when": "config.git.enabled && scmProvider == git && !cline.isGeneratingCommit"
|
||||
"when": "config.git.enabled && !cline.isGeneratingCommit"
|
||||
},
|
||||
{
|
||||
"command": "cline.abortGitCommitMessage",
|
||||
"when": "config.git.enabled && scmProvider == git && cline.isGeneratingCommit"
|
||||
"when": "config.git.enabled && cline.isGeneratingCommit"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -304,6 +316,7 @@
|
||||
"dev:cli:watch": "node scripts/dev-cli-watch.mjs",
|
||||
"postcompile-standalone": "node scripts/package-standalone.mjs",
|
||||
"postcompile-standalone-npm": "node scripts/package-standalone.mjs --target=npm",
|
||||
"dev": "npm run protos && npm run watch",
|
||||
"watch": "npm-run-all -p watch:*",
|
||||
"watch:esbuild": "node esbuild.mjs --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
@@ -330,7 +343,7 @@
|
||||
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
|
||||
"test": "npm-run-all test:unit test:integration",
|
||||
"test:integration": "vscode-test",
|
||||
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha",
|
||||
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha # Use `UPDATE_SNAPSHOTS=true npm run test:unit` to rebuild prompt snapshots",
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"test:sca-server": "npx tsx watch scripts/test-standalone-core-api-server.ts",
|
||||
"test:tp-orchestrator": "npx tsx scripts/testing-platform-orchestrator.ts",
|
||||
@@ -352,7 +365,8 @@
|
||||
"docs": "cd docs && npm run dev",
|
||||
"docs:check-links": "cd docs && npm run check",
|
||||
"docs:rename-file": "cd docs && npm run rename",
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
"report-issue": "node scripts/report-issue.js",
|
||||
"storybook": "cd webview-ui && npm run storybook"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
@@ -410,7 +424,7 @@
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
"@google/genai": "^1.11.0",
|
||||
"@google/genai": "^1.30.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
@@ -435,8 +449,8 @@
|
||||
"@opentelemetry/sdk-trace-node": "^1.30.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.37.0",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sap-ai-sdk/ai-api": "^1.17.0",
|
||||
"@sap-ai-sdk/orchestration": "^1.17.0",
|
||||
"@sap-ai-sdk/ai-api": "^2.1.0",
|
||||
"@sap-ai-sdk/orchestration": "^2.1.0",
|
||||
"@sentry/browser": "^9.12.0",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
@@ -454,25 +468,23 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"execa": "^9.5.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase": "^11.2.0",
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
"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",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"ollama": "^0.5.13",
|
||||
"open": "^10.1.2",
|
||||
"open-graph-scraper": "^6.9.0",
|
||||
"openai": "^4.83.0",
|
||||
"openai": "^6.9.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-mutex": "^1.0.0",
|
||||
"p-timeout": "^6.1.4",
|
||||
@@ -490,13 +502,17 @@
|
||||
"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",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": ">=3.1.1"
|
||||
"tar-fs": ">=3.1.1",
|
||||
"tar": "^7.5.2",
|
||||
"vite": "^7.1.11",
|
||||
"js-yaml": "^4.1.1"
|
||||
},
|
||||
"c8": {
|
||||
"reporter": [
|
||||
|
||||
+93
-4
@@ -49,6 +49,9 @@ service FileService {
|
||||
// Toggle a Windsurf rule (enable or disable)
|
||||
rpc toggleWindsurfRule(ToggleWindsurfRuleRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Toggle an Agents rule (enable or disable)
|
||||
rpc toggleAgentsRule(ToggleAgentsRuleRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Refreshes all rule toggles (Cline, External, and Workflows)
|
||||
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
|
||||
|
||||
@@ -66,6 +69,18 @@ service FileService {
|
||||
|
||||
// Opens or creates a focus chain checklist markdown file for editing
|
||||
rpc openFocusChainFile(StringRequest) returns (Empty);
|
||||
|
||||
// Refreshes all hook toggles (discovers hooks and their enabled state)
|
||||
rpc refreshHooks(EmptyRequest) returns (HooksToggles);
|
||||
|
||||
// Toggles a hook on or off via chmod +x/-x
|
||||
rpc toggleHook(ToggleHookRequest) returns (ToggleHookResponse);
|
||||
|
||||
// Creates a new hook from template
|
||||
rpc createHook(CreateHookRequest) returns (CreateHookResponse);
|
||||
|
||||
// Deletes an existing hook file
|
||||
rpc deleteHook(DeleteHookRequest) returns (DeleteHookResponse);
|
||||
}
|
||||
|
||||
// Response for refreshRules operation
|
||||
@@ -74,8 +89,9 @@ message RefreshedRules {
|
||||
ClineRulesToggles local_cline_rules_toggles = 2;
|
||||
ClineRulesToggles local_cursor_rules_toggles = 3;
|
||||
ClineRulesToggles local_windsurf_rules_toggles = 4;
|
||||
ClineRulesToggles local_workflow_toggles = 5;
|
||||
ClineRulesToggles global_workflow_toggles = 6;
|
||||
ClineRulesToggles local_agents_rules_toggles = 5;
|
||||
ClineRulesToggles local_workflow_toggles = 6;
|
||||
ClineRulesToggles global_workflow_toggles = 7;
|
||||
}
|
||||
|
||||
// Request to toggle a Windsurf rule
|
||||
@@ -85,6 +101,13 @@ message ToggleWindsurfRuleRequest {
|
||||
bool enabled = 3; // Whether to enable or disable the rule
|
||||
}
|
||||
|
||||
// Request to toggle an Agents rule
|
||||
message ToggleAgentsRuleRequest {
|
||||
Metadata metadata = 1;
|
||||
string rule_path = 2; // Path to the rule file
|
||||
bool enabled = 3; // Whether to enable or disable the rule
|
||||
}
|
||||
|
||||
// Request to convert a list of URIs to relative paths
|
||||
message RelativePathsRequest {
|
||||
Metadata metadata = 1;
|
||||
@@ -156,10 +179,17 @@ message RuleFile {
|
||||
bool already_exists = 3; // For createRuleFile, indicates if file already existed
|
||||
}
|
||||
|
||||
// Enum for rule scope (local, global, or remote)
|
||||
enum RuleScope {
|
||||
LOCAL = 0;
|
||||
GLOBAL = 1;
|
||||
REMOTE = 2;
|
||||
}
|
||||
|
||||
// Request to toggle a Cline rule
|
||||
message ToggleClineRuleRequest {
|
||||
Metadata metadata = 1;
|
||||
bool is_global = 2; // Whether this is a global rule or workspace rule
|
||||
RuleScope scope = 2; // Scope of the rule (local, global, or remote)
|
||||
string rule_path = 3; // Path to the rule file
|
||||
bool enabled = 4; // Whether to enable or disable the rule
|
||||
}
|
||||
@@ -173,6 +203,7 @@ message ClineRulesToggles {
|
||||
message ToggleClineRules {
|
||||
ClineRulesToggles global_cline_rules_toggles = 1;
|
||||
ClineRulesToggles local_cline_rules_toggles = 2;
|
||||
ClineRulesToggles remote_rules_toggles = 3;
|
||||
}
|
||||
|
||||
// Request to toggle a Cursor rule
|
||||
@@ -187,5 +218,63 @@ message ToggleWorkflowRequest {
|
||||
Metadata metadata = 1;
|
||||
string workflow_path = 2;
|
||||
bool enabled = 3;
|
||||
bool is_global = 4;
|
||||
RuleScope scope = 4; // Scope of the workflow (local, global, or remote)
|
||||
}
|
||||
|
||||
// Maps from hook name to enabled/disabled status
|
||||
message HookInfo {
|
||||
string name = 1;
|
||||
bool enabled = 2;
|
||||
string absolutePath = 3;
|
||||
}
|
||||
|
||||
message WorkspaceHooks {
|
||||
string workspace_name = 1;
|
||||
repeated HookInfo hooks = 2;
|
||||
}
|
||||
|
||||
message HooksToggles {
|
||||
repeated HookInfo global_hooks = 1;
|
||||
repeated WorkspaceHooks workspace_hooks = 2;
|
||||
bool is_windows = 3; // Whether the system is Windows (toggles disabled)
|
||||
}
|
||||
|
||||
// Request to toggle a hook
|
||||
message ToggleHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook (e.g., "TaskStart")
|
||||
bool is_global = 3; // Whether this is a global or workspace hook
|
||||
bool enabled = 4; // Whether to enable (chmod +x) or disable (chmod -x)
|
||||
optional string workspace_name = 5; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for toggleHook operation
|
||||
message ToggleHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
// Request to create a hook
|
||||
message CreateHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook to create
|
||||
bool is_global = 3; // Whether to create in global or workspace hooks directory
|
||||
optional string workspace_name = 4; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for createHook operation
|
||||
message CreateHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
// Request to delete a hook
|
||||
message DeleteHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook to delete
|
||||
bool is_global = 3; // Whether this is a global or workspace hook
|
||||
optional string workspace_name = 4; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for deleteHook operation
|
||||
message DeleteHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ service McpService {
|
||||
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
|
||||
rpc refreshMcpMarketplace(EmptyRequest) returns (McpMarketplaceCatalog);
|
||||
rpc openMcpSettings(EmptyRequest) returns (Empty);
|
||||
rpc authenticateMcpServer(StringRequest) returns (Empty);
|
||||
|
||||
// Subscribe to MCP marketplace catalog updates
|
||||
rpc subscribeToMcpMarketplaceCatalog(EmptyRequest) returns (stream McpMarketplaceCatalog);
|
||||
@@ -43,6 +44,7 @@ message AddRemoteMcpServerRequest {
|
||||
Metadata metadata = 1;
|
||||
string server_name = 2;
|
||||
string server_url = 3;
|
||||
optional string transport_type = 4;
|
||||
}
|
||||
|
||||
message ToggleToolAutoApproveRequest {
|
||||
@@ -91,6 +93,8 @@ message McpServer {
|
||||
repeated McpResourceTemplate resource_templates = 7;
|
||||
optional bool disabled = 8;
|
||||
optional int32 timeout = 9;
|
||||
optional bool oauth_required = 10;
|
||||
optional string oauth_auth_status = 11;
|
||||
}
|
||||
|
||||
message McpServers {
|
||||
|
||||
@@ -23,8 +23,6 @@ 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
|
||||
@@ -98,6 +96,8 @@ message OpenRouterModelInfo {
|
||||
optional ThinkingConfig thinking_config = 10;
|
||||
optional bool supports_global_endpoint = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional string name = 13;
|
||||
optional double temperature = 14;
|
||||
}
|
||||
|
||||
// Shared response message for model information
|
||||
@@ -314,7 +314,7 @@ message ApiConfiguration {
|
||||
// Request for updating API configuration (new - uses separate options and secrets)
|
||||
message UpdateApiConfigurationRequestNew {
|
||||
Metadata metadata = 1;
|
||||
ApiConfiguration api_configuration = 2;
|
||||
ApiConfiguration updates = 2;
|
||||
|
||||
// Required field mask specifying which fields to update.
|
||||
// Field paths use dot notation with camelCase field names:
|
||||
@@ -322,7 +322,7 @@ message UpdateApiConfigurationRequestNew {
|
||||
// - "options.openAiHeaders" (for options fields)
|
||||
// - "secrets.apiKey" (for secrets fields)
|
||||
// - "secrets.openRouterApiKey" (for secrets fields)
|
||||
google.protobuf.FieldMask update_mask = 3;
|
||||
repeated string update_mask = 3;
|
||||
}
|
||||
|
||||
// Request for partially updating API configuration using FieldMask
|
||||
@@ -423,6 +423,7 @@ enum ApiProvider {
|
||||
MINIMAX = 36;
|
||||
HICAP = 37;
|
||||
AIHUBMIX = 38;
|
||||
NOUSRESEARCH = 39;
|
||||
}
|
||||
|
||||
// Model info for OpenAI-compatible models
|
||||
@@ -547,6 +548,7 @@ message ModelsApiConfiguration {
|
||||
optional string aihubmix_api_key = 82;
|
||||
optional string aihubmix_base_url = 83;
|
||||
optional string aihubmix_app_code = 84;
|
||||
optional string nous_research_api_key = 85;
|
||||
|
||||
// Plan mode configurations
|
||||
optional ApiProvider plan_mode_api_provider = 100;
|
||||
@@ -586,6 +588,8 @@ message ModelsApiConfiguration {
|
||||
optional OpenRouterModelInfo plan_mode_hicap_model_info = 134;
|
||||
optional string plan_mode_aihubmix_model_id = 135;
|
||||
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 136;
|
||||
optional string plan_mode_nous_research_model_id = 137;
|
||||
optional string gemini_plan_mode_thinking_level = 138;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
@@ -625,4 +629,6 @@ message ModelsApiConfiguration {
|
||||
optional OpenRouterModelInfo act_mode_hicap_model_info = 234;
|
||||
optional string act_mode_aihubmix_model_id = 235;
|
||||
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 236;
|
||||
optional string act_mode_nous_research_model_id = 237;
|
||||
optional string gemini_act_mode_thinking_level = 238;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ 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);
|
||||
@@ -30,6 +31,7 @@ service StateService {
|
||||
rpc installClineCli(EmptyRequest) returns (Empty);
|
||||
rpc checkCliInstallation(EmptyRequest) returns (Boolean);
|
||||
rpc getProcessInfo(EmptyRequest) returns (ProcessInfo);
|
||||
rpc flushPendingState(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message AutoApprovalActions {
|
||||
@@ -88,6 +90,7 @@ message Secrets {
|
||||
optional string oca_api_key = 37;
|
||||
optional string oca_refresh_token = 38;
|
||||
optional string hicap_api_key = 39;
|
||||
optional string mcp_oauth_secrets = 40;
|
||||
}
|
||||
|
||||
message Settings {
|
||||
@@ -359,6 +362,7 @@ message UpdateSettingsRequest {
|
||||
optional int32 subagent_terminal_output_line_limit = 30;
|
||||
optional string cline_env = 31;
|
||||
optional bool native_tool_call_enabled = 32;
|
||||
optional OnboardingModelGroup onboarding_models = 33;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutRequest {
|
||||
@@ -379,3 +383,24 @@ 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;
|
||||
}
|
||||
|
||||
message OnboardingModelGroup {
|
||||
repeated OnboardingModel models = 1;
|
||||
}
|
||||
|
||||
message OnboardingModel {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
int32 score = 3;
|
||||
int32 latency = 4;
|
||||
string badge = 5;
|
||||
string group = 6;
|
||||
OpenRouterModelInfo info = 7;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ message TaskResponse {
|
||||
int32 tokens_out = 8;
|
||||
int32 cache_writes = 9;
|
||||
int32 cache_reads = 10;
|
||||
string model_id = 11;
|
||||
}
|
||||
|
||||
// Request for getting task history with filtering
|
||||
@@ -99,6 +100,7 @@ message TaskItem {
|
||||
int32 tokens_out = 8;
|
||||
int32 cache_writes = 9;
|
||||
int32 cache_reads = 10;
|
||||
string model_id = 11;
|
||||
}
|
||||
|
||||
// Request for ask response operation
|
||||
|
||||
@@ -32,6 +32,7 @@ enum ClineAsk {
|
||||
CONDENSE = 13;
|
||||
REPORT_BUG = 14;
|
||||
SUMMARIZE_TASK = 15;
|
||||
ACT_MODE_RESPOND = 16;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
@@ -77,6 +78,7 @@ enum ClineSayToolType {
|
||||
LIST_CODE_DEFINITION_NAMES = 5;
|
||||
SEARCH_FILES = 6;
|
||||
WEB_FETCH = 7;
|
||||
FILE_DELETED = 8;
|
||||
}
|
||||
|
||||
// Enum for browser actions
|
||||
@@ -183,6 +185,11 @@ message ClineApiReqInfo {
|
||||
ApiReqRetryStatus retry_status = 9;
|
||||
}
|
||||
|
||||
message ClineModelInfo {
|
||||
string provider_id = 1;
|
||||
string model_id = 2;
|
||||
}
|
||||
|
||||
// Main ClineMessage type
|
||||
message ClineMessage {
|
||||
int64 ts = 1;
|
||||
@@ -209,6 +216,7 @@ message ClineMessage {
|
||||
ClineAskQuestion ask_question = 20;
|
||||
ClineAskNewTask ask_new_task = 21;
|
||||
ClineApiReqInfo api_req_info = 22;
|
||||
ClineModelInfo model_info = 23;
|
||||
}
|
||||
|
||||
// UiService provides methods for managing UI interactions
|
||||
|
||||
@@ -95,6 +95,7 @@ const ENABLED_PROVIDERS = [
|
||||
"ollama", // Ollama local models
|
||||
"cerebras", // Cerebras models
|
||||
"oca", // Oracle Code Assist
|
||||
"nousResearch", // NousResearch provider
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import fs from "fs"
|
||||
import https from "https"
|
||||
import path from "path"
|
||||
import { pipeline } from "stream/promises"
|
||||
import tar from "tar"
|
||||
import * as tar from "tar"
|
||||
import { promisify } from "util"
|
||||
import { createGunzip } from "zlib"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler } from "../../core/api/index"
|
||||
import { ApiStream } from "../../core/api/transform/stream"
|
||||
|
||||
@@ -32,7 +33,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -254,7 +255,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
+5
-34
@@ -9,14 +9,6 @@ export interface EnvironmentConfig {
|
||||
appBaseUrl: string
|
||||
apiBaseUrl: string
|
||||
mcpBaseUrl: string
|
||||
firebase: {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
projectId: string
|
||||
storageBucket?: string
|
||||
messagingSenderId?: string
|
||||
appId?: string
|
||||
}
|
||||
}
|
||||
|
||||
class ClineEndpoint {
|
||||
@@ -28,8 +20,8 @@ class ClineEndpoint {
|
||||
private environment: Environment = Environment.production
|
||||
|
||||
private constructor() {
|
||||
// Set environment at module load
|
||||
const _env = process?.env?.CLINE_ENVIRONMENT
|
||||
// Set environment at module load. Use override if provided.
|
||||
const _env = process?.env?.CLINE_ENVIRONMENT_OVERRIDE || process?.env?.CLINE_ENVIRONMENT
|
||||
if (_env && Object.values(Environment).includes(_env as Environment)) {
|
||||
this.environment = _env as Environment
|
||||
return
|
||||
@@ -61,16 +53,8 @@ class ClineEndpoint {
|
||||
return {
|
||||
environment: Environment.staging,
|
||||
appBaseUrl: "https://staging-app.cline.bot",
|
||||
apiBaseUrl: "https://core-api.staging.int.cline.bot",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
|
||||
authDomain: "cline-staging.firebaseapp.com",
|
||||
projectId: "cline-staging",
|
||||
storageBucket: "cline-staging.firebasestorage.app",
|
||||
messagingSenderId: "853479478430",
|
||||
appId: "1:853479478430:web:2de0dba1c63c3262d4578f",
|
||||
},
|
||||
apiBaseUrl: "http://localhost:7777",
|
||||
mcpBaseUrl: "https://core-api.staging.int.cline.bot/v1/mcp",
|
||||
}
|
||||
case Environment.local:
|
||||
return {
|
||||
@@ -78,26 +62,13 @@ class ClineEndpoint {
|
||||
appBaseUrl: "http://localhost:3000",
|
||||
apiBaseUrl: "http://localhost:7777",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
|
||||
authDomain: "cline-preview.firebaseapp.com",
|
||||
projectId: "cline-preview",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return {
|
||||
environment: Environment.production,
|
||||
appBaseUrl: "https://app.cline.bot",
|
||||
apiBaseUrl: "https://api.cline.bot",
|
||||
apiBaseUrl: "http://localhost:7777",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyC5rx59Xt8UgwdU3PCfzUF7vCwmp9-K2vk",
|
||||
authDomain: "cline-prod.firebaseapp.com",
|
||||
projectId: "cline-prod",
|
||||
storageBucket: "cline-prod.firebasestorage.app",
|
||||
messagingSenderId: "941048379330",
|
||||
appId: "1:941048379330:web:45058eedeefc5cdfcc485b",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-7
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { AIhubmixHandler } from "./providers/aihubmix"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
@@ -25,6 +25,7 @@ import { MinimaxHandler } from "./providers/minimax"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { MoonshotHandler } from "./providers/moonshot"
|
||||
import { NebiusHandler } from "./providers/nebius"
|
||||
import { NousResearchHandler } from "./providers/nousresearch"
|
||||
import { OcaHandler } from "./providers/oca"
|
||||
import { OllamaHandler } from "./providers/ollama"
|
||||
import { OpenAiHandler } from "./providers/openai"
|
||||
@@ -46,9 +47,8 @@ import { ApiStream, ApiStreamUsageChunk } from "./transform/stream"
|
||||
export type CommonApiHandlerOptions = {
|
||||
onRetryAttempt?: ApiConfiguration["onRetryAttempt"]
|
||||
}
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream
|
||||
createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[], useResponseApi?: boolean): ApiStream
|
||||
getModel(): ApiHandlerModel
|
||||
getApiStreamUsage?(): Promise<ApiStreamUsageChunk | undefined>
|
||||
}
|
||||
@@ -94,6 +94,7 @@ function createHandlerForProvider(
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
geminiThinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
})
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler({
|
||||
@@ -166,6 +167,7 @@ function createHandlerForProvider(
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
thinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
ulid: options.ulid,
|
||||
})
|
||||
@@ -250,6 +252,7 @@ function createHandlerForProvider(
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
geminiThinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
})
|
||||
case "litellm":
|
||||
return new LiteLlmHandler({
|
||||
@@ -367,10 +370,10 @@ function createHandlerForProvider(
|
||||
return new VercelAIGatewayHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
vercelAiGatewayApiKey: options.vercelAiGatewayApiKey,
|
||||
vercelAiGatewayModelId:
|
||||
mode === "plan" ? options.planModeVercelAiGatewayModelId : options.actModeVercelAiGatewayModelId,
|
||||
vercelAiGatewayModelInfo:
|
||||
mode === "plan" ? options.planModeVercelAiGatewayModelInfo : options.actModeVercelAiGatewayModelInfo,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
})
|
||||
case "zai":
|
||||
return new ZAiHandler({
|
||||
@@ -416,6 +419,12 @@ function createHandlerForProvider(
|
||||
hicapApiKey: options.hicapApiKey,
|
||||
hicapModelId: mode === "plan" ? options.planModeHicapModelId : options.actModeHicapModelId,
|
||||
})
|
||||
case "nousResearch":
|
||||
return new NousResearchHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
nousResearchApiKey: options.nousResearchApiKey,
|
||||
apiModelId: mode === "plan" ? options.planModeNousResearchModelId : options.actModeNousResearchModelId,
|
||||
})
|
||||
default:
|
||||
return new AnthropicHandler({
|
||||
onRetryAttempt: options.onRetryAttempt,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import sinon from "sinon"
|
||||
import "should"
|
||||
import { ClaudeCodeHandler } from "@core/api/providers/claude-code"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
describe("ClaudeCodeHandler", () => {
|
||||
let handler: ClaudeCodeHandler
|
||||
@@ -71,7 +71,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -140,7 +140,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { LiteLlmHandler, type LiteLlmModelInfoResponse } from "@core/api/providers/litellm"
|
||||
import { convertToOpenAiMessages } from "@core/api/transform/openai-format"
|
||||
import { expect } from "chai"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
const fakeClient = {
|
||||
chat: {
|
||||
@@ -10,12 +11,12 @@ const fakeClient = {
|
||||
create: sinon.stub(),
|
||||
},
|
||||
},
|
||||
baseURL: "fake",
|
||||
baseURL: "https://fake.example",
|
||||
}
|
||||
|
||||
describe("LiteLlmHandler", () => {
|
||||
const originalFetch = global.fetch
|
||||
const mockFetch = sinon.stub()
|
||||
let doneMockingFetch: (value: any) => void = () => {}
|
||||
|
||||
const mockModelFetch = (modelInfo: LiteLlmModelInfoResponse["data"][number]) => {
|
||||
mockFetch.resolves({
|
||||
@@ -45,7 +46,11 @@ describe("LiteLlmHandler", () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = mockFetch
|
||||
mockFetchForTesting(mockFetch, () => {
|
||||
return new Promise((resolve) => {
|
||||
doneMockingFetch = resolve
|
||||
})
|
||||
})
|
||||
|
||||
// Configure the stub to return a stream that closes immediately with usage data
|
||||
fakeClient.chat.completions.create.resolves(
|
||||
@@ -68,8 +73,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
afterEach(() => {
|
||||
sinon.reset()
|
||||
|
||||
global.fetch = originalFetch
|
||||
doneMockingFetch(void 0)
|
||||
})
|
||||
|
||||
const createAsyncIterable = (data: any[] = []) => {
|
||||
@@ -105,7 +109,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("sends the system prompt and messages with the openai format", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
@@ -157,7 +161,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("inserts the cache control in the system prompt and the last two user messages", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, before, beforeEach, describe, it } from "mocha"
|
||||
import "should"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
|
||||
describe("OllamaHandler", () => {
|
||||
@@ -59,7 +59,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
const usageInfo = []
|
||||
@@ -114,7 +114,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
// Start the request and catch the error
|
||||
let errorMessage = ""
|
||||
@@ -158,7 +158,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
@@ -204,7 +204,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
const { baseURL, appCode, ...rest } = options
|
||||
this.options = {
|
||||
baseURL: baseURL ?? "https://aihubmix.com",
|
||||
appCode: appCode ?? "KUWF9311", // 应用代码,享受折扣
|
||||
appCode: appCode ?? "KUWF9311",
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,6 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
this.geminiClient = new GoogleGenAI({
|
||||
apiKey: this.options.apiKey,
|
||||
httpOptions: {
|
||||
// AIhubmix Gemini 兼容网关,按 Google GenAI 路径规范
|
||||
baseUrl: `${this.options.baseURL}/gemini`,
|
||||
headers: {
|
||||
// @ts-expect-error
|
||||
@@ -97,9 +96,6 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
return this.geminiClient
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模型名称路由到对应的客户端
|
||||
*/
|
||||
private routeModel(modelName: string): "anthropic" | "openai" | "gemini" | "openai-response" {
|
||||
const id = modelName || ""
|
||||
if (id.startsWith("claude")) {
|
||||
@@ -114,9 +110,6 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
return "openai"
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复空工具时的 tool_choice 问题
|
||||
*/
|
||||
private fixToolChoice(requestBody: any): any {
|
||||
if (requestBody.tools?.length === 0 && requestBody.tool_choice) {
|
||||
delete requestBody.tool_choice
|
||||
@@ -203,25 +196,21 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
const client = this.ensureOpenaiClient()
|
||||
const modelId = this.options.modelId || "gpt-4o-mini"
|
||||
|
||||
// 将 Anthropic 风格消息转换为 Responses API 的 input 结构
|
||||
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 }
|
||||
}
|
||||
// 文本(用户 -> input_text,助手 -> output_text)
|
||||
const text = c.text ?? (typeof c === "string" ? c : "")
|
||||
return { type: role === "assistant" ? "output_text" : "input_text", text }
|
||||
})
|
||||
return { role, content }
|
||||
})
|
||||
|
||||
// 使用 Responses 流式 API,以事件驱动产出
|
||||
const stream = await (client as any).responses.stream({
|
||||
model: modelId,
|
||||
instructions: systemPrompt,
|
||||
@@ -261,7 +250,6 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
stream: true,
|
||||
}
|
||||
|
||||
// 修复空工具问题
|
||||
const fixedRequestBody = this.fixToolChoice(requestBody)
|
||||
|
||||
const stream = await client.chat.completions.create(fixedRequestBody)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool, MessageParam } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { AnthropicModelId, anthropicDefaultModelId, anthropicModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AnthropicHandlerOptions extends CommonApiHandlerOptions {
|
||||
@@ -31,6 +33,7 @@ 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}`)
|
||||
@@ -40,7 +43,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: AnthropicTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
const model = this.getModel()
|
||||
@@ -61,6 +64,7 @@ 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":
|
||||
@@ -70,47 +74,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "claude-opus-4-1-20250805":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307": {
|
||||
/*
|
||||
The latest message will be the new user message, one before will be the assistant message from a previous request, and the user message before that will be a previously cached user message. So we need to mark the latest user message as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server know the last message to retrieve from the cache for the current request..
|
||||
*/
|
||||
const userMsgIndices = messages.reduce((acc, msg, index) => {
|
||||
if (msg.role === "user") {
|
||||
acc.push(index)
|
||||
}
|
||||
return acc
|
||||
}, [] as number[])
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
|
||||
const anthropicMessages: 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
|
||||
})
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, true)
|
||||
|
||||
stream = await client.messages.create(
|
||||
{
|
||||
@@ -130,7 +94,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
messages: anthropicMessages,
|
||||
// tools, // cache breakpoints go from tools > system > messages, and since tools dont change, we can just set the breakpoint at the end of system (this avoids having to set a breakpoint at the end of tools which by itself does not meet min requirements for haiku caching)
|
||||
stream: true,
|
||||
tools: nativeToolsOn ? (tools as AnthropicTool[]) : undefined,
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
// tool_choice options:
|
||||
// - none: disables tool use, even if tools are provided. Claude will not call any tools.
|
||||
// - auto: allows Claude to decide whether to call any provided tools or not. This is the default value when tools are provided.
|
||||
@@ -159,29 +123,30 @@ export class AnthropicHandler implements ApiHandler {
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages,
|
||||
// tools,
|
||||
// tool_choice: { type: "auto" },
|
||||
messages: sanitizeAnthropicMessages(messages, false),
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
tool_choice: { type: "auto" },
|
||||
stream: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let thinkingDeltaAccumulator = ""
|
||||
const lastStartedToolCall = { id: "", name: "", arguments: "" }
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: usage.input_tokens || 0,
|
||||
outputTokens: usage.output_tokens || 0,
|
||||
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
{
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: usage.input_tokens || 0,
|
||||
outputTokens: usage.output_tokens || 0,
|
||||
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
}
|
||||
break
|
||||
case "message_delta":
|
||||
@@ -202,15 +167,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
}
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
}
|
||||
signature: chunk.content_block.signature,
|
||||
}
|
||||
break
|
||||
case "redacted_thinking":
|
||||
@@ -218,10 +175,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
redacted_data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
@@ -255,15 +209,14 @@ export class AnthropicHandler implements ApiHandler {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
}
|
||||
thinkingDeltaAccumulator += chunk.delta.thinking
|
||||
break
|
||||
case "signature_delta":
|
||||
// It's used when sending the thinking block back to the API
|
||||
// API expects this in completed form, not as array of deltas
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
if (chunk.delta.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
type: "reasoning",
|
||||
reasoning: "", // reasoning text is already sent via thinking_delta
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -46,7 +47,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
try {
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
interface BasetenHandlerOptions extends CommonApiHandlerOptions {
|
||||
basetenApiKey?: string
|
||||
@@ -31,6 +34,7 @@ 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}`)
|
||||
@@ -96,10 +100,11 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const maxTokens = this.getOptimalMaxTokens(model)
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
@@ -113,21 +118,22 @@ export class BasetenHandler implements ApiHandler {
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
tools,
|
||||
tool_choice: tools && tools.length > 0 ? "auto" : undefined,
|
||||
})
|
||||
|
||||
let didOutputUsage = false
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
const delta = chunk?.choices?.[0]?.delta
|
||||
|
||||
// Handle reasoning field if present (for reasoning models with parsed output)
|
||||
if ((delta as any)?.reasoning) {
|
||||
const reasoningContent = (delta as any).reasoning as string
|
||||
if (delta && "reasoning" in delta && delta?.reasoning) {
|
||||
const reasoning = typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning)
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningContent,
|
||||
reasoning,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle content field
|
||||
@@ -138,6 +144,10 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
// Handle usage information - only output once
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Import proper AWS SDK types
|
||||
import type { ContentBlock, Message } from "@aws-sdk/client-bedrock-runtime"
|
||||
import {
|
||||
@@ -12,6 +11,7 @@ import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI, calculateApiCostQwen } from "@utils/cost"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
@@ -121,7 +121,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ maxRetries: 4 })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
const rawModelId = await this.getModelId()
|
||||
|
||||
@@ -270,6 +270,10 @@ 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}`,
|
||||
region: this.getRegion(),
|
||||
@@ -338,7 +342,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createDeepseekMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -476,7 +480,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* First uses convertToR1Format to merge consecutive messages with the same role,
|
||||
* then converts to the string format that DeepSeek R1 expects
|
||||
*/
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// First use convertToR1Format to merge consecutive messages with the same role
|
||||
const r1Messages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
|
||||
@@ -509,7 +513,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Estimates token count based on text length (approximate)
|
||||
* Note: This is a rough estimation, as the actual token count depends on the tokenizer
|
||||
*/
|
||||
private estimateInputTokens(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): number {
|
||||
private estimateInputTokens(systemPrompt: string, messages: ClineStorageMessage[]): number {
|
||||
// For Deepseek R1, we estimate the token count of the formatted prompt
|
||||
// The formatted prompt includes special tokens and consistent formatting
|
||||
const formattedPrompt = this.formatDeepseekR1Prompt(systemPrompt, messages)
|
||||
@@ -676,11 +680,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing Converse API response:", error)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Failed to process response: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,9 +699,20 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
text: `[ERROR] Model stream error: ${chunk.modelStreamErrorException.message}`,
|
||||
}
|
||||
} else if (chunk.validationException) {
|
||||
// Check if this is a context window error - if so, throw it
|
||||
// so the retry mechanism can handle truncation
|
||||
const message = chunk.validationException.message || ""
|
||||
const isContextError = /input.*too long|context.*exceed|maximum.*token|input length.*max.*tokens/i.test(message)
|
||||
|
||||
if (isContextError) {
|
||||
// Throw as exception so context management can handle it
|
||||
throw chunk.validationException
|
||||
}
|
||||
|
||||
// Otherwise yield as error text
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Validation error: ${chunk.validationException.message}`,
|
||||
text: `[ERROR] Validation error: ${message}`,
|
||||
}
|
||||
} else if (chunk.throttlingException) {
|
||||
yield {
|
||||
@@ -775,7 +786,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createAnthropicMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
enable1mContextWindow: boolean,
|
||||
@@ -831,7 +842,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Formats messages for models using the Converse API specification
|
||||
* Used by both Anthropic and Nova models to avoid code duplication
|
||||
*/
|
||||
private formatMessagesForConverseAPI(messages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
private formatMessagesForConverseAPI(messages: ClineStorageMessage[]): Message[] {
|
||||
return messages.map((message) => {
|
||||
// Determine role (user or assistant)
|
||||
const role = message.role === "user" ? ConversationRole.USER : ConversationRole.ASSISTANT
|
||||
@@ -964,7 +975,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createNovaMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1004,7 +1015,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createOpenAIMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1139,7 +1150,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createQwenMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { CerebrasModelId, cerebrasDefaultModelId, cerebrasModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -31,6 +32,7 @@ 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}`)
|
||||
@@ -44,7 +46,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
baseDelay: 5000, // Start with 5 second delay
|
||||
maxDelay: 60000, // Allow up to 60 second delays to respect rate limits
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
// Convert Anthropic messages to Cerebras format
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { filterMessagesForClaudeCode } from "@/integrations/claude-code/message-filter"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { ClaudeCodeModelId, claudeCodeDefaultModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { type ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { type ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
@@ -24,7 +24,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// Filter out image blocks since Claude Code doesn't support them
|
||||
const filteredMessages = filterMessagesForClaudeCode(messages)
|
||||
|
||||
@@ -113,7 +113,18 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
|
||||
// Yield tool_use blocks to the streaming pipeline for proper tool execution
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
call_id: content.id,
|
||||
function: {
|
||||
id: content.id,
|
||||
name: content.name,
|
||||
arguments: content.input,
|
||||
},
|
||||
},
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
@@ -8,7 +7,10 @@ import { ClineEnv } from "@/config"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
@@ -25,6 +27,7 @@ interface ClineHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
clineAccountId?: string
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class ClineHandler implements ApiHandler {
|
||||
@@ -43,6 +46,7 @@ export class ClineHandler implements ApiHandler {
|
||||
|
||||
private async ensureClient(): Promise<OpenAI> {
|
||||
const clineAccountAuthToken = await this._authService.getAuthToken()
|
||||
console.log(clineAccountAuthToken)
|
||||
if (!clineAccountAuthToken) {
|
||||
throw new Error(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE)
|
||||
}
|
||||
@@ -95,7 +99,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = await this.ensureClient()
|
||||
|
||||
@@ -113,11 +117,13 @@ export class ClineHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("ClineHandler chunk:" + JSON.stringify(chunk))
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
@@ -148,6 +154,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const delta = choice?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -164,8 +171,7 @@ export class ClineHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,20 +186,23 @@ export class ClineHandler implements ApiHandler {
|
||||
"reasoning_details" in delta &&
|
||||
delta.reasoning_details &&
|
||||
// @ts-ignore-next-line
|
||||
delta.reasoning_details.length && // exists and non-0
|
||||
delta?.reasoning_details?.length && // exists and non-0
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning_details",
|
||||
reasoning_details: delta.reasoning_details,
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
console.log("didOutputUsage", didOutputUsage, chunk.usage)
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
// @ts-ignore-next-line
|
||||
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
|
||||
if (this.getModel().id === "x-ai/grok-code-fast-1") {
|
||||
if (this.getModel().id === "x-ai/grok-code-fast-1" || this.getModel().id === "minimax/minimax-m2") {
|
||||
totalCost = 0
|
||||
}
|
||||
|
||||
@@ -239,6 +248,7 @@ 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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -32,6 +33,7 @@ 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}`)
|
||||
@@ -73,7 +75,7 @@ export class DeepSeekHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -96,7 +97,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -383,7 +384,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DoubaoModelId, doubaoDefaultModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -27,6 +28,7 @@ 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}`)
|
||||
@@ -48,7 +50,7 @@ export class DoubaoHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { FireworksModelId, fireworksDefaultModelId, fireworksModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -30,6 +31,7 @@ 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}`)
|
||||
@@ -39,7 +41,7 @@ export class FireworksHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.fireworksModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import {
|
||||
ApiError,
|
||||
@@ -7,10 +6,11 @@ import {
|
||||
type GenerateContentResponseUsageMetadata,
|
||||
GoogleGenAI,
|
||||
FunctionDeclaration as GoogleTool,
|
||||
Part,
|
||||
ThinkingLevel,
|
||||
} from "@google/genai"
|
||||
import { GeminiModelId, geminiDefaultModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { RetriableError, withRetry } from "../retry"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
@@ -28,6 +28,7 @@ interface GeminiHandlerOptions extends CommonApiHandlerOptions {
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
thinkingBudgetTokens?: number
|
||||
thinkingLevel?: string
|
||||
apiModelId?: string
|
||||
ulid?: string
|
||||
}
|
||||
@@ -110,34 +111,49 @@ export class GeminiHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: GoogleTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: GoogleTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const { id: modelId, info } = this.getModel()
|
||||
const contents = messages.map(convertAnthropicMessageToGemini)
|
||||
|
||||
// Configure thinking budget if supported
|
||||
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const _maxBudget = info.thinkingConfig?.maxBudget ?? 0
|
||||
const _thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const maxBudget = info.thinkingConfig?.maxBudget ?? 24576
|
||||
const thinkingBudget = Math.min(_thinkingBudget, maxBudget)
|
||||
// When ThinkingLevel is defineded, thinking budget cannot be zero
|
||||
// and only level is used to control thinking behavior.
|
||||
let thinkingLevel: ThinkingLevel | undefined
|
||||
if (this.options.thinkingLevel === "low") {
|
||||
thinkingLevel = ThinkingLevel.LOW
|
||||
} else if (this.options.thinkingLevel === "high") {
|
||||
thinkingLevel = ThinkingLevel.HIGH
|
||||
}
|
||||
|
||||
// Set up base generation config
|
||||
const requestConfig: GenerateContentConfig = {
|
||||
// Add base URL if configured
|
||||
httpOptions: this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined,
|
||||
...{ systemInstruction: systemPrompt },
|
||||
systemInstruction: systemPrompt,
|
||||
// Set temperature (default to 0)
|
||||
temperature: 0,
|
||||
// Gemini 3.0 recommends 1.0
|
||||
temperature: info.temperature ?? 1,
|
||||
}
|
||||
|
||||
// Add thinking config if the model supports it
|
||||
if (thinkingBudget > 0) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: thinkingBudget,
|
||||
includeThoughts: true,
|
||||
}
|
||||
requestConfig.thinkingConfig = {
|
||||
// Turn off thinking:
|
||||
// thinkingBudget: 0
|
||||
// Turn on dynamic thinking:
|
||||
// thinkingBudget: -1
|
||||
// Turn on fixed thinking budget:
|
||||
thinkingBudget: thinkingLevel ? undefined : thinkingBudget,
|
||||
thinkingLevel,
|
||||
includeThoughts: thinkingBudget > 0 || !!thinkingLevel,
|
||||
}
|
||||
|
||||
// Generate content using the configured parameters
|
||||
const sdkCallStartTime = Date.now()
|
||||
let responseId: string | undefined
|
||||
let sdkFirstChunkTime: number | undefined
|
||||
let ttftSdkMs: number | undefined
|
||||
let apiSuccess = false
|
||||
@@ -148,7 +164,8 @@ export class GeminiHandler implements ApiHandler {
|
||||
let thoughtsTokenCount = 0 // Initialize thought token counts
|
||||
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
|
||||
|
||||
if (tools?.length) {
|
||||
const isNativeToolCallsEnabled = tools?.length
|
||||
if (isNativeToolCallsEnabled) {
|
||||
requestConfig.tools = [{ functionDeclarations: tools }]
|
||||
requestConfig.toolConfig = {
|
||||
// Force the model to call 'any' function.
|
||||
@@ -176,56 +193,45 @@ export class GeminiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Handle thinking content from Gemini's response
|
||||
const candidateForThoughts = chunk?.candidates?.[0]
|
||||
const partsForThoughts = candidateForThoughts?.content?.parts
|
||||
let thoughts = "" // Initialize as empty string
|
||||
|
||||
if (partsForThoughts) {
|
||||
// This ensures partsForThoughts is a Part[] array
|
||||
for (const part of partsForThoughts) {
|
||||
const { thought, text } = part as Part
|
||||
if (thought && text) {
|
||||
// Ensure part.text exists
|
||||
// Handle the thought part
|
||||
thoughts += text + "\n" // Append thought and a newline
|
||||
const parts = chunk?.candidates?.[0]?.content?.parts || []
|
||||
for (const part of parts) {
|
||||
if (part.thought && part.text) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.responseId,
|
||||
reasoning: part.text || "",
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
} else if (part.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: part.text,
|
||||
id: chunk.responseId,
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thoughts.trim() !== "") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thoughts.trim(),
|
||||
}
|
||||
thoughts = "" // Reset thoughts after yielding
|
||||
}
|
||||
|
||||
if (chunk.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.text,
|
||||
}
|
||||
}
|
||||
|
||||
if (tools && chunk.functionCalls && chunk.functionCalls?.length > 0) {
|
||||
for (const functionCall of chunk.functionCalls) {
|
||||
if (functionCall.args) {
|
||||
console.log("[GeminiHandler] tool call received:", functionCall)
|
||||
if (part.functionCall) {
|
||||
const functionCall = part.functionCall
|
||||
const args = Object.entries(functionCall.args || {}).filter(([_key, val]) => !!val)
|
||||
if (functionCall.args && args.length > 0) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: chunk.responseId,
|
||||
tool_call: {
|
||||
function: {
|
||||
id: functionCall.id || functionCall.name,
|
||||
id: chunk.responseId,
|
||||
name: functionCall.name,
|
||||
arguments: JSON.stringify(functionCall.args),
|
||||
},
|
||||
},
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usageMetadata) {
|
||||
responseId = chunk.responseId
|
||||
lastUsageMetadata = chunk.usageMetadata
|
||||
promptTokens = lastUsageMetadata.promptTokenCount ?? promptTokens
|
||||
outputTokens = lastUsageMetadata.candidatesTokenCount ?? outputTokens
|
||||
@@ -251,6 +257,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens: 0,
|
||||
totalCost,
|
||||
id: responseId,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { GroqModelId, groqDefaultModelId, groqModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -102,6 +103,7 @@ 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}`)
|
||||
@@ -190,7 +192,7 @@ export class GroqHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const modelFamily = this.detectModelFamily(model.id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { hicapModelInfoSaneDefaults, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -44,7 +44,7 @@ export class HicapHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.hicapModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuaweiCloudMaasModelId, huaweiCloudMaasDefaultModelId, huaweiCloudMaasModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -30,6 +31,7 @@ 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}`)
|
||||
@@ -60,7 +62,7 @@ export class HuaweiCloudMaaSHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuggingFaceModelId, huggingFaceDefaultModelId, huggingFaceModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -37,6 +38,7 @@ 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}`)
|
||||
@@ -67,7 +69,7 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { isAnthropicModelId } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -55,6 +57,7 @@ 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}`)
|
||||
@@ -181,7 +184,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam | Anthropic.Messages.TextBlockParam = {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -29,6 +30,7 @@ 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}`)
|
||||
@@ -38,7 +40,7 @@ export class LmStudioHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { MinimaxModelId, ModelInfo, minimaxDefaultModelId, minimaxModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -34,6 +36,7 @@ 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}`)
|
||||
@@ -43,7 +46,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -64,12 +67,11 @@ export class MinimaxHandler implements ApiHandler {
|
||||
tool_choice: nativeToolsOn ? { type: "any" } : undefined,
|
||||
})
|
||||
|
||||
let thinkingDeltaAccumulator = ""
|
||||
const lastStartedToolCall = { id: "", name: "", arguments: "" }
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
case "message_start": {
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
@@ -80,6 +82,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
break
|
||||
}
|
||||
case "message_delta":
|
||||
// tells us stop_reason, stop_sequence, and output tokens along the way and at the end of the message
|
||||
yield {
|
||||
@@ -98,13 +101,11 @@ export class MinimaxHandler implements ApiHandler {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
}
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
if (chunk.content_block.thinking && chunk.content_block.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking,
|
||||
signature: chunk.content_block.signature,
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -113,10 +114,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
redacted_data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "tool_use":
|
||||
@@ -145,20 +143,19 @@ export class MinimaxHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
|
||||
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
}
|
||||
thinkingDeltaAccumulator += chunk.delta.thinking
|
||||
break
|
||||
case "signature_delta":
|
||||
// It's used when sending the thinking block back to the API
|
||||
// API expects this in completed form, not as array of deltas
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
if (chunk.delta.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
type: "reasoning",
|
||||
reasoning: "",
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Mistral } from "@mistralai/mistralai"
|
||||
import { HTTPClient } from "@mistralai/mistralai/lib/http"
|
||||
import { Tool as MistralTool } from "@mistralai/mistralai/models/components/tool"
|
||||
import { MistralModelId, ModelInfo, mistralDefaultModelId, mistralModels } from "@shared/api"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToMistralMessages } from "../transform/mistral-format"
|
||||
@@ -27,8 +29,16 @@ 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}`)
|
||||
@@ -38,7 +48,7 @@ export class MistralHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const stream = await client.chat
|
||||
.stream({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ModelInfo, MoonshotModelId, moonshotDefaultModelId, moonshotModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -29,6 +30,7 @@ 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}`)
|
||||
@@ -38,7 +40,7 @@ export class MoonshotHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, type NebiusModelId, nebiusDefaultModelId, nebiusModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -28,6 +29,7 @@ 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}`)
|
||||
@@ -37,7 +39,7 @@ export class NebiusHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ModelInfo, NousResearchModelId, nousResearchDefaultModelId, nousResearchModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface NousResearchHandlerOptions extends CommonApiHandlerOptions {
|
||||
nousResearchApiKey?: string
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
export class NousResearchHandler implements ApiHandler {
|
||||
private options: NousResearchHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
|
||||
constructor(options: NousResearchHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
private ensureClient(): OpenAI {
|
||||
if (!this.client) {
|
||||
if (!this.options.nousResearchApiKey) {
|
||||
throw new Error("NousResearch API key is required")
|
||||
}
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://inference-api.nousResearch.com/v1",
|
||||
apiKey: this.options.nousResearchApiKey,
|
||||
})
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error creating NousResearch client: ${error.message}`)
|
||||
}
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: openAiMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
})
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: (delta.reasoning_content as string | undefined) || "",
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: NousResearchModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
|
||||
if (modelId && modelId in nousResearchModels) {
|
||||
const id = modelId as NousResearchModelId
|
||||
return { id, info: nousResearchModels[id] }
|
||||
}
|
||||
return { id: nousResearchDefaultModelId, info: nousResearchModels[nousResearchDefaultModelId] }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { APIError, OpenAIError } from "openai"
|
||||
import type { FinalRequestOptions, Headers as OpenAIHeaders } from "openai/core"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { OcaAuthService } from "@/services/auth/oca/OcaAuthService"
|
||||
import {
|
||||
@@ -11,6 +9,8 @@ import {
|
||||
} from "@/services/auth/oca/utils/constants"
|
||||
import { createOcaHeaders } from "@/services/auth/oca/utils/utils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, type CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -37,7 +37,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
protected initializeClient(options: OcaHandlerOptions) {
|
||||
return new (class OCIOpenAI extends OpenAI {
|
||||
protected override async prepareOptions(opts: FinalRequestOptions<unknown>): Promise<void> {
|
||||
protected override async prepareOptions(opts: any): Promise<void> {
|
||||
const token = await OcaAuthService.getInstance().getAuthToken()
|
||||
if (!token) {
|
||||
throw new OpenAIError("Unable to handle auth, Oracle Code Assist (OCA) access token is not available")
|
||||
@@ -54,7 +54,7 @@ export class OcaHandler implements ApiHandler {
|
||||
status: number | undefined,
|
||||
error: Object | undefined,
|
||||
message: string | undefined,
|
||||
headers: OpenAIHeaders | undefined,
|
||||
headers: any | undefined,
|
||||
): APIError {
|
||||
interface OciError {
|
||||
code?: string
|
||||
@@ -74,13 +74,15 @@ export class OcaHandler implements ApiHandler {
|
||||
if (opcRequestId) {
|
||||
ociErrorMessage += `\n(${OCI_HEADER_OPC_REQUEST_ID}: ${opcRequestId})`
|
||||
}
|
||||
return super.makeStatusError(status, error, ociErrorMessage, headers)
|
||||
const statusCode = typeof status === "number" ? status : 500
|
||||
return super.makeStatusError(statusCode, error ?? {}, ociErrorMessage, headers)
|
||||
}
|
||||
})({
|
||||
baseURL:
|
||||
options.ocaBaseUrl ||
|
||||
(options.ocaMode === "internal" ? DEFAULT_INTERNAL_OCA_BASE_URL : DEFAULT_EXTERNAL_OCA_BASE_URL),
|
||||
apiKey: "noop",
|
||||
fetch, // Use configured fetch with proxy support
|
||||
})
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ export class OcaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
|
||||
@@ -149,7 +151,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
// Configuration for extended thinking
|
||||
const budgetTokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = budgetTokens !== 0 ? true : false
|
||||
const reasoningOn = budgetTokens !== 0
|
||||
const thinkingConfig = reasoningOn ? { type: "enabled", budget_tokens: budgetTokens } : undefined
|
||||
|
||||
let temperature: number | undefined = this.options.ocaModelInfo?.temperature ?? 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { type Config, type Message, Ollama } from "ollama"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOllamaMessages } from "../transform/ollama-format"
|
||||
@@ -48,7 +48,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const ollamaMessages: Message[] = [{ role: "system", content: systemPrompt }, ...convertToOllamaMessages(messages)]
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, OpenAiNativeModelId, openAiNativeDefaultModelId, openAiNativeModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToOpenAIResponsesInput } from "../transform/openai-response-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
@@ -31,6 +34,7 @@ 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}`)
|
||||
@@ -59,7 +63,20 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
@withRetry()
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
useResponseFormat = false,
|
||||
): ApiStream {
|
||||
if (useResponseFormat) {
|
||||
yield* this.createResponseStream(systemPrompt, messages, tools)
|
||||
} else {
|
||||
yield* this.createCompletionStream(systemPrompt, messages, tools)
|
||||
}
|
||||
}
|
||||
|
||||
private async *createCompletionStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
@@ -113,6 +130,9 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
case "gpt-5-2025-08-07":
|
||||
case "gpt-5-mini-2025-08-07":
|
||||
case "gpt-5-nano-2025-08-07":
|
||||
case "gpt-5.1-2025-11-13":
|
||||
case "gpt-5.1-chat-latest":
|
||||
case "gpt-5.1": {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
temperature: 1,
|
||||
@@ -146,6 +166,7 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
@@ -179,6 +200,208 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *createResponseStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
// Convert messages to Responses API input format
|
||||
const input = convertToOpenAIResponsesInput(messages)
|
||||
|
||||
// Convert ChatCompletion tools to Responses API format if provided
|
||||
const responseTools = tools
|
||||
?.filter((tool) => tool.type === "function")
|
||||
.map((tool: any) => ({
|
||||
type: "function" as const,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
parameters: tool.function.parameters,
|
||||
strict: tool.function.strict ?? true, // Responses API defaults to strict mode
|
||||
}))
|
||||
|
||||
Logger.debug("OpenAI Responses Input: " + JSON.stringify(input))
|
||||
|
||||
// const lastAssistantMessage = [...messages].reverse().find((msg) => msg.role === "assistant" && msg.id)
|
||||
// const previous_response_id = lastAssistantMessage?.id
|
||||
|
||||
// Create the response using Responses API
|
||||
const stream = await client.responses.create({
|
||||
model: model.id,
|
||||
instructions: systemPrompt,
|
||||
input,
|
||||
stream: true,
|
||||
tools: responseTools,
|
||||
// previous_response_id,
|
||||
// store: true,
|
||||
reasoning: { effort: "medium", summary: "auto" },
|
||||
// include: ["reasoning.encrypted_content"],
|
||||
})
|
||||
|
||||
// Process the response stream
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("OpenAI Responses Chunk: " + JSON.stringify(chunk))
|
||||
|
||||
// Handle different event types from Responses API
|
||||
if (chunk.type === "response.output_item.added") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call" && item.id) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning" && item.encrypted_content && item.id) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
reasoning: "",
|
||||
redacted_data: item.encrypted_content,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_item.done") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id || item.call_id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
details: item.summary,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.added") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.part.text,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_text.delta") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.done") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
details: chunk.part,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_text.delta") {
|
||||
// Handle text content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "text",
|
||||
text: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_text.delta") {
|
||||
// Handle reasoning content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.delta") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.item_id,
|
||||
arguments: chunk.delta,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.done") {
|
||||
// Handle completed function call
|
||||
if (chunk.item_id && chunk.name && chunk.arguments) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
chunk.type === "response.incomplete" &&
|
||||
chunk.response?.status === "incomplete" &&
|
||||
chunk.response?.incomplete_details?.reason === "max_output_tokens"
|
||||
) {
|
||||
console.log("Ran out of tokens")
|
||||
if (chunk.response?.output_text?.length > 0) {
|
||||
console.log("Partial output:", chunk.response.output_text)
|
||||
} else {
|
||||
console.log("Ran out of tokens during reasoning")
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.type === "response.completed" && chunk.response?.usage) {
|
||||
// Handle usage information when response is complete
|
||||
const usage = chunk.response.usage
|
||||
const inputTokens = usage.input_tokens || 0
|
||||
const outputTokens = usage.output_tokens || 0
|
||||
const cacheReadTokens = usage.output_tokens_details?.reasoning_tokens || 0
|
||||
const cacheWriteTokens = usage.input_tokens_details?.cached_tokens || 0
|
||||
const totalTokens = usage.total_tokens || 0
|
||||
Logger.log(`Total tokens from Responses API usage: ${totalTokens}`)
|
||||
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
|
||||
const nonCachedInputTokens = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: nonCachedInputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: cacheWriteTokens,
|
||||
cacheReadTokens: cacheReadTokens,
|
||||
totalCost: totalCost,
|
||||
id: chunk.response.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: OpenAiNativeModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in openAiNativeModels) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user