mirror of
https://github.com/cline/cline.git
synced 2026-09-09 06:45:53 +08:00
Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66b1b86d04 | ||
|
|
3633608c3f | ||
|
|
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 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added Nous Research provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Prevents adding multiple tool results by adding existence check
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add AGENTS.md support
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix XML entity escaping in model content processor
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added change to hide the context window usage message from env details when using next gen models and before the usage has reached an elevated state
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix task timeline display height.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Docs: Add missing proto generation step in CONTRIBUTING.md and new `npm run dev` script for easier terminal workflow (fixes #7335)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Created model-family breakouts for deep-planning prompting, and laid groundwork for similar changes for other slash commands.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Use HTTP proxies in more places
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Ensure tool arguments are streamed during file operations when native tool calling is enabled.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added thinking level setting for Gemini 3.0 Pro
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: add thought signature support for Gemini SDK
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: Enable native tool calling for Baseten and Kimi K2 models
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: restore commit msg generation functionality to command palette
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Nous Hermes 4 model family system prompt
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add Kimi K2 Thinking to Baseten Provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix OpenAI Compatiblr provider to ensure temperature parameter is explicitly converted to number
|
||||
@@ -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
|
||||
---
|
||||
|
||||
Adjusted prompting around focus chain, particularly for next-get/native tool calling models.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Anthropic provider missing signature param when thinking is enabled.
|
||||
@@ -20,6 +20,9 @@ eslint-rules/**
|
||||
.husky/**
|
||||
.env
|
||||
|
||||
# cli
|
||||
cli/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
.nvmrc
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# 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
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
@@ -76,10 +75,11 @@ func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL
|
||||
}
|
||||
}
|
||||
|
||||
// WORKAROUND: Wait for debounced state persistence to complete
|
||||
// Fixes `cline auth` issue when ran in docker environments
|
||||
// TODO: implement better solution w/ changes in StateManager
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
// Flush pending state changes to disk immediately
|
||||
// This ensures all configuration changes are persisted before the instance terminates
|
||||
if _, err := manager.GetClient().State.FlushPendingState(ctx, &cline.EmptyRequest{}); err != nil {
|
||||
return fmt.Errorf("failed to flush pending state: %w", err)
|
||||
}
|
||||
|
||||
// Success message
|
||||
fmt.Printf("\n✓ Successfully configured %s provider\n", GetProviderDisplayName(providerEnum))
|
||||
|
||||
@@ -517,7 +517,7 @@ func (pw *ProviderWizard) applyModelChange(provider cline.ApiProvider, modelID s
|
||||
ModelInfo: modelInfo,
|
||||
}
|
||||
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, false)
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, true)
|
||||
}
|
||||
|
||||
// SwitchToBYOProvider switches to a BYO provider that's already configured.
|
||||
|
||||
@@ -221,83 +221,7 @@ func (p *ToolResultParser) ParseCodeDefinitions(content string) string {
|
||||
|
||||
// ParseWebFetch formats webFetch tool results with content preview
|
||||
func (p *ToolResultParser) ParseWebFetch(content, url string) string {
|
||||
if content == "" {
|
||||
return fmt.Sprintf("*Fetched content from %s (empty response)*", url)
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
var result strings.Builder
|
||||
|
||||
// Try to extract title
|
||||
var title string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "##") {
|
||||
title = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if title != "" {
|
||||
result.WriteString(fmt.Sprintf("**Title:** %s\n\n", title))
|
||||
}
|
||||
|
||||
// Show preview of content
|
||||
result.WriteString("**Preview:**\n")
|
||||
|
||||
charCount := 0
|
||||
maxChars := 500
|
||||
previewLines := []string{}
|
||||
|
||||
for _, line := range lines {
|
||||
// Skip markdown headers
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if charCount+len(trimmed) > maxChars {
|
||||
break
|
||||
}
|
||||
|
||||
previewLines = append(previewLines, trimmed)
|
||||
charCount += len(trimmed)
|
||||
}
|
||||
|
||||
result.WriteString(strings.Join(previewLines, " "))
|
||||
result.WriteString("...\n\n")
|
||||
|
||||
// Extract sections
|
||||
sections := []string{}
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "##") {
|
||||
section := strings.TrimSpace(strings.TrimPrefix(trimmed, "##"))
|
||||
sections = append(sections, section)
|
||||
if len(sections) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sections) > 0 {
|
||||
result.WriteString("**Sections Found:**\n")
|
||||
for _, section := range sections {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", section))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// Word count estimate
|
||||
wordCount := len(strings.Fields(content))
|
||||
result.WriteString(fmt.Sprintf("*[Full content: ~%s]*", p.formatWordCount(wordCount)))
|
||||
|
||||
return result.String()
|
||||
return ""
|
||||
}
|
||||
|
||||
// detectLanguage returns syntax highlighting language based on file extension
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
|
||||
|
||||
Cline supports the following Cerebras models:
|
||||
|
||||
- `qwen-3-coder-480b-free` (Free tier) - High-performance coding model at no cost
|
||||
- `qwen-3-coder-480b` - Flagship 480B parameter coding model
|
||||
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
|
||||
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
|
||||
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
|
||||
- `llama-3.3-70b` - Meta's Llama 3.3 model optimized for speed
|
||||
|
||||
Generated
-1422
File diff suppressed because it is too large
Load Diff
+46
-45
@@ -1,47 +1,48 @@
|
||||
{
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1"
|
||||
}
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+407
-929
File diff suppressed because it is too large
Load Diff
+9
-8
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.36.1",
|
||||
"version": "3.38.1",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -417,7 +417,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",
|
||||
@@ -442,8 +442,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",
|
||||
@@ -461,7 +461,6 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"execa": "^9.5.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase": "^11.2.0",
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
@@ -471,7 +470,6 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
@@ -479,7 +477,7 @@
|
||||
"ollama": "^0.5.13",
|
||||
"open": "^10.1.2",
|
||||
"open-graph-scraper": "^6.9.0",
|
||||
"openai": "^4.83.0",
|
||||
"openai": "^6.9.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-mutex": "^1.0.0",
|
||||
"p-timeout": "^6.1.4",
|
||||
@@ -504,7 +502,10 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": ">=3.1.1"
|
||||
"tar-fs": ">=3.1.1",
|
||||
"tar": "^7.5.2",
|
||||
"vite": "^7.1.11",
|
||||
"js-yaml": "^4.1.1"
|
||||
},
|
||||
"c8": {
|
||||
"reporter": [
|
||||
|
||||
@@ -97,6 +97,7 @@ message OpenRouterModelInfo {
|
||||
optional bool supports_global_endpoint = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional string name = 13;
|
||||
optional double temperature = 14;
|
||||
}
|
||||
|
||||
// Shared response message for model information
|
||||
@@ -588,6 +589,7 @@ message ModelsApiConfiguration {
|
||||
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;
|
||||
@@ -628,4 +630,5 @@ message ModelsApiConfiguration {
|
||||
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;
|
||||
}
|
||||
|
||||
+16
-1
@@ -31,6 +31,7 @@ service StateService {
|
||||
rpc installClineCli(EmptyRequest) returns (Empty);
|
||||
rpc checkCliInstallation(EmptyRequest) returns (Boolean);
|
||||
rpc getProcessInfo(EmptyRequest) returns (ProcessInfo);
|
||||
rpc flushPendingState(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message AutoApprovalActions {
|
||||
@@ -361,7 +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 bool show_onboarding_flow = 33;
|
||||
optional OnboardingModelGroup onboarding_models = 33;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutRequest {
|
||||
@@ -389,3 +390,17 @@ message OnboardingProgressRequest {
|
||||
optional bool completed = 3;
|
||||
optional string model_selected = 4;
|
||||
}
|
||||
|
||||
message OnboardingModelGroup {
|
||||
repeated OnboardingModel models = 1;
|
||||
}
|
||||
|
||||
message OnboardingModel {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
int32 score = 3;
|
||||
int32 latency = 4;
|
||||
string badge = 5;
|
||||
string group = 6;
|
||||
OpenRouterModelInfo info = 7;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ enum ClineAsk {
|
||||
CONDENSE = 13;
|
||||
REPORT_BUG = 14;
|
||||
SUMMARIZE_TASK = 15;
|
||||
ACT_MODE_RESPOND = 16;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
|
||||
@@ -11,7 +11,7 @@ import fs from "fs"
|
||||
import https from "https"
|
||||
import path from "path"
|
||||
import { pipeline } from "stream/promises"
|
||||
import tar from "tar"
|
||||
import * as tar from "tar"
|
||||
import { promisify } from "util"
|
||||
import { createGunzip } from "zlib"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler } from "../../core/api/index"
|
||||
import { ApiStream } from "../../core/api/transform/stream"
|
||||
@@ -33,7 +33,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -255,7 +255,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
@@ -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 {
|
||||
@@ -63,14 +55,6 @@ class ClineEndpoint {
|
||||
appBaseUrl: "https://staging-app.cline.bot",
|
||||
apiBaseUrl: "https://core-api.staging.int.cline.bot",
|
||||
mcpBaseUrl: "https://core-api.staging.int.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
|
||||
authDomain: "cline-staging.firebaseapp.com",
|
||||
projectId: "cline-staging",
|
||||
storageBucket: "cline-staging.firebasestorage.app",
|
||||
messagingSenderId: "853479478430",
|
||||
appId: "1:853479478430:web:2de0dba1c63c3262d4578f",
|
||||
},
|
||||
}
|
||||
case Environment.local:
|
||||
return {
|
||||
@@ -78,11 +62,6 @@ class ClineEndpoint {
|
||||
appBaseUrl: "http://localhost:3000",
|
||||
apiBaseUrl: "http://localhost:7777",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
|
||||
authDomain: "cline-preview.firebaseapp.com",
|
||||
projectId: "cline-preview",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return {
|
||||
@@ -90,14 +69,6 @@ class ClineEndpoint {
|
||||
appBaseUrl: "https://app.cline.bot",
|
||||
apiBaseUrl: "https://api.cline.bot",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyC5rx59Xt8UgwdU3PCfzUF7vCwmp9-K2vk",
|
||||
authDomain: "cline-prod.firebaseapp.com",
|
||||
projectId: "cline-prod",
|
||||
storageBucket: "cline-prod.firebasestorage.app",
|
||||
messagingSenderId: "941048379330",
|
||||
appId: "1:941048379330:web:45058eedeefc5cdfcc485b",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -47,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>
|
||||
}
|
||||
@@ -95,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({
|
||||
@@ -167,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,
|
||||
})
|
||||
@@ -251,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({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import sinon from "sinon"
|
||||
import "should"
|
||||
import { ClaudeCodeHandler } from "@core/api/providers/claude-code"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
describe("ClaudeCodeHandler", () => {
|
||||
let handler: ClaudeCodeHandler
|
||||
@@ -71,7 +71,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -140,7 +140,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { LiteLlmHandler, type LiteLlmModelInfoResponse } from "@core/api/providers/litellm"
|
||||
import { convertToOpenAiMessages } from "@core/api/transform/openai-format"
|
||||
import { expect } from "chai"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
const fakeClient = {
|
||||
@@ -109,7 +109,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("sends the system prompt and messages with the openai format", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
@@ -161,7 +161,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("inserts the cache control in the system prompt and the last two user messages", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, before, beforeEach, describe, it } from "mocha"
|
||||
import "should"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
|
||||
describe("OllamaHandler", () => {
|
||||
@@ -59,7 +59,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
const usageInfo = []
|
||||
@@ -114,7 +114,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
// Start the request and catch the error
|
||||
let errorMessage = ""
|
||||
@@ -158,7 +158,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
@@ -204,7 +204,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { AnthropicModelId, anthropicDefaultModelId, anthropicModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
@@ -43,7 +43,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: AnthropicTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
const model = this.getModel()
|
||||
@@ -74,19 +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 = sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex)
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, true)
|
||||
|
||||
stream = await client.messages.create(
|
||||
{
|
||||
@@ -106,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.
|
||||
@@ -135,9 +123,9 @@ export class AnthropicHandler implements ApiHandler {
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
// tools,
|
||||
// tool_choice: { type: "auto" },
|
||||
messages: sanitizeAnthropicMessages(messages, false),
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
tool_choice: { type: "auto" },
|
||||
stream: true,
|
||||
})
|
||||
break
|
||||
@@ -216,7 +204,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -47,7 +47,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
try {
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
interface BasetenHandlerOptions extends CommonApiHandlerOptions {
|
||||
basetenApiKey?: string
|
||||
@@ -98,10 +100,11 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const maxTokens = this.getOptimalMaxTokens(model)
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
@@ -115,21 +118,22 @@ export class BasetenHandler implements ApiHandler {
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
tools,
|
||||
tool_choice: tools && tools.length > 0 ? "auto" : undefined,
|
||||
})
|
||||
|
||||
let didOutputUsage = false
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
const delta = chunk?.choices?.[0]?.delta
|
||||
|
||||
// Handle reasoning field if present (for reasoning models with parsed output)
|
||||
if ((delta as any)?.reasoning) {
|
||||
const reasoningContent = (delta as any).reasoning as string
|
||||
if (delta && "reasoning" in delta && delta?.reasoning) {
|
||||
const reasoning = typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning)
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningContent,
|
||||
reasoning,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle content field
|
||||
@@ -140,6 +144,10 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
// Handle usage information - only output once
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Import proper AWS SDK types
|
||||
import type { ContentBlock, Message } from "@aws-sdk/client-bedrock-runtime"
|
||||
import {
|
||||
@@ -12,6 +11,7 @@ import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI, calculateApiCostQwen } from "@utils/cost"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
@@ -121,7 +121,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ maxRetries: 4 })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
const rawModelId = await this.getModelId()
|
||||
|
||||
@@ -342,7 +342,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createDeepseekMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -480,7 +480,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* First uses convertToR1Format to merge consecutive messages with the same role,
|
||||
* then converts to the string format that DeepSeek R1 expects
|
||||
*/
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// First use convertToR1Format to merge consecutive messages with the same role
|
||||
const r1Messages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
|
||||
@@ -513,7 +513,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Estimates token count based on text length (approximate)
|
||||
* Note: This is a rough estimation, as the actual token count depends on the tokenizer
|
||||
*/
|
||||
private estimateInputTokens(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): number {
|
||||
private estimateInputTokens(systemPrompt: string, messages: ClineStorageMessage[]): number {
|
||||
// For Deepseek R1, we estimate the token count of the formatted prompt
|
||||
// The formatted prompt includes special tokens and consistent formatting
|
||||
const formattedPrompt = this.formatDeepseekR1Prompt(systemPrompt, messages)
|
||||
@@ -680,11 +680,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing Converse API response:", error)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Failed to process response: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,9 +699,20 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
text: `[ERROR] Model stream error: ${chunk.modelStreamErrorException.message}`,
|
||||
}
|
||||
} else if (chunk.validationException) {
|
||||
// Check if this is a context window error - if so, throw it
|
||||
// so the retry mechanism can handle truncation
|
||||
const message = chunk.validationException.message || ""
|
||||
const isContextError = /input.*too long|context.*exceed|maximum.*token|input length.*max.*tokens/i.test(message)
|
||||
|
||||
if (isContextError) {
|
||||
// Throw as exception so context management can handle it
|
||||
throw chunk.validationException
|
||||
}
|
||||
|
||||
// Otherwise yield as error text
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Validation error: ${chunk.validationException.message}`,
|
||||
text: `[ERROR] Validation error: ${message}`,
|
||||
}
|
||||
} else if (chunk.throttlingException) {
|
||||
yield {
|
||||
@@ -779,7 +786,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createAnthropicMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
enable1mContextWindow: boolean,
|
||||
@@ -835,7 +842,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Formats messages for models using the Converse API specification
|
||||
* Used by both Anthropic and Nova models to avoid code duplication
|
||||
*/
|
||||
private formatMessagesForConverseAPI(messages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
private formatMessagesForConverseAPI(messages: ClineStorageMessage[]): Message[] {
|
||||
return messages.map((message) => {
|
||||
// Determine role (user or assistant)
|
||||
const role = message.role === "user" ? ConversationRole.USER : ConversationRole.ASSISTANT
|
||||
@@ -968,7 +975,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createNovaMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1008,7 +1015,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createOpenAIMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1143,7 +1150,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createQwenMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { CerebrasModelId, cerebrasDefaultModelId, cerebrasModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -46,7 +46,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
baseDelay: 5000, // Start with 5 second delay
|
||||
maxDelay: 60000, // Allow up to 60 second delays to respect rate limits
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
// Convert Anthropic messages to Cerebras format
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { filterMessagesForClaudeCode } from "@/integrations/claude-code/message-filter"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { ClaudeCodeModelId, claudeCodeDefaultModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { type ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { type ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
@@ -24,7 +24,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// Filter out image blocks since Claude Code doesn't support them
|
||||
const filteredMessages = filterMessagesForClaudeCode(messages)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
@@ -8,7 +7,9 @@ import { ClineEnv } from "@/config"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -26,6 +27,7 @@ interface ClineHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
clineAccountId?: string
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class ClineHandler implements ApiHandler {
|
||||
@@ -96,7 +98,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = await this.ensureClient()
|
||||
|
||||
@@ -114,11 +116,13 @@ export class ClineHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("ClineHandler chunk:" + JSON.stringify(chunk))
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
@@ -149,6 +153,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const delta = choice?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -180,7 +185,7 @@ 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 {
|
||||
@@ -190,11 +195,13 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -75,7 +75,7 @@ export class DeepSeekHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
@@ -97,7 +97,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -384,7 +384,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DoubaoModelId, doubaoDefaultModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -50,7 +50,7 @@ export class DoubaoHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { FireworksModelId, fireworksDefaultModelId, fireworksModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -41,7 +41,7 @@ export class FireworksHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.fireworksModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import {
|
||||
ApiError,
|
||||
@@ -7,10 +6,11 @@ import {
|
||||
type GenerateContentResponseUsageMetadata,
|
||||
GoogleGenAI,
|
||||
FunctionDeclaration as GoogleTool,
|
||||
Part,
|
||||
ThinkingLevel,
|
||||
} from "@google/genai"
|
||||
import { GeminiModelId, geminiDefaultModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { RetriableError, withRetry } from "../retry"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
@@ -28,6 +28,7 @@ interface GeminiHandlerOptions extends CommonApiHandlerOptions {
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
thinkingBudgetTokens?: number
|
||||
thinkingLevel?: string
|
||||
apiModelId?: string
|
||||
ulid?: string
|
||||
}
|
||||
@@ -110,34 +111,49 @@ export class GeminiHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: GoogleTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: GoogleTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const { id: modelId, info } = this.getModel()
|
||||
const contents = messages.map(convertAnthropicMessageToGemini)
|
||||
|
||||
// Configure thinking budget if supported
|
||||
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const _maxBudget = info.thinkingConfig?.maxBudget ?? 0
|
||||
const _thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const maxBudget = info.thinkingConfig?.maxBudget ?? 24576
|
||||
const thinkingBudget = Math.min(_thinkingBudget, maxBudget)
|
||||
// When ThinkingLevel is defineded, thinking budget cannot be zero
|
||||
// and only level is used to control thinking behavior.
|
||||
let thinkingLevel: ThinkingLevel | undefined
|
||||
if (this.options.thinkingLevel === "low") {
|
||||
thinkingLevel = ThinkingLevel.LOW
|
||||
} else if (this.options.thinkingLevel === "high") {
|
||||
thinkingLevel = ThinkingLevel.HIGH
|
||||
}
|
||||
|
||||
// Set up base generation config
|
||||
const requestConfig: GenerateContentConfig = {
|
||||
// Add base URL if configured
|
||||
httpOptions: this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined,
|
||||
...{ systemInstruction: systemPrompt },
|
||||
systemInstruction: systemPrompt,
|
||||
// Set temperature (default to 0)
|
||||
temperature: 0,
|
||||
// Gemini 3.0 recommends 1.0
|
||||
temperature: info.temperature ?? 1,
|
||||
}
|
||||
|
||||
// Add thinking config if the model supports it
|
||||
if (thinkingBudget > 0) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: thinkingBudget,
|
||||
includeThoughts: true,
|
||||
}
|
||||
requestConfig.thinkingConfig = {
|
||||
// Turn off thinking:
|
||||
// thinkingBudget: 0
|
||||
// Turn on dynamic thinking:
|
||||
// thinkingBudget: -1
|
||||
// Turn on fixed thinking budget:
|
||||
thinkingBudget: thinkingLevel ? undefined : thinkingBudget,
|
||||
thinkingLevel,
|
||||
includeThoughts: thinkingBudget > 0 || !!thinkingLevel,
|
||||
}
|
||||
|
||||
// Generate content using the configured parameters
|
||||
const sdkCallStartTime = Date.now()
|
||||
let responseId: string | undefined
|
||||
let sdkFirstChunkTime: number | undefined
|
||||
let ttftSdkMs: number | undefined
|
||||
let apiSuccess = false
|
||||
@@ -148,7 +164,8 @@ export class GeminiHandler implements ApiHandler {
|
||||
let thoughtsTokenCount = 0 // Initialize thought token counts
|
||||
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
|
||||
|
||||
if (tools?.length) {
|
||||
const isNativeToolCallsEnabled = tools?.length
|
||||
if (isNativeToolCallsEnabled) {
|
||||
requestConfig.tools = [{ functionDeclarations: tools }]
|
||||
requestConfig.toolConfig = {
|
||||
// Force the model to call 'any' function.
|
||||
@@ -176,56 +193,45 @@ export class GeminiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Handle thinking content from Gemini's response
|
||||
const candidateForThoughts = chunk?.candidates?.[0]
|
||||
const partsForThoughts = candidateForThoughts?.content?.parts
|
||||
let thoughts = "" // Initialize as empty string
|
||||
|
||||
if (partsForThoughts) {
|
||||
// This ensures partsForThoughts is a Part[] array
|
||||
for (const part of partsForThoughts) {
|
||||
const { thought, text } = part as Part
|
||||
if (thought && text) {
|
||||
// Ensure part.text exists
|
||||
// Handle the thought part
|
||||
thoughts += text + "\n" // Append thought and a newline
|
||||
const parts = chunk?.candidates?.[0]?.content?.parts || []
|
||||
for (const part of parts) {
|
||||
if (part.thought && part.text) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.responseId,
|
||||
reasoning: part.text || "",
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
} else if (part.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: part.text,
|
||||
id: chunk.responseId,
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thoughts.trim() !== "") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thoughts.trim(),
|
||||
}
|
||||
thoughts = "" // Reset thoughts after yielding
|
||||
}
|
||||
|
||||
if (chunk.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.text,
|
||||
}
|
||||
}
|
||||
|
||||
if (tools && chunk.functionCalls && chunk.functionCalls?.length > 0) {
|
||||
for (const functionCall of chunk.functionCalls) {
|
||||
if (functionCall.args) {
|
||||
console.log("[GeminiHandler] tool call received:", functionCall)
|
||||
if (part.functionCall) {
|
||||
const functionCall = part.functionCall
|
||||
const args = Object.entries(functionCall.args || {}).filter(([_key, val]) => !!val)
|
||||
if (functionCall.args && args.length > 0) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: chunk.responseId,
|
||||
tool_call: {
|
||||
function: {
|
||||
id: functionCall.id || functionCall.name,
|
||||
id: chunk.responseId,
|
||||
name: functionCall.name,
|
||||
arguments: JSON.stringify(functionCall.args),
|
||||
},
|
||||
},
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usageMetadata) {
|
||||
responseId = chunk.responseId
|
||||
lastUsageMetadata = chunk.usageMetadata
|
||||
promptTokens = lastUsageMetadata.promptTokenCount ?? promptTokens
|
||||
outputTokens = lastUsageMetadata.candidatesTokenCount ?? outputTokens
|
||||
@@ -251,6 +257,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens: 0,
|
||||
totalCost,
|
||||
id: responseId,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { GroqModelId, groqDefaultModelId, groqModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -192,7 +192,7 @@ export class GroqHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const modelFamily = this.detectModelFamily(model.id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { hicapModelInfoSaneDefaults, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -44,7 +44,7 @@ export class HicapHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.hicapModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuaweiCloudMaasModelId, huaweiCloudMaasDefaultModelId, huaweiCloudMaasModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -62,7 +62,7 @@ export class HuaweiCloudMaaSHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuggingFaceModelId, huggingFaceDefaultModelId, huggingFaceModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -69,7 +69,7 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { isAnthropicModelId } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
@@ -183,7 +184,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam | Anthropic.Messages.TextBlockParam = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -40,7 +40,7 @@ export class LmStudioHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { MinimaxModelId, ModelInfo, minimaxDefaultModelId, minimaxModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
@@ -45,7 +46,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Mistral } from "@mistralai/mistralai"
|
||||
import { HTTPClient } from "@mistralai/mistralai/lib/http"
|
||||
import { Tool as MistralTool } from "@mistralai/mistralai/models/components/tool"
|
||||
import { MistralModelId, ModelInfo, mistralDefaultModelId, mistralModels } from "@shared/api"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -48,7 +48,7 @@ export class MistralHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const stream = await client.chat
|
||||
.stream({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ModelInfo, MoonshotModelId, moonshotDefaultModelId, moonshotModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -40,7 +40,7 @@ export class MoonshotHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, type NebiusModelId, nebiusDefaultModelId, nebiusModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -39,7 +39,7 @@ export class NebiusHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
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"
|
||||
@@ -37,7 +37,7 @@ export class NousResearchHandler 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()
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { APIError, OpenAIError } from "openai"
|
||||
import type { FinalRequestOptions, Headers as OpenAIHeaders } from "openai/core"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { OcaAuthService } from "@/services/auth/oca/OcaAuthService"
|
||||
import {
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
} from "@/services/auth/oca/utils/constants"
|
||||
import { createOcaHeaders } from "@/services/auth/oca/utils/utils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, type CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -38,7 +37,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
protected initializeClient(options: OcaHandlerOptions) {
|
||||
return new (class OCIOpenAI extends OpenAI {
|
||||
protected override async prepareOptions(opts: FinalRequestOptions<unknown>): Promise<void> {
|
||||
protected override async prepareOptions(opts: any): Promise<void> {
|
||||
const token = await OcaAuthService.getInstance().getAuthToken()
|
||||
if (!token) {
|
||||
throw new OpenAIError("Unable to handle auth, Oracle Code Assist (OCA) access token is not available")
|
||||
@@ -55,7 +54,7 @@ export class OcaHandler implements ApiHandler {
|
||||
status: number | undefined,
|
||||
error: Object | undefined,
|
||||
message: string | undefined,
|
||||
headers: OpenAIHeaders | undefined,
|
||||
headers: any | undefined,
|
||||
): APIError {
|
||||
interface OciError {
|
||||
code?: string
|
||||
@@ -75,7 +74,8 @@ export class OcaHandler implements ApiHandler {
|
||||
if (opcRequestId) {
|
||||
ociErrorMessage += `\n(${OCI_HEADER_OPC_REQUEST_ID}: ${opcRequestId})`
|
||||
}
|
||||
return super.makeStatusError(status, error, ociErrorMessage, headers)
|
||||
const statusCode = typeof status === "number" ? status : 500
|
||||
return super.makeStatusError(statusCode, error ?? {}, ociErrorMessage, headers)
|
||||
}
|
||||
})({
|
||||
baseURL:
|
||||
@@ -139,7 +139,7 @@ export class OcaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
|
||||
@@ -151,7 +151,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
// Configuration for extended thinking
|
||||
const budgetTokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = budgetTokens !== 0 ? true : false
|
||||
const reasoningOn = budgetTokens !== 0
|
||||
const thinkingConfig = reasoningOn ? { type: "enabled", budget_tokens: budgetTokens } : undefined
|
||||
|
||||
let temperature: number | undefined = this.options.ocaModelInfo?.temperature ?? 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { type Config, type Message, Ollama } from "ollama"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOllamaMessages } from "../transform/ollama-format"
|
||||
@@ -48,7 +48,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const ollamaMessages: Message[] = [{ role: "system", content: systemPrompt }, ...convertToOllamaMessages(messages)]
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, OpenAiNativeModelId, openAiNativeDefaultModelId, openAiNativeModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToOpenAIResponsesInput } from "../transform/openai-response-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
@@ -61,7 +63,20 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
@withRetry()
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
useResponseFormat = false,
|
||||
): ApiStream {
|
||||
if (useResponseFormat) {
|
||||
yield* this.createResponseStream(systemPrompt, messages, tools)
|
||||
} else {
|
||||
yield* this.createCompletionStream(systemPrompt, messages, tools)
|
||||
}
|
||||
}
|
||||
|
||||
private async *createCompletionStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
@@ -114,7 +129,10 @@ 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-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,
|
||||
@@ -182,6 +200,208 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *createResponseStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
// Convert messages to Responses API input format
|
||||
const input = convertToOpenAIResponsesInput(messages)
|
||||
|
||||
// Convert ChatCompletion tools to Responses API format if provided
|
||||
const responseTools = tools
|
||||
?.filter((tool) => tool.type === "function")
|
||||
.map((tool: any) => ({
|
||||
type: "function" as const,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
parameters: tool.function.parameters,
|
||||
strict: tool.function.strict ?? true, // Responses API defaults to strict mode
|
||||
}))
|
||||
|
||||
Logger.debug("OpenAI Responses Input: " + JSON.stringify(input))
|
||||
|
||||
// const lastAssistantMessage = [...messages].reverse().find((msg) => msg.role === "assistant" && msg.id)
|
||||
// const previous_response_id = lastAssistantMessage?.id
|
||||
|
||||
// Create the response using Responses API
|
||||
const stream = await client.responses.create({
|
||||
model: model.id,
|
||||
instructions: systemPrompt,
|
||||
input,
|
||||
stream: true,
|
||||
tools: responseTools,
|
||||
// previous_response_id,
|
||||
// store: true,
|
||||
reasoning: { effort: "medium", summary: "auto" },
|
||||
// include: ["reasoning.encrypted_content"],
|
||||
})
|
||||
|
||||
// Process the response stream
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("OpenAI Responses Chunk: " + JSON.stringify(chunk))
|
||||
|
||||
// Handle different event types from Responses API
|
||||
if (chunk.type === "response.output_item.added") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call" && item.id) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning" && item.encrypted_content && item.id) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
reasoning: "",
|
||||
redacted_data: item.encrypted_content,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_item.done") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id || item.call_id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
details: item.summary,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.added") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.part.text,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_text.delta") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.done") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
details: chunk.part,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_text.delta") {
|
||||
// Handle text content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "text",
|
||||
text: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_text.delta") {
|
||||
// Handle reasoning content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.delta") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.item_id,
|
||||
arguments: chunk.delta,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.done") {
|
||||
// Handle completed function call
|
||||
if (chunk.item_id && chunk.name && chunk.arguments) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
chunk.type === "response.incomplete" &&
|
||||
chunk.response?.status === "incomplete" &&
|
||||
chunk.response?.incomplete_details?.reason === "max_output_tokens"
|
||||
) {
|
||||
console.log("Ran out of tokens")
|
||||
if (chunk.response?.output_text?.length > 0) {
|
||||
console.log("Partial output:", chunk.response.output_text)
|
||||
} else {
|
||||
console.log("Ran out of tokens during reasoning")
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.type === "response.completed" && chunk.response?.usage) {
|
||||
// Handle usage information when response is complete
|
||||
const usage = chunk.response.usage
|
||||
const inputTokens = usage.input_tokens || 0
|
||||
const outputTokens = usage.output_tokens || 0
|
||||
const cacheReadTokens = usage.output_tokens_details?.reasoning_tokens || 0
|
||||
const cacheWriteTokens = usage.input_tokens_details?.cached_tokens || 0
|
||||
const totalTokens = usage.total_tokens || 0
|
||||
Logger.log(`Total tokens from Responses API usage: ${totalTokens}`)
|
||||
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
|
||||
const nonCachedInputTokens = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: nonCachedInputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: cacheWriteTokens,
|
||||
cacheReadTokens: cacheReadTokens,
|
||||
totalCost: totalCost,
|
||||
id: chunk.response.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: OpenAiNativeModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in openAiNativeModels) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { azureOpenAiDefaultApiVersion, ModelInfo, OpenAiCompatibleModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { AzureOpenAI } from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -65,11 +65,7 @@ export class OpenAiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ChatCompletionTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.openAiModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { StateManager } from "@core/storage/StateManager"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -20,6 +21,7 @@ interface OpenRouterHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterProviderSorting?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class OpenRouterHandler implements ApiHandler {
|
||||
@@ -54,7 +56,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
this.lastGenerationId = undefined
|
||||
|
||||
@@ -67,6 +69,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
let didOutputUsage: boolean = false
|
||||
@@ -214,11 +217,11 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.openRouterModelId
|
||||
const modelInfo = this.options.openRouterModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
const modelId = this.options.openRouterModelId || openRouterDefaultModelId
|
||||
const cachedModelInfo = StateManager.get().getModelInfo("openRouter", modelId)
|
||||
return {
|
||||
id: modelId,
|
||||
info: cachedModelInfo || openRouterDefaultModelInfo,
|
||||
}
|
||||
return { id: openRouterDefaultModelId, info: openRouterDefaultModelInfo }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { promises as fs } from "node:fs"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, QwenCodeModelId, qwenCodeDefaultModelId, qwenCodeModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -177,7 +177,7 @@ export class QwenCodeHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
await this.ensureAuthenticated()
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import {
|
||||
InternationalQwenModelId,
|
||||
internationalQwenDefaultModelId,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
} 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"
|
||||
@@ -81,7 +81,7 @@ export class QwenHandler 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 isDeepseekReasoner = model.id.includes("deepseek-r1")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import { toRequestyServiceStringUrl } from "@/shared/clients/requesty"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -59,7 +59,7 @@ export class RequestyHandler 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()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -42,7 +42,7 @@ export class SambanovaHandler 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()
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import {
|
||||
ConversationRole as BedrockConversationRole,
|
||||
type Message as BedrockMessage,
|
||||
} from "@aws-sdk/client-bedrock-runtime"
|
||||
import { ChatMessages, LlmModuleConfig, OrchestrationClient, TemplatingModuleConfig } from "@sap-ai-sdk/orchestration"
|
||||
import { ChatMessage, OrchestrationClient, OrchestrationModuleConfig } from "@sap-ai-sdk/orchestration"
|
||||
import { ModelInfo, SapAiCoreModelId, sapAiCoreDefaultModelId, sapAiCoreModels } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -115,7 +116,7 @@ namespace Bedrock {
|
||||
* Formats messages for models using the Converse API specification
|
||||
* Used by both Anthropic and Nova models to avoid code duplication
|
||||
*/
|
||||
export function formatMessagesForConverseAPI(messages: Anthropic.Messages.MessageParam[]): BedrockMessage[] {
|
||||
export function formatMessagesForConverseAPI(messages: ClineStorageMessage[]): BedrockMessage[] {
|
||||
return messages.map((message) => {
|
||||
// Determine role (user or assistant)
|
||||
const role = message.role === "user" ? BedrockConversationRole.USER : BedrockConversationRole.ASSISTANT
|
||||
@@ -315,7 +316,7 @@ namespace Gemini {
|
||||
*/
|
||||
export function prepareRequestPayload(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
messages: ClineStorageMessage[],
|
||||
model: { id: SapAiCoreModelId; info: ModelInfo },
|
||||
thinkingBudgetTokens?: number,
|
||||
): any {
|
||||
@@ -458,7 +459,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
if (this.options.sapAiCoreUseOrchestrationMode) {
|
||||
yield* this.createMessageWithOrchestration(systemPrompt, messages)
|
||||
} else {
|
||||
@@ -490,29 +491,31 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
this.isAiCoreEnvSetup = true
|
||||
}
|
||||
|
||||
private async *createMessageWithOrchestration(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
private async *createMessageWithOrchestration(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
try {
|
||||
// Ensure AI Core environment variable is set up (only runs once)
|
||||
this.ensureAiCoreEnvSetup()
|
||||
const model = this.getModel()
|
||||
|
||||
// Define the LLM to be used by the Orchestration pipeline
|
||||
const llm: LlmModuleConfig = {
|
||||
model_name: model.id,
|
||||
const orchestrationConfig: OrchestrationModuleConfig = {
|
||||
promptTemplating: {
|
||||
model: {
|
||||
name: model.id,
|
||||
},
|
||||
prompt: {
|
||||
template: [
|
||||
{
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const templating: TemplatingModuleConfig = {
|
||||
template: [
|
||||
{
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
},
|
||||
],
|
||||
}
|
||||
const orchestrationClient = new OrchestrationClient(
|
||||
{ llm, templating },
|
||||
{ resourceGroup: this.options.sapAiResourceGroup || "default" },
|
||||
)
|
||||
const orchestrationClient = new OrchestrationClient(orchestrationConfig, {
|
||||
resourceGroup: this.options.sapAiResourceGroup || "default",
|
||||
})
|
||||
|
||||
const sapMessages = this.convertMessageParamToSAPMessages(messages)
|
||||
|
||||
@@ -538,7 +541,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *createMessageWithDeployments(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
private async *createMessageWithDeployments(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
const token = await this.getToken()
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -1040,8 +1043,8 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
return { id: sapAiCoreDefaultModelId, info: sapAiCoreModels[sapAiCoreDefaultModelId] }
|
||||
}
|
||||
private convertMessageParamToSAPMessages(messages: Anthropic.Messages.MessageParam[]): ChatMessages {
|
||||
private convertMessageParamToSAPMessages(messages: ClineStorageMessage[]): ChatMessage[] {
|
||||
// Use the existing OpenAI converter since the logic is identical
|
||||
return convertToOpenAiMessages(messages) as ChatMessages
|
||||
return convertToOpenAiMessages(messages) as ChatMessage[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -42,7 +42,7 @@ export class TogetherHandler 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 modelId = this.options.togetherModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } 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"
|
||||
@@ -47,7 +47,7 @@ export class VercelAIGatewayHandler 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 modelId = this.getModel().id
|
||||
const modelInfo = this.getModel().info
|
||||
@@ -82,8 +82,7 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,22 +101,16 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
const inputTokens = chunk.usage.prompt_tokens || 0
|
||||
const outputTokens =
|
||||
(chunk.usage.completion_tokens || 0) + (chunk.usage.completion_tokens_details?.reasoning_tokens || 0)
|
||||
|
||||
const cacheReadTokens = chunk.usage.prompt_tokens_details?.cached_tokens || 0
|
||||
// @ts-ignore - Vercel AI Gateway extends OpenAI types
|
||||
const cacheWriteTokens = chunk.usage.cache_creation_input_tokens || 0
|
||||
const totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: cacheWriteTokens,
|
||||
cacheReadTokens: cacheReadTokens,
|
||||
// @ts-expect-error - Vercel AI Gateway extends OpenAI types
|
||||
totalCost: chunk.usage.cost || 0,
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: (chunk.usage.prompt_tokens || 0) - (chunk.usage.prompt_tokens_details?.cached_tokens || 0),
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
totalCost,
|
||||
}
|
||||
didOutputUsage = true
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"
|
||||
import { FunctionDeclaration as GoogleTool } from "@google/genai"
|
||||
import { ModelInfo, VertexModelId, vertexDefaultModelId, vertexModels } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -67,7 +67,7 @@ export class VertexHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
const modelId = model.id
|
||||
|
||||
@@ -103,13 +103,6 @@ export class VertexHandler implements ApiHandler {
|
||||
case "claude-3-5-haiku@20241022":
|
||||
case "claude-3-opus@20240229":
|
||||
case "claude-3-haiku@20240307": {
|
||||
// Find indices of user messages for cache control
|
||||
const userMsgIndices = messages.reduce(
|
||||
(acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc),
|
||||
[] as number[],
|
||||
)
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
stream = await clientAnthropic.beta.messages.create(
|
||||
{
|
||||
model: modelId,
|
||||
@@ -123,7 +116,7 @@ export class VertexHandler implements ApiHandler {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
messages: sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex),
|
||||
messages: sanitizeAnthropicMessages(messages, true),
|
||||
stream: true,
|
||||
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
@@ -149,7 +142,7 @@ export class VertexHandler implements ApiHandler {
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
messages: sanitizeAnthropicMessages(messages, false),
|
||||
stream: true,
|
||||
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "@shared/vsCodeSelectorUtils"
|
||||
import { calculateApiCostAnthropic } from "@utils/cost"
|
||||
import * as vscode from "vscode"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions, SingleCompletionHandler } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -366,7 +366,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
// Ensure clean state before starting a new request
|
||||
this.ensureCleanState()
|
||||
const client: vscode.LanguageModelChat = await this.getClient()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, XAIModelId, xaiDefaultModelId, xaiModels } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -44,7 +44,7 @@ export class XAIHandler 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 modelId = this.getModel().id
|
||||
// ensure reasoning effort is either "low" or "high" for grok-3-mini
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import {
|
||||
internationalZAiDefaultModelId,
|
||||
internationalZAiModelId,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
} 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 { version as extensionVersion } from "../../../../package.json"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
@@ -76,7 +76,7 @@ export class ZAiHandler 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,77 +1,90 @@
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage, convertClineStorageToAnthropicMessage } from "@/shared/messages/content"
|
||||
|
||||
/**
|
||||
* Sanitize Anthropic messages by removing reasoning details and adding ephemeral cache control
|
||||
* to the last two user messages to prevent them from being stored in Anthropic's cache.
|
||||
* Converts Cline storage messages to Anthropic API format with optional cache control.
|
||||
* Adds ephemeral cache control to the last two user messages to prevent them from being
|
||||
* stored in Anthropic's cache.
|
||||
*
|
||||
* @param clineMessages - Array of Cline storage messages to convert
|
||||
* @param lastUserMsgIndex - Optional index of the last user message
|
||||
* @param secondLastMsgUserIndex - Optional index of the second-to-last user message
|
||||
* @returns Array of Anthropic-compatible messages with cache control applied
|
||||
*/
|
||||
export function sanitizeAnthropicMessages(
|
||||
messages: Array<ClineStorageMessage>,
|
||||
lastUserMsgIndex?: number,
|
||||
secondLastMsgUserIndex?: number,
|
||||
): Array<ClineStorageMessage> {
|
||||
return messages.map((_message, index) => {
|
||||
const message = removeReasoningDetails(_message)
|
||||
const addCacheControl = lastUserMsgIndex !== undefined && secondLastMsgUserIndex !== undefined
|
||||
clineMessages: Array<ClineStorageMessage | Anthropic.MessageParam>,
|
||||
supportCache: boolean,
|
||||
): Array<Anthropic.MessageParam> {
|
||||
// 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 = clineMessages.reduce((acc, msg, index) => {
|
||||
if (msg.role === "user") {
|
||||
acc.push(index)
|
||||
}
|
||||
return acc
|
||||
}, [] as number[])
|
||||
// Set to -1 if there are no user messages so the indices are invalid
|
||||
const indicesLength = userMsgIndices.length ?? -1
|
||||
const lastUserMsgIndex = userMsgIndices[indicesLength - 1]
|
||||
const secondLastMsgUserIndex = userMsgIndices[indicesLength - 2]
|
||||
|
||||
if (addCacheControl && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
}
|
||||
return clineMessages.map((msg, index) => {
|
||||
const anthropicMsg = convertClineStorageToAnthropicMessage(msg)
|
||||
|
||||
// Add cache control to the last two user messages
|
||||
if (supportCache && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
|
||||
return addCacheControl(anthropicMsg)
|
||||
}
|
||||
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
}
|
||||
return anthropicMsg
|
||||
})
|
||||
}
|
||||
|
||||
const isThinkingBlock = (
|
||||
block: Anthropic.ContentBlockParam,
|
||||
): block is Anthropic.Messages.ThinkingBlockParam | Anthropic.Messages.RedactedThinkingBlockParam => {
|
||||
return block.type === "thinking" || block.type === "redacted_thinking"
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove reasoning details from a single Anthropic message parameter
|
||||
* Adds ephemeral cache control to the last content block of a message.
|
||||
* Returns a new message object without mutating the original.
|
||||
*
|
||||
* @param message - The Anthropic message to add cache control to
|
||||
* @returns A new message with cache control added to the last content block
|
||||
*/
|
||||
function removeReasoningDetails(param: ClineStorageMessage): ClineStorageMessage {
|
||||
if (Array.isArray(param.content)) {
|
||||
function addCacheControl(message: Anthropic.MessageParam): Anthropic.MessageParam {
|
||||
// Convert string content to array format
|
||||
if (typeof message.content === "string") {
|
||||
return {
|
||||
...param,
|
||||
content: param.content.map((item) => {
|
||||
if (item.type === "text") {
|
||||
return {
|
||||
...item,
|
||||
reasoning_details: undefined,
|
||||
}
|
||||
}
|
||||
return item
|
||||
}),
|
||||
...message,
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: { type: "ephemeral" },
|
||||
} satisfies Anthropic.TextBlockParam,
|
||||
],
|
||||
}
|
||||
}
|
||||
return param
|
||||
|
||||
// Handle array content - add cache control to the last block
|
||||
const content = [...message.content]
|
||||
const lastIndex = content.length - 1
|
||||
|
||||
if (lastIndex >= 0) {
|
||||
const lastBlock = content[lastIndex]
|
||||
|
||||
// Only add cache_control to block types that support it (not ThinkingBlockParam)
|
||||
if (!isThinkingBlock(lastBlock)) {
|
||||
content[lastIndex] = {
|
||||
...lastBlock,
|
||||
cache_control: { type: "ephemeral" },
|
||||
} satisfies Anthropic.ContentBlockParam
|
||||
}
|
||||
}
|
||||
|
||||
return { ...message, content }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Content, GenerateContentResponse, Part } from "@google/genai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
export function convertAnthropicContentToGemini(content: string | Anthropic.ContentBlockParam[]): Part[] {
|
||||
export function convertAnthropicContentToGemini(content: string | ClineStorageMessage["content"]): Part[] {
|
||||
if (typeof content === "string") {
|
||||
return [{ text: content }]
|
||||
}
|
||||
@@ -9,7 +10,7 @@ export function convertAnthropicContentToGemini(content: string | Anthropic.Cont
|
||||
.flatMap((block): Part | undefined => {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { text: block.text }
|
||||
return { text: block.text, thoughtSignature: block.signature }
|
||||
case "image":
|
||||
if (block.source.type !== "base64") {
|
||||
throw new Error("Unsupported image source type")
|
||||
@@ -26,6 +27,7 @@ export function convertAnthropicContentToGemini(content: string | Anthropic.Cont
|
||||
name: block.name,
|
||||
args: block.input as Record<string, unknown>,
|
||||
},
|
||||
thoughtSignature: block.signature,
|
||||
}
|
||||
case "tool_result":
|
||||
return {
|
||||
|
||||
@@ -10,6 +10,15 @@ import {
|
||||
ClineUserToolResultContentBlock,
|
||||
} from "@/shared/messages/content"
|
||||
|
||||
/**
|
||||
* Converts an array of ClineStorageMessage objects to OpenAI's Completions API format.
|
||||
*
|
||||
* Handles conversion of Cline-specific content types (tool uses, tool results, images, reasoning details)
|
||||
* into OpenAI's expected message structure, including tool_calls and tool_call_id fields.
|
||||
*
|
||||
* @param anthropicMessages - Array of ClineStorageMessage objects to be converted
|
||||
* @returns Array of OpenAI.Chat.ChatCompletionMessageParam objects
|
||||
*/
|
||||
export function convertToOpenAiMessages(
|
||||
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
|
||||
): OpenAI.Chat.ChatCompletionMessageParam[] {
|
||||
@@ -150,6 +159,7 @@ export function convertToOpenAiMessages(
|
||||
// delete part.reasoning_details
|
||||
}
|
||||
if (part.type === "thinking" && part.thinking) {
|
||||
// Reasoning details should have been moved to the text block
|
||||
thinkingBlock.push(part)
|
||||
}
|
||||
})
|
||||
@@ -164,15 +174,26 @@ export function convertToOpenAiMessages(
|
||||
}
|
||||
|
||||
// Process tool use messages
|
||||
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] = toolMessages.map((toolMessage) => ({
|
||||
id: toolMessage.id,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolMessage.name,
|
||||
// json string
|
||||
arguments: JSON.stringify(toolMessage.input),
|
||||
},
|
||||
}))
|
||||
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] = toolMessages.map((toolMessage) => {
|
||||
const toolDetails = toolMessage.reasoning_details
|
||||
if (toolDetails?.length) {
|
||||
if (Array.isArray(toolDetails)) {
|
||||
reasoningDetails.push(...toolDetails)
|
||||
} else {
|
||||
reasoningDetails.push(toolDetails)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: toolMessage.id,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolMessage.name,
|
||||
// json string
|
||||
arguments: JSON.stringify(toolMessage.input),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Set content to blank when tool_calls are present but content has no text, per OpenAI API spec
|
||||
const hasToolCalls = tool_calls.length > 0
|
||||
@@ -339,29 +360,30 @@ export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.Ch
|
||||
}
|
||||
try {
|
||||
if (openAiMessage?.tool_calls?.length) {
|
||||
anthropicMessage.content.push(
|
||||
...openAiMessage.tool_calls
|
||||
.map((toolCall): Anthropic.ToolUseBlock => {
|
||||
const parsedName = toolCall.type === "function" && toolCall.function.name
|
||||
let parsedInput = toolCall.function.arguments
|
||||
const functionCalls = openAiMessage.tool_calls.filter((tc: any) => tc?.type === "function" && tc.function)
|
||||
if (functionCalls.length > 0) {
|
||||
anthropicMessage.content.push(
|
||||
...functionCalls.map((toolCall: any): Anthropic.ToolUseBlock => {
|
||||
let parsedInput = {}
|
||||
try {
|
||||
parsedInput = JSON.parse(toolCall.function.arguments || "{}")
|
||||
parsedInput = JSON.parse(toolCall.function?.arguments || "{}")
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tool arguments:", error)
|
||||
}
|
||||
return {
|
||||
type: "tool_use",
|
||||
id: toolCall.id,
|
||||
name: parsedName || UNIQUE_ERROR_TOOL_NAME,
|
||||
name: toolCall.function?.name || UNIQUE_ERROR_TOOL_NAME,
|
||||
input: parsedInput,
|
||||
}
|
||||
})
|
||||
// Filter out any tool uses with the UNIQUE_ERROR_TOOL_NAME, which indicates a parsing error
|
||||
.filter((toolUse) => toolUse.name !== UNIQUE_ERROR_TOOL_NAME),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return anthropicMessage
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process tool calls:", error)
|
||||
console.error("Error converting OpenAI message to Anthropic format:", error)
|
||||
}
|
||||
|
||||
return anthropicMessage
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
import { ResponseInput, ResponseInputMessageContentList, ResponseReasoningItem } from "openai/resources/responses/responses"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
/**
|
||||
* Converts an array of ClineStorageMessage objects (extension of Anthropic format) to a ResponseInput array to use with OpenAI's Responses API.
|
||||
*
|
||||
* ## Key Differences from Chat Completions API
|
||||
*
|
||||
* The Responses API has stricter requirements than the Chat Completions API:
|
||||
*
|
||||
* ### Chat Completions API:
|
||||
* - Messages are simple role/content pairs
|
||||
* - System prompts are separate messages with role="system"
|
||||
* - No explicit reasoning item structure
|
||||
* - More forgiving about message ordering
|
||||
*
|
||||
* ### Responses API:
|
||||
* - Uses an "input" array of heterogeneous items (messages, reasoning, function_calls, etc.)
|
||||
* - System prompts go in an "instructions" field, not as messages
|
||||
* - Reasoning items MUST be immediately followed by a message or function_call
|
||||
* - Strict ordering requirements match training data distribution
|
||||
*
|
||||
* ## The Reasoning Item Constraint
|
||||
*
|
||||
* **THE CRITICAL ERROR:** "Item 'rs_...' of type 'reasoning' was provided without its required following item"
|
||||
*
|
||||
* This error occurs when reasoning items are orphaned or separated from their corresponding output.
|
||||
*
|
||||
* ### What Causes This Error:
|
||||
* ```
|
||||
* ❌ WRONG - Reasoning orphaned between turns:
|
||||
* [
|
||||
* { role: "user", content: [...] },
|
||||
* { type: "reasoning", id: "rs_abc", summary: [...] }, // ← ORPHANED!
|
||||
* { type: "message", role: "assistant", content: [...] },
|
||||
* { role: "user", content: [...] }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* ### The Fix - Keep Complete Assistant Turns Together:
|
||||
* ```
|
||||
* ✅ CORRECT - Reasoning paired with its message:
|
||||
* [
|
||||
* { role: "user", content: [...] },
|
||||
* { type: "reasoning", id: "rs_abc", summary: [...] },
|
||||
* { type: "message", role: "assistant", content: [...] }, // ← Immediately follows reasoning
|
||||
* { role: "user", content: [...] }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* **Per OpenAI Engineering Guidance:**
|
||||
* - ❌ WRONG: `content += filter(lambda x: x.type == "reasoning", resp.output)`
|
||||
* - ✅ CORRECT: `content += resp.output`
|
||||
*
|
||||
* Never extract only reasoning items - always include the complete output sequence
|
||||
* (reasoning + message/function_call) as provided by the API.
|
||||
*
|
||||
* ## Implementation Strategy
|
||||
*
|
||||
* 1. **Separate processing for assistant vs user messages** - Assistant turns need special
|
||||
* handling to maintain reasoning-message pairing
|
||||
* 2. **Collect all assistant items together** - Gather reasoning, messages, and function_calls
|
||||
* for the entire assistant turn before validating
|
||||
* 3. **Validate pairing within each turn** - Ensure each reasoning item is immediately followed
|
||||
* by a message or function_call, inserting placeholders if needed
|
||||
* 4. **Flush complete turns atomically** - Add all items from an assistant turn together to
|
||||
* maintain proper sequencing
|
||||
*
|
||||
* @link https://community.openai.com/t/openai-api-error-function-call-was-provided-without-its-required-reasoning-item-the-real-issue/1355347
|
||||
*
|
||||
* @param messages - Array of ClineStorageMessage objects to be converted
|
||||
* @returns ResponseInput array containing the transformed messages with proper reasoning pairing
|
||||
*/
|
||||
export function convertToOpenAIResponsesInput(messages: ClineStorageMessage[]): ResponseInput {
|
||||
const allItems: any[] = []
|
||||
const toolUseIdToCallId = new Map<string, string>()
|
||||
|
||||
for (const m of messages) {
|
||||
if (typeof m.content === "string") {
|
||||
allItems.push({ role: m.role, content: [{ type: "input_text", text: m.content }] })
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "assistant") {
|
||||
// For assistant messages, we must ensure reasoning items are IMMEDIATELY followed
|
||||
// by their corresponding message or function_call. Process the entire assistant
|
||||
// turn and ensure proper pairing.
|
||||
const assistantItems: any[] = []
|
||||
|
||||
for (const part of m.content) {
|
||||
switch (part.type) {
|
||||
case "thinking":
|
||||
// Include reasoning item if it has a call_id, even if thinking is empty
|
||||
// This is required because the API expects reasoning items to be paired with
|
||||
// their corresponding function_calls, and will error if a function_call
|
||||
// references a reasoning item that wasn't sent
|
||||
if (part.call_id && part.call_id.length > 0) {
|
||||
assistantItems.push({
|
||||
id: part.call_id,
|
||||
type: "reasoning",
|
||||
summary: part.thinking
|
||||
? [
|
||||
{
|
||||
type: "summary_text",
|
||||
text: part.thinking,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
} as ResponseReasoningItem)
|
||||
}
|
||||
break
|
||||
case "redacted_thinking":
|
||||
// Include reasoning item with encrypted content if it has a call_id
|
||||
// Even if data is missing, we need to maintain the reasoning-function_call pairing
|
||||
if (part.call_id && part.call_id.length > 0) {
|
||||
const reasoningItem: any = {
|
||||
id: part.call_id,
|
||||
type: "reasoning",
|
||||
summary: [],
|
||||
}
|
||||
// Only include encrypted_content if data exists
|
||||
if (part.data) {
|
||||
reasoningItem.encrypted_content = part.data
|
||||
}
|
||||
assistantItems.push(reasoningItem as ResponseReasoningItem)
|
||||
}
|
||||
break
|
||||
case "text":
|
||||
assistantItems.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: part.text }],
|
||||
})
|
||||
break
|
||||
case "image":
|
||||
assistantItems.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: `[image:${part.source.media_type}]` }],
|
||||
})
|
||||
break
|
||||
case "tool_use": {
|
||||
const call_id = part.call_id || part.id
|
||||
if (part.call_id) {
|
||||
toolUseIdToCallId.set(part.id, part.call_id)
|
||||
}
|
||||
assistantItems.push({
|
||||
type: "function_call",
|
||||
call_id,
|
||||
id: part.id,
|
||||
name: part.name,
|
||||
arguments: JSON.stringify(part.input ?? {}),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure every reasoning item is followed by a message or function_call
|
||||
for (let i = 0; i < assistantItems.length; i++) {
|
||||
const item = assistantItems[i]
|
||||
if (item.type === "reasoning") {
|
||||
const nextItem = assistantItems[i + 1]
|
||||
if (!nextItem || (nextItem.type !== "message" && nextItem.type !== "function_call")) {
|
||||
// Insert a placeholder message immediately after this reasoning item
|
||||
assistantItems.splice(i + 1, 0, {
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "" }],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allItems.push(...assistantItems)
|
||||
} else {
|
||||
// User messages - collect all content
|
||||
const messageContent: ResponseInputMessageContentList = []
|
||||
|
||||
for (const part of m.content) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
messageContent.push({ type: "input_text", text: part.text })
|
||||
break
|
||||
case "image":
|
||||
messageContent.push({
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: `data:${part.source.media_type};base64,${part.source.data}`,
|
||||
})
|
||||
break
|
||||
case "tool_result": {
|
||||
// Flush any pending message content before adding tool result
|
||||
if (messageContent.length > 0) {
|
||||
allItems.push({ role: m.role, content: [...messageContent] })
|
||||
messageContent.length = 0
|
||||
}
|
||||
const call_id = part.call_id || toolUseIdToCallId.get(part.tool_use_id) || part.tool_use_id
|
||||
allItems.push({
|
||||
type: "function_call_output",
|
||||
call_id,
|
||||
output: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any remaining user message content
|
||||
if (messageContent.length > 0) {
|
||||
allItems.push({ role: m.role, content: [...messageContent] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allItems
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export async function createOpenRouterStream(
|
||||
thinkingBudgetTokens?: number,
|
||||
openRouterProviderSorting?: string,
|
||||
tools?: Array<ChatCompletionTool>,
|
||||
geminiThinkingLevel?: string,
|
||||
) {
|
||||
// Convert Anthropic messages to OpenAI format
|
||||
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
@@ -138,6 +139,10 @@ export async function createOpenRouterStream(
|
||||
topP = 0.95
|
||||
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
}
|
||||
if (model.id.startsWith("google/gemini-3.0") || model.id === "google/gemini-3.0") {
|
||||
// Recommended value from google
|
||||
temperature = 1.0
|
||||
}
|
||||
|
||||
let reasoning: { max_tokens: number } | undefined
|
||||
switch (model.id) {
|
||||
@@ -161,7 +166,12 @@ export async function createOpenRouterStream(
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (thinkingBudgetTokens && model.info?.thinkingConfig && thinkingBudgetTokens > 0) {
|
||||
if (
|
||||
thinkingBudgetTokens &&
|
||||
model.info?.thinkingConfig &&
|
||||
thinkingBudgetTokens > 0 &&
|
||||
!(model.id.includes("gemini") && geminiThinkingLevel)
|
||||
) {
|
||||
temperature = undefined // extended thinking does not support non-1 temperature
|
||||
reasoning = { max_tokens: thinkingBudgetTokens }
|
||||
break
|
||||
@@ -189,6 +199,9 @@ export async function createOpenRouterStream(
|
||||
...(providerPreferences ? { provider: providerPreferences } : {}),
|
||||
...(isClaudeSonnet1m ? { provider: { order: ["anthropic", "google-vertex/global"], allow_fallbacks: false } } : {}),
|
||||
...getOpenAIToolParams(tools),
|
||||
...(model.id.includes("gemini") && geminiThinkingLevel
|
||||
? { thinking_config: { thinking_level: geminiThinkingLevel, include_thoughts: true } }
|
||||
: {}),
|
||||
})
|
||||
|
||||
return stream
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk>
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk> & { id?: string }
|
||||
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamThinkingChunk | ApiStreamUsageChunk | ApiStreamToolCallsChunk
|
||||
|
||||
export interface ApiStreamTextChunk {
|
||||
type: "text"
|
||||
/**
|
||||
* Text content generated by the model
|
||||
*/
|
||||
text: string
|
||||
/**
|
||||
* The response ID associated with this chunk
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* The thought signature associated with this chunk used by Gemini
|
||||
*/
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ApiStreamUsageChunk {
|
||||
@@ -14,27 +25,74 @@ export interface ApiStreamUsageChunk {
|
||||
cacheReadTokens?: number
|
||||
thoughtsTokenCount?: number // openrouter
|
||||
totalCost?: number // openrouter
|
||||
/**
|
||||
* The response ID associated with this response
|
||||
*/
|
||||
id?: string
|
||||
}
|
||||
|
||||
export interface ApiStreamToolCallsChunk {
|
||||
type: "tool_calls"
|
||||
/**
|
||||
* The tool call information
|
||||
*/
|
||||
tool_call: ApiStreamToolCall
|
||||
/**
|
||||
* The response ID associated with this chunk
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* The thought signature associated with this chunk used by Gemini
|
||||
*/
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ApiStreamToolCall {
|
||||
call_id?: string // The call / request ID associated with this tool call
|
||||
/**
|
||||
* The call ID associated with this tool call
|
||||
*/
|
||||
call_id?: string
|
||||
// Information about the tool being called
|
||||
function: {
|
||||
id?: string // The tool call ID
|
||||
/**
|
||||
* The tool call ID
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* Name of the tool
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* The arguments passed to the tool execution
|
||||
*/
|
||||
arguments?: any
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApiStreamThinkingChunk {
|
||||
type: "reasoning"
|
||||
/**
|
||||
* The reasoning text generated by the model.
|
||||
* Redacted reasoning block will have this field set to "[REDACTED]" or an empty string.
|
||||
*/
|
||||
reasoning: string
|
||||
details?: unknown // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
/**
|
||||
* openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
* This is also where we store the summary details for OpenAI.
|
||||
*/
|
||||
details?: unknown
|
||||
/**
|
||||
* It's used when sending the thinking block back to the API.
|
||||
* API expects this in completed form, not as array of deltas.
|
||||
* Also used by Gemini for thought signature associated with this chunk
|
||||
*/
|
||||
signature?: string
|
||||
/**
|
||||
* redacted data
|
||||
*/
|
||||
redacted_data?: string
|
||||
/**
|
||||
* The response ID associated with this chunk
|
||||
*/
|
||||
id?: string
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export function getOpenAIToolParams(tools?: OpenAITool[]) {
|
||||
? {
|
||||
tools,
|
||||
tool_choice: tools ? ("auto" as ChatCompletionToolChoiceOption) : undefined,
|
||||
parallel_tool_calls: tools ? true : undefined,
|
||||
parallel_tool_calls: tools ? false : undefined, // Set to false to force single tool calls
|
||||
}
|
||||
: {
|
||||
tools: undefined,
|
||||
|
||||
@@ -52,16 +52,46 @@ export interface ToolUse {
|
||||
// params is a partial record, allowing only some or none of the possible parameters to be used
|
||||
params: Partial<Record<ToolParamName, string>>
|
||||
partial: boolean
|
||||
// Whether this tool use was initiated by a native tool call
|
||||
/**
|
||||
* Whether this tool use was initiated by a native tool call
|
||||
*/
|
||||
isNativeToolCall?: boolean
|
||||
/**
|
||||
* The call / response ID this tool use is associated with.
|
||||
*/
|
||||
call_id?: string // optional call ID for tracking tool use calls
|
||||
/**
|
||||
* Thought signature associated with this tool use, used by Gemini
|
||||
*/
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ReasoningStreamContent {
|
||||
type: "reasoning"
|
||||
/**
|
||||
* The reasoning text generated by the model.
|
||||
* Redacted reasoning block will have this field set to "[REDACTED]" or an empty string.
|
||||
*/
|
||||
reasoning: string
|
||||
details?: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
/**
|
||||
* openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
*/
|
||||
details?: any
|
||||
/**
|
||||
* It's used when sending the thinking block back to the API.
|
||||
* API expects this in completed form, not as array of deltas.
|
||||
*/
|
||||
signature?: string
|
||||
redacted?: boolean // whether this reasoning block has been redacted
|
||||
data?: string // redacted data
|
||||
/**
|
||||
* whether this reasoning block has been redacted
|
||||
*/
|
||||
redacted?: boolean
|
||||
/**
|
||||
* redacted data
|
||||
*/
|
||||
data?: string
|
||||
/**
|
||||
* Indicates whether this is a partial reasoning block
|
||||
*/
|
||||
partial: boolean
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ export function checkContextWindowExceededError(error: unknown): boolean {
|
||||
checkIsOpenAIContextWindowError(error) ||
|
||||
checkIsOpenRouterContextWindowError(error) ||
|
||||
checkIsAnthropicContextWindowError(error) ||
|
||||
checkIsCerebrasContextWindowError(error)
|
||||
checkIsCerebrasContextWindowError(error) ||
|
||||
checkIsBedrockContextWindowError(error)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -70,3 +71,45 @@ function checkIsCerebrasContextWindowError(response: any): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsBedrockContextWindowError(error: any): boolean {
|
||||
try {
|
||||
// Bedrock returns ValidationException for context window errors
|
||||
const errorType = error?.name ?? error?.error?.type ?? error?.__type
|
||||
const errorCode = error?.code ?? error?.error?.code ?? error?.$metadata?.httpStatusCode
|
||||
|
||||
// Handle nested error structures (e.g., through Vercel AI SDK)
|
||||
const nestedError = error?.error?.param
|
||||
const nestedErrorCode = nestedError?.statusCode ?? error?.details?.code
|
||||
const nestedMessage = nestedError?.message ?? nestedError?.error
|
||||
|
||||
const message: string = String(error?.message || error?.error?.message || nestedMessage || "")
|
||||
|
||||
// Check for ValidationException with HTTP 400
|
||||
const isValidationException =
|
||||
errorType === "ValidationException" ||
|
||||
errorType === "AI_APICallError" ||
|
||||
String(errorCode) === "400" ||
|
||||
String(nestedErrorCode) === "400" ||
|
||||
error?.code === "stream_initialization_failed"
|
||||
|
||||
if (!isValidationException) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Known Bedrock context window error patterns
|
||||
const BEDROCK_CONTEXT_PATTERNS = [
|
||||
/maximum tokens.*exceeds.*model limit/i,
|
||||
/input length and max_tokens exceed context limit/i,
|
||||
/context length.*exceeds/i,
|
||||
/total number of tokens.*exceeds.*limit/i,
|
||||
/requested.*tokens.*exceeds.*limit/i,
|
||||
/reduce.*length.*messages.*completion/i,
|
||||
/input is too long/i,
|
||||
] as const
|
||||
|
||||
return BEDROCK_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import {
|
||||
combineRuleToggles,
|
||||
getRuleFilesTotalContent,
|
||||
@@ -12,48 +9,10 @@ import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { listFiles } from "@services/glob/list-files"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { fileExistsAtPath, isDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
|
||||
// Types for better code clarity
|
||||
type RuleSource = {
|
||||
filePath: string
|
||||
extension?: string
|
||||
}
|
||||
|
||||
type RuleConfig = {
|
||||
stateKey: "localWindsurfRulesToggles" | "localCursorRulesToggles" | "localAgentsRulesToggles"
|
||||
sources: RuleSource[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory is a sensitive location (home directory or Desktop)
|
||||
* Returns true if the directory is safe to process rules from
|
||||
*/
|
||||
function isSafeDirectory(workingDirectory: string): boolean {
|
||||
const normalizedPath = path.resolve(workingDirectory)
|
||||
const homeDir = os.homedir()
|
||||
const desktopDir = path.join(homeDir, "Desktop")
|
||||
|
||||
// Don't process rules from home directory or Desktop
|
||||
if (normalizedPath === homeDir || normalizedPath === desktopDir) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to synchronize a single rule source
|
||||
*/
|
||||
async function syncRuleSource(
|
||||
workingDirectory: string,
|
||||
source: RuleSource,
|
||||
currentToggles: ClineRulesToggles,
|
||||
): Promise<ClineRulesToggles> {
|
||||
const fullPath = path.resolve(workingDirectory, source.filePath)
|
||||
return await synchronizeRuleToggles(fullPath, currentToggles, source.extension)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the toggles for windsurf, cursor, and agents rules
|
||||
*/
|
||||
@@ -65,84 +24,36 @@ export async function refreshExternalRulesToggles(
|
||||
cursorLocalToggles: ClineRulesToggles
|
||||
agentsLocalToggles: ClineRulesToggles
|
||||
}> {
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(workingDirectory)) {
|
||||
// Return empty toggles for unsafe directories
|
||||
return {
|
||||
windsurfLocalToggles: {},
|
||||
cursorLocalToggles: {},
|
||||
agentsLocalToggles: {},
|
||||
}
|
||||
}
|
||||
// local windsurf toggles
|
||||
const localWindsurfRulesToggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
|
||||
const localWindsurfRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.windsurfRules)
|
||||
const updatedLocalWindsurfToggles = await synchronizeRuleToggles(localWindsurfRulesFilePath, localWindsurfRulesToggles)
|
||||
controller.stateManager.setWorkspaceState("localWindsurfRulesToggles", updatedLocalWindsurfToggles)
|
||||
|
||||
const configs: Record<string, RuleConfig> = {
|
||||
windsurf: {
|
||||
stateKey: "localWindsurfRulesToggles",
|
||||
sources: [{ filePath: GlobalFileNames.windsurfRules }],
|
||||
},
|
||||
cursor: {
|
||||
stateKey: "localCursorRulesToggles",
|
||||
sources: [
|
||||
{ filePath: GlobalFileNames.cursorRulesDir, extension: ".mdc" },
|
||||
{ filePath: GlobalFileNames.cursorRulesFile },
|
||||
],
|
||||
},
|
||||
agents: {
|
||||
stateKey: "localAgentsRulesToggles",
|
||||
sources: [{ filePath: GlobalFileNames.agentsRulesFile }],
|
||||
},
|
||||
}
|
||||
// local cursor toggles
|
||||
const localCursorRulesToggles = controller.stateManager.getWorkspaceStateKey("localCursorRulesToggles")
|
||||
|
||||
// Process windsurf
|
||||
const windsurfConfig = configs.windsurf
|
||||
const windsurfToggles = controller.stateManager.getWorkspaceStateKey(windsurfConfig.stateKey)
|
||||
const windsurfLocalToggles = await syncRuleSource(workingDirectory, windsurfConfig.sources[0], windsurfToggles)
|
||||
controller.stateManager.setWorkspaceState(windsurfConfig.stateKey, windsurfLocalToggles)
|
||||
// cursor has two valid locations for rules files, so we need to check both and combine
|
||||
// synchronizeRuleToggles will drop whichever rules files are not in each given path, but combining the results will result in no data loss
|
||||
let localCursorRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.cursorRulesDir)
|
||||
const updatedLocalCursorToggles1 = await synchronizeRuleToggles(localCursorRulesFilePath, localCursorRulesToggles, ".mdc")
|
||||
|
||||
// Process cursor (combine results from both sources)
|
||||
const cursorConfig = configs.cursor
|
||||
const cursorToggles = controller.stateManager.getWorkspaceStateKey(cursorConfig.stateKey)
|
||||
const [cursorToggles1, cursorToggles2] = await Promise.all([
|
||||
syncRuleSource(workingDirectory, cursorConfig.sources[0], cursorToggles),
|
||||
syncRuleSource(workingDirectory, cursorConfig.sources[1], cursorToggles),
|
||||
])
|
||||
const cursorLocalToggles = combineRuleToggles(cursorToggles1, cursorToggles2)
|
||||
controller.stateManager.setWorkspaceState(cursorConfig.stateKey, cursorLocalToggles)
|
||||
localCursorRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.cursorRulesFile)
|
||||
const updatedLocalCursorToggles2 = await synchronizeRuleToggles(localCursorRulesFilePath, localCursorRulesToggles)
|
||||
|
||||
// Process agents
|
||||
const agentsConfig = configs.agents
|
||||
const agentsToggles = controller.stateManager.getWorkspaceStateKey(agentsConfig.stateKey)
|
||||
const agentsLocalToggles = await syncRuleSource(workingDirectory, agentsConfig.sources[0], agentsToggles)
|
||||
controller.stateManager.setWorkspaceState(agentsConfig.stateKey, agentsLocalToggles)
|
||||
const updatedLocalCursorToggles = combineRuleToggles(updatedLocalCursorToggles1, updatedLocalCursorToggles2)
|
||||
controller.stateManager.setWorkspaceState("localCursorRulesToggles", updatedLocalCursorToggles)
|
||||
|
||||
// local agents toggles
|
||||
const localAgentsRulesToggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
const localAgentsRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.agentsRulesFile)
|
||||
const updatedLocalAgentsToggles = await synchronizeRuleToggles(localAgentsRulesFilePath, localAgentsRulesToggles)
|
||||
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", updatedLocalAgentsToggles)
|
||||
|
||||
return {
|
||||
windsurfLocalToggles,
|
||||
cursorLocalToggles,
|
||||
agentsLocalToggles,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a single rule file
|
||||
*/
|
||||
async function readRuleFile(filePath: string, toggles: ClineRulesToggles): Promise<string | undefined> {
|
||||
// Check if file exists and is enabled
|
||||
if (!(await fileExistsAtPath(filePath))) {
|
||||
return undefined
|
||||
}
|
||||
if (await isDirectory(filePath)) {
|
||||
return undefined
|
||||
}
|
||||
if (filePath in toggles && toggles[filePath] === false) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const content = (await fs.readFile(filePath, "utf8")).trim()
|
||||
return content || undefined
|
||||
} catch (error) {
|
||||
console.error(`Failed to read rule file at ${filePath}:`, error)
|
||||
return undefined
|
||||
windsurfLocalToggles: updatedLocalWindsurfToggles,
|
||||
cursorLocalToggles: updatedLocalCursorToggles,
|
||||
agentsLocalToggles: updatedLocalAgentsToggles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,50 +61,70 @@ async function readRuleFile(filePath: string, toggles: ClineRulesToggles): Promi
|
||||
* Gather formatted windsurf rules
|
||||
*/
|
||||
export const getLocalWindsurfRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(cwd)) {
|
||||
return undefined
|
||||
const windsurfRulesFilePath = path.resolve(cwd, GlobalFileNames.windsurfRules)
|
||||
|
||||
let windsurfRulesFileInstructions: string | undefined
|
||||
|
||||
if (await fileExistsAtPath(windsurfRulesFilePath)) {
|
||||
if (!(await isDirectory(windsurfRulesFilePath))) {
|
||||
try {
|
||||
if (windsurfRulesFilePath in toggles && toggles[windsurfRulesFilePath] !== false) {
|
||||
const ruleFileContent = (await fs.readFile(windsurfRulesFilePath, "utf8")).trim()
|
||||
if (ruleFileContent) {
|
||||
windsurfRulesFileInstructions = formatResponse.windsurfRulesLocalFileInstructions(cwd, ruleFileContent)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .windsurfrules file at ${windsurfRulesFilePath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const filePath = path.resolve(cwd, GlobalFileNames.windsurfRules)
|
||||
const content = await readRuleFile(filePath, toggles)
|
||||
|
||||
return content ? formatResponse.windsurfRulesLocalFileInstructions(cwd, content) : undefined
|
||||
return windsurfRulesFileInstructions
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather formatted cursor rules, which can come from two sources
|
||||
*/
|
||||
export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(cwd)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const results: (string | undefined)[] = []
|
||||
|
||||
// Check .cursorrules file
|
||||
// we first check for the .cursorrules file
|
||||
const cursorRulesFilePath = path.resolve(cwd, GlobalFileNames.cursorRulesFile)
|
||||
const fileContent = await readRuleFile(cursorRulesFilePath, toggles)
|
||||
if (fileContent) {
|
||||
results.push(formatResponse.cursorRulesLocalFileInstructions(cwd, fileContent))
|
||||
}
|
||||
let cursorRulesFileInstructions: string | undefined
|
||||
|
||||
// Check .cursor/rules directory
|
||||
const cursorRulesDirPath = path.resolve(cwd, GlobalFileNames.cursorRulesDir)
|
||||
if ((await fileExistsAtPath(cursorRulesDirPath)) && (await isDirectory(cursorRulesDirPath))) {
|
||||
try {
|
||||
const rulesFilePaths = await readDirectoryRecursive(cursorRulesDirPath, ".mdc")
|
||||
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, cwd, toggles)
|
||||
if (rulesFilesTotalContent) {
|
||||
results.push(formatResponse.cursorRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent))
|
||||
if (await fileExistsAtPath(cursorRulesFilePath)) {
|
||||
if (!(await isDirectory(cursorRulesFilePath))) {
|
||||
try {
|
||||
if (cursorRulesFilePath in toggles && toggles[cursorRulesFilePath] !== false) {
|
||||
const ruleFileContent = (await fs.readFile(cursorRulesFilePath, "utf8")).trim()
|
||||
if (ruleFileContent) {
|
||||
cursorRulesFileInstructions = formatResponse.cursorRulesLocalFileInstructions(cwd, ruleFileContent)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .cursorrules file at ${cursorRulesFilePath}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to read .cursor/rules directory at ${cursorRulesDirPath}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
// we then check for the .cursor/rules dir
|
||||
const cursorRulesDirPath = path.resolve(cwd, GlobalFileNames.cursorRulesDir)
|
||||
let cursorRulesDirInstructions: string | undefined
|
||||
|
||||
if (await fileExistsAtPath(cursorRulesDirPath)) {
|
||||
if (await isDirectory(cursorRulesDirPath)) {
|
||||
try {
|
||||
const rulesFilePaths = await readDirectoryRecursive(cursorRulesDirPath, ".mdc")
|
||||
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, cwd, toggles)
|
||||
if (rulesFilesTotalContent) {
|
||||
cursorRulesDirInstructions = formatResponse.cursorRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent)
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .cursor/rules directory at ${cursorRulesDirPath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [cursorRulesFileInstructions, cursorRulesDirInstructions]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,18 +132,22 @@ export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
* Only searches if a top-level agents.md file exists
|
||||
*/
|
||||
async function findAgentsMdFiles(cwd: string): Promise<string[]> {
|
||||
// First check if top-level agents.md exists
|
||||
const topLevelAgentsPath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
if (!(await fileExistsAtPath(topLevelAgentsPath))) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
// First check if top-level agents.md exists
|
||||
const topLevelAgentsPath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
const topLevelExists = await fileExistsAtPath(topLevelAgentsPath)
|
||||
|
||||
// Only search recursively if top-level agents.md exists
|
||||
if (!topLevelExists) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Search recursively for all agents.md files
|
||||
const [allFiles] = await listFiles(cwd, true, 500)
|
||||
const agentsFileName = GlobalFileNames.agentsRulesFile.toLowerCase()
|
||||
|
||||
return allFiles.filter((filePath) => path.basename(filePath).toLowerCase() === agentsFileName)
|
||||
return allFiles.filter((filePath) => {
|
||||
const basename = path.basename(filePath).toLowerCase()
|
||||
return basename === GlobalFileNames.agentsRulesFile.toLowerCase()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to find agents.md files in ${cwd}:`, error)
|
||||
return []
|
||||
@@ -223,11 +158,6 @@ async function findAgentsMdFiles(cwd: string): Promise<string[]> {
|
||||
* Gather formatted agents rules - searches recursively and combines all agents.md files
|
||||
*/
|
||||
export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(cwd)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const agentsRulesFilePath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
|
||||
// Check if the top-level agents.md file is enabled
|
||||
@@ -237,33 +167,35 @@ export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
|
||||
try {
|
||||
const agentsMdFiles = await findAgentsMdFiles(cwd)
|
||||
|
||||
if (agentsMdFiles.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Read and combine all agents.md files in parallel
|
||||
const contentPromises = agentsMdFiles.map(async (filePath) => {
|
||||
try {
|
||||
const fullPath = path.resolve(cwd, filePath)
|
||||
const content = (await fs.readFile(fullPath, "utf8")).trim()
|
||||
if (!content) {
|
||||
// Read and combine all agents.md files
|
||||
const combinedContent = await Promise.all(
|
||||
agentsMdFiles.map(async (filePath) => {
|
||||
try {
|
||||
const fullPath = path.resolve(cwd, filePath)
|
||||
const content = (await fs.readFile(fullPath, "utf8")).trim()
|
||||
if (content) {
|
||||
const relativePath = path.relative(cwd, fullPath)
|
||||
return `## ${relativePath}\n\n${content}`
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error(`Failed to read agents.md file at ${filePath}:`, error)
|
||||
return null
|
||||
}
|
||||
}),
|
||||
).then((contents) => contents.filter(Boolean).join("\n\n"))
|
||||
|
||||
const relativePath = path.relative(cwd, fullPath)
|
||||
return `## ${relativePath}\n\n${content}`
|
||||
} catch (error) {
|
||||
console.error(`Failed to read agents.md file at ${filePath}:`, error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const contents = await Promise.all(contentPromises)
|
||||
const combinedContent = contents.filter(Boolean).join("\n\n")
|
||||
|
||||
return combinedContent ? formatResponse.agentsRulesLocalFileInstructions(cwd, combinedContent) : undefined
|
||||
if (combinedContent) {
|
||||
return formatResponse.agentsRulesLocalFileInstructions(cwd, combinedContent)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to read agents.md files:", error)
|
||||
return undefined
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import { tryAcquireTaskLockWithRetry } from "@core/task/TaskLockUtils"
|
||||
import { detectWorkspaceRoots } from "@core/workspace/detection"
|
||||
import { setupWorkspaceManager } from "@core/workspace/setup"
|
||||
import { WorkspaceRootManager } from "@core/workspace/WorkspaceRootManager"
|
||||
import type { WorkspaceRootManager } from "@core/workspace/WorkspaceRootManager"
|
||||
import { cleanupLegacyCheckpoints } from "@integrations/checkpoints/CheckpointMigration"
|
||||
import { downloadTask } from "@integrations/misc/export-markdown"
|
||||
import { ClineAccountService } from "@services/account/ClineAccountService"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ChatContent } from "@shared/ChatContent"
|
||||
import { ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { McpMarketplaceCatalog, McpMarketplaceItem } from "@shared/mcp"
|
||||
import { Settings } from "@shared/storage/state-keys"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { UserInfo } from "@shared/UserInfo"
|
||||
import type { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import type { ChatContent } from "@shared/ChatContent"
|
||||
import type { ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import type { HistoryItem } from "@shared/HistoryItem"
|
||||
import type { McpMarketplaceCatalog, McpMarketplaceItem } from "@shared/mcp"
|
||||
import type { Settings } from "@shared/storage/state-keys"
|
||||
import type { Mode } from "@shared/storage/types"
|
||||
import type { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import type { UserInfo } from "@shared/UserInfo"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import axios from "axios"
|
||||
import fs from "fs/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import type { FolderLockWithRetryResult } from "src/core/locks/types"
|
||||
import * as vscode from "vscode"
|
||||
import type * as vscode from "vscode"
|
||||
import { ClineEnv } from "@/config"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
@@ -35,7 +35,7 @@ import { getDistinctId } from "@/services/logging/distinctId"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { AuthState } from "@/shared/proto/index.cline"
|
||||
import type { AuthState } from "@/shared/proto/index.cline"
|
||||
import { getLatestAnnouncementId } from "@/utils/announcements"
|
||||
import { getCwd, getDesktopDir } from "@/utils/path"
|
||||
import { PromptRegistry } from "../prompts/system-prompt"
|
||||
@@ -47,10 +47,11 @@ import {
|
||||
writeMcpMarketplaceCatalogToCache,
|
||||
} from "../storage/disk"
|
||||
import { fetchRemoteConfig } from "../storage/remote-config/fetch"
|
||||
import { PersistenceErrorEvent, StateManager } from "../storage/StateManager"
|
||||
import { type PersistenceErrorEvent, StateManager } from "../storage/StateManager"
|
||||
import { Task } from "../task"
|
||||
import { StreamingResponseHandler } from "./grpc-handler"
|
||||
import type { StreamingResponseHandler } from "./grpc-handler"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { getClineOnboardingModels } from "./models/getClineOnboardingModels"
|
||||
import { appendClineStealthModels } from "./models/refreshOpenRouterModels"
|
||||
import { checkCliInstallation } from "./state/checkCliInstallation"
|
||||
import { sendStateUpdate } from "./state/subscribeToState"
|
||||
@@ -846,6 +847,7 @@ export class Controller {
|
||||
|
||||
async getStateToPostToWebview(): Promise<ExtensionState> {
|
||||
// Get API configuration from cache for immediate access
|
||||
const onboardingModels = getClineOnboardingModels()
|
||||
const apiConfiguration = this.stateManager.getApiConfiguration()
|
||||
const lastShownAnnouncementId = this.stateManager.getGlobalStateKey("lastShownAnnouncementId")
|
||||
const taskHistory = this.stateManager.getGlobalStateKey("taskHistory")
|
||||
@@ -858,7 +860,6 @@ export class Controller {
|
||||
const mode = this.stateManager.getGlobalSettingsKey("mode")
|
||||
const strictPlanModeEnabled = this.stateManager.getGlobalSettingsKey("strictPlanModeEnabled")
|
||||
const yoloModeToggled = this.stateManager.getGlobalSettingsKey("yoloModeToggled")
|
||||
const useAutoCondense = this.stateManager.getGlobalSettingsKey("useAutoCondense")
|
||||
const userInfo = this.stateManager.getGlobalStateKey("userInfo")
|
||||
const mcpMarketplaceEnabled = this.stateManager.getGlobalStateKey("mcpMarketplaceEnabled")
|
||||
const mcpDisplayMode = this.stateManager.getGlobalStateKey("mcpDisplayMode")
|
||||
@@ -933,7 +934,10 @@ export class Controller {
|
||||
mode,
|
||||
strictPlanModeEnabled,
|
||||
yoloModeToggled,
|
||||
useAutoCondense,
|
||||
useAutoCondense: {
|
||||
user: this.stateManager.getGlobalSettingsKey("useAutoCondense"),
|
||||
featureFlag: featureFlagsService.getUseAutoCondenseEnabled(),
|
||||
},
|
||||
userInfo,
|
||||
mcpMarketplaceEnabled,
|
||||
mcpDisplayMode,
|
||||
@@ -958,7 +962,7 @@ export class Controller {
|
||||
defaultTerminalProfile,
|
||||
isNewUser,
|
||||
welcomeViewCompleted,
|
||||
showOnboardingFlow: featureFlagsService.getOnboardingEnabled(),
|
||||
onboardingModels,
|
||||
mcpResponsesCollapsed,
|
||||
terminalOutputLineLimit,
|
||||
maxConsecutiveMistakes,
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { featureFlagsService } from "@/services/feature-flags"
|
||||
import { CLINE_ONBOARDING_MODELS } from "@/shared/cline/onboarding"
|
||||
import { OnboardingModel, OnboardingModelGroup } from "@/shared/proto/cline/state"
|
||||
|
||||
type OnboardingModelOverride = OnboardingModel & { hidden?: boolean }
|
||||
|
||||
let cached: OnboardingModelGroup | null = null
|
||||
|
||||
export function getClineOnboardingModels(): OnboardingModelGroup {
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const remoteOverrides = featureFlagsService.getOnboardingOverrides()
|
||||
const models = new Map<string, OnboardingModel>(CLINE_ONBOARDING_MODELS.map((model) => [model.id, model]))
|
||||
|
||||
// Apply remote overrides if available
|
||||
if (remoteOverrides) {
|
||||
for (const [id, override] of Object.entries(remoteOverrides) as [string, OnboardingModelOverride][]) {
|
||||
if (override.hidden) {
|
||||
models.delete(id)
|
||||
} else {
|
||||
const baseModel = models.get(id)
|
||||
models.set(id, mergeModelWithOverride(baseModel, override))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cached = { models: Array.from(models.values()) }
|
||||
return cached
|
||||
}
|
||||
|
||||
function mergeModelWithOverride(baseModel: OnboardingModel | undefined, override: OnboardingModelOverride): OnboardingModel {
|
||||
const baseInfo = baseModel?.info
|
||||
const overrideInfo = override.info
|
||||
|
||||
// Merge info with proper defaults
|
||||
const mergedInfo = {
|
||||
...baseInfo,
|
||||
...overrideInfo,
|
||||
supportsPromptCache: overrideInfo?.supportsPromptCache ?? baseInfo?.supportsPromptCache ?? false,
|
||||
tiers: overrideInfo?.tiers ?? baseInfo?.tiers ?? [],
|
||||
}
|
||||
|
||||
// Return merged model, using base as foundation if available
|
||||
return baseModel ? { ...baseModel, ...override, info: mergedInfo } : { ...override, info: mergedInfo }
|
||||
}
|
||||
|
||||
export function clearOnboardingModelsCache(): void {
|
||||
cached = null
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import axios from "axios"
|
||||
import cloneDeep from "clone-deep"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import {
|
||||
ANTHROPIC_MAX_THINKING_BUDGET,
|
||||
CLAUDE_SONNET_1M_TIERS,
|
||||
@@ -78,7 +79,7 @@ interface OpenRouterRawModelInfo {
|
||||
export async function refreshOpenRouterModels(controller: Controller): Promise<Record<string, ModelInfo>> {
|
||||
const openRouterModelsFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
|
||||
|
||||
const models: Record<string, ModelInfo> = {}
|
||||
let models: Record<string, ModelInfo> = {}
|
||||
try {
|
||||
const response = await axios.get("https://openrouter.ai/api/v1/models", getAxiosSettings())
|
||||
|
||||
@@ -242,12 +243,17 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
|
||||
// If we failed to fetch models, try to read cached models
|
||||
const cachedModels = await controller.readOpenRouterModels()
|
||||
if (cachedModels) {
|
||||
// Cached models are already in application format (ModelInfo)
|
||||
return appendClineStealthModels(cachedModels as Record<string, ModelInfo>)
|
||||
models = cachedModels
|
||||
}
|
||||
}
|
||||
|
||||
// Append stealth models if any
|
||||
return appendClineStealthModels(models)
|
||||
const finalModels = appendClineStealthModels(models)
|
||||
|
||||
// Store in StateManager's in-memory cache
|
||||
StateManager.get().setModelsCache("openRouter", finalModels)
|
||||
|
||||
return finalModels
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { OpenRouterCompatibleModelInfo, OpenRouterModelInfo } from "@shared/proto/cline/models"
|
||||
import axios from "axios"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { toRequestyServiceUrl } from "@/shared/clients/requesty"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { Controller } from ".."
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,6 +110,8 @@ export async function updateApiConfigurationProto(
|
||||
actModeAihubmixModelInfo: protoApiConfiguration.actModeAihubmixModelInfo
|
||||
? fromProtobufOpenAiCompatibleModelInfo(protoApiConfiguration.actModeAihubmixModelInfo)
|
||||
: undefined,
|
||||
geminiPlanModeThinkingLevel: protoApiConfiguration.geminiPlanModeThinkingLevel,
|
||||
geminiActModeThinkingLevel: protoApiConfiguration.geminiActModeThinkingLevel,
|
||||
}
|
||||
|
||||
// Update the API configuration in storage
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { Empty } from "@shared/proto/cline/common"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
* Flush all pending state changes immediately to disk
|
||||
* Bypasses the debounced persistence and forces immediate writes
|
||||
*/
|
||||
export async function flushPendingState(controller: Controller, request: EmptyRequest): Promise<Empty> {
|
||||
try {
|
||||
await controller.stateManager.flushPendingState()
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
console.error("[flushPendingState] Error flushing pending state:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Error thrown when a PreToolUse hook requests cancellation.
|
||||
* This signals to the tool handler that execution should be aborted.
|
||||
*/
|
||||
export class PreToolUseHookCancellationError extends Error {
|
||||
constructor(message: string = "PreToolUse hook requested cancellation") {
|
||||
super(message)
|
||||
this.name = "PreToolUseHookCancellationError"
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
|
||||
console.log(JSON.stringify({
|
||||
cancel: false,
|
||||
contextModification: "COMPLETED: " + input.taskComplete.taskMetadata.result,
|
||||
errorMessage: ""
|
||||
}));
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
console.error("Hook execution error");
|
||||
process.exit(1);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user