Compare commits

...

17 Commits

Author SHA1 Message Date
0xtoshii 62e91dc0cd updating menu text to mention new tools 2025-11-19 12:09:14 -08:00
CandiedUniverse 5049326f02 fix(hooks): Fix two minor cancel-resume issues (#7502)
* fix(hooks): Fix cancel: true returned by TaskResume

* fix(hooks): Prevent TaskCancel from being triggered twice by TaskStart cancel and by TaskResume cancel scenarios
2025-11-17 14:44:16 -08:00
Ara b02ce46a57 Fix: Vercel provider token usage (#7481) 2025-11-17 14:15:32 -08:00
Saoud Rizwan de974737c8 fix: improve layout and styling in OnboardingView component for small width viewport (#7391) 2025-11-17 13:35:13 -08:00
Bee d072156e9a fix(account): memoize credits history table component (#7439)
Use React.memo to wrap CreditsHistoryTable, reducing unnecessary re-renders
when props are unchanged and improving performance of the account view that makes it looks like it glinches.
2025-11-17 11:36:19 -08:00
celestial-vault 4939309a09 fix openrouter defaulting modelId when modelInfo is not present (#7482) 2025-11-15 13:50:29 -08:00
CandiedUniverse 1a07ca7906 fix(hooks): Honor '"cancel": true' in hook JSON output (#7479) 2025-11-14 20:37:16 -08:00
Bee c1eefbad3f refactor(api): unify provider message type with ClineStorageMessage (#7478)
* refactor: replace Anthropic MessageParam with ClineStorageMessage type

Replace Anthropic SDK's MessageParam type with the new ClineStorageMessage type across API providers and tests in the effort of storing api messages in a type safe environment that we can expand from and avoid adding undocumented properties to Anthropc Message type that are not visible to the downstream services.

This change:

- Removes dependency on @anthropic-ai/sdk types in multiple providers
- Introduces ClineStorageMessage from shared messages module
- Updates method signatures in Dify, OpenAI, LiteLLM, and ClaudeCode handlers
- Updates corresponding test files to use the new type

This decouples the codebase from Anthropic-specific types and standardizes message handling using an internal storage format across all providers that  improves type-safety, preparing for the properties added by the Response API use.

As ClineStorageMessage is an extension of the Anthropic Message type, everything should work the same with no breaking changes. Green CI is expected.

* clean up
2025-11-14 20:24:56 -08:00
canvrno 1bfdce9b84 Remove new_task from system prompts (#7350)
* Removed new_task from system prompts, updated slash command prompt, added helper function for native tool calling checks

* Update src/core/prompts/system-prompt/registry/PromptBuilder.ts

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>

* Update src/core/task/index.ts

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>

* Updates with requested changes for PR #7350

* Updated package-lock.json

---------

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
2025-11-14 17:57:47 -08:00
canvrno b002cdacdb Maint: package updates (#7477)
* maint: package updates

* Updated download-ripgrep script for compatability with new tar dependency
2025-11-14 16:09:03 -08:00
CandiedUniverse cf4005b25e fix(hooks): Reorder the UI elements so that PreToolUse appears above tool (#7449)
* fix(hooks): Reorder the UI elements so that PreToolUse appears above tool

* fix(hooks): Prevent PreToolUse hook from migrating down the screen

* fix(hooks): PreToolUse reordering should apply to 'tool', 'command', 'use_mcp_server', and 'browser_action_launch'  message types
2025-11-14 15:40:23 -08:00
Bee 1494d145d5 feat: support feature flag payload & remote dynamic onboarding model list (#7454)
* feat: support feature flag payload & dynamic onboarding model list

- Updated proto to use OnboardingModelGroup instead of bool flag for flexible onboarding
- Added getClineOnboardingModels function with caching and remote overrides for dynamic model fetching
- Modified controller to fetch and pass onboarding models to webview
- Updated UI to use dynamic models for selection, enabling flexible onboarding
- Enhanced feature flag service to support non-boolean payloads for better configurability

* clearOnboardingModelsCache
2025-11-14 14:49:05 -08:00
canvrno 1ab4b3cc24 fix:SAP provider type error - See PR #6547 (#7475) 2025-11-14 14:15:13 -08:00
canvrno 535b653228 Added stronger prompting around the use of act_mode_respond (#7448) 2025-11-14 12:12:34 -08:00
Igor Tceglevskii 1335fa5452 Retire firebase (#7362) 2025-11-14 09:29:47 -08:00
yuvalman b2a4395f71 feat: upgrade sap ai-sdk-js packages major version (#6547)
* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version
2025-11-14 09:11:03 -08:00
Toshii ae34a3a8c5 adding state variable for clineWebToolsEnabled (noop) (#7455)
* adding state variable for clineWebToolsEnabled

* removing console log
2025-11-14 08:20:01 -08:00
118 changed files with 1067 additions and 3203 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix for SAP provider - OrchestrationClient matches the OrchestrationModuleConfig type and no longer uses the invalid promptTemplating property
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Removed new_task from system prompts, updated slash command prompt, added helper function for native tool calling checks
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
upgrade sap ai-sdk-js packages major version
+6 -34
View File
@@ -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",
+4
View File
@@ -14,5 +14,9 @@
"description": "",
"dependencies": {
"mintlify": "^4.2.23"
},
"overrides": {
"tar-fs": "^3.1.1",
"js-yaml": "^4.1.1"
}
}
-1422
View File
File diff suppressed because it is too large Load Diff
+46 -45
View File
@@ -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"
}
}
+187 -860
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -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",
@@ -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": [
+15 -1
View File
@@ -362,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 {
@@ -390,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;
}
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -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()
-29
View File
@@ -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,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 -1
View File
@@ -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 { 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"
@@ -43,7 +44,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?: ClineTool[]): ApiStream {
const client = this.ensureClient()
const model = this.getModel()
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -1,7 +1,7 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
import { calculateApiCostOpenAI } from "@utils/cost"
import OpenAI from "openai"
import { ClineStorageMessage } from "@/shared/messages/content"
import { fetch } from "@/shared/net"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
@@ -98,7 +98,7 @@ export class BasetenHandler 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 maxTokens = this.getOptimalMaxTokens(model)
+10 -10
View File
@@ -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)
@@ -779,7 +779,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 +835,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 +968,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 +1008,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 +1143,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 {
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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"
@@ -9,6 +8,7 @@ import { ClineAccountService } from "@/services/account/ClineAccountService"
import { AuthService } from "@/services/auth/AuthService"
import { buildClineExtraHeaders } from "@/services/EnvUtils"
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
import { ClineStorageMessage } from "@/shared/messages/content"
import { fetch, getAxiosSettings } from "@/shared/net"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
@@ -96,7 +96,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()
+2 -2
View File
@@ -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()
+3 -3
View File
@@ -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()
+2 -2
View File
@@ -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[] = [
+2 -2
View File
@@ -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 ?? ""
+2 -2
View File
@@ -1,4 +1,3 @@
import type { Anthropic } from "@anthropic-ai/sdk"
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
import {
ApiError,
@@ -11,6 +10,7 @@ import {
} 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"
@@ -110,7 +110,7 @@ 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)
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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 ?? ""
+2 -2
View File
@@ -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[] = [
+2 -2
View File
@@ -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()
+2 -1
View File
@@ -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 = {
+2 -2
View File
@@ -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 -1
View File
@@ -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()
+2 -2
View File
@@ -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({
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -1,4 +1,3 @@
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"
@@ -11,6 +10,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"
@@ -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 = {
+2 -2
View File
@@ -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)]
+2 -6
View File
@@ -1,8 +1,8 @@
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 { ClineStorageMessage } from "@/shared/messages/content"
import { fetch } from "@/shared/net"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
@@ -59,11 +59,7 @@ export class OpenAiNativeHandler 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 model = this.getModel()
const toolCallProcessor = new ToolCallProcessor()
+2 -6
View File
@@ -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")
+5 -7
View File
@@ -1,10 +1,10 @@
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
import { Anthropic } from "@anthropic-ai/sdk"
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"
@@ -54,7 +54,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
@@ -214,11 +214,9 @@ 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 }
return {
id: this.options.openRouterModelId || openRouterDefaultModelId,
info: this.options.openRouterModelInfo || openRouterDefaultModelInfo,
}
return { id: openRouterDefaultModelId, info: openRouterDefaultModelInfo }
}
}
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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")
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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()
+26 -23
View File
@@ -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[]
}
}
+2 -2
View File
@@ -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")
+8 -14
View File
@@ -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
@@ -102,22 +102,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
}
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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[] = [
+18 -10
View File
@@ -1,5 +1,5 @@
import Anthropic from "@anthropic-ai/sdk"
import { ClineStorageMessage } from "@/shared/messages/content"
import { ClineContent, ClineStorageMessage } from "@/shared/messages/content"
/**
* Sanitize Anthropic messages by removing reasoning details and adding ephemeral cache control
@@ -63,14 +63,22 @@ function removeUnknownParams(param: ClineStorageMessage): Anthropic.Messages.Mes
// Construct new content array with known Anthropic content blocks only.
return {
role: param.role === "user" ? "user" : "assistant",
content: Array.isArray(param.content)
? param.content.map((item) => {
return {
...item,
// Ensure reasoning_details is removed
reasoning_details: undefined,
}
})
: param.content, // String content remains unchanged
content: Array.isArray(param.content) ? param.content.map(sanitizeAnthropicContentBlock) : param.content, // String content remains unchanged
}
}
/**
* Clean a content block by removing Cline-specific fields and returning only provider-compatible fields
*/
function sanitizeAnthropicContentBlock(block: ClineContent): Anthropic.ContentBlock {
// Fast path: if no reasoning_details property exists, return as-is
// Including reasoning_details in non-openrouter/cline providers may cause API errors
if (!("reasoning_details" in block)) {
return block as Anthropic.ContentBlock
}
// Remove reasoning_details from text blocks
// biome-ignore lint/correctness/noUnusedVariables: intentional destructuring to remove property
const { reasoning_details, ...cleanBlock } = block
return cleanBlock as Anthropic.ContentBlock
}
+18 -16
View File
@@ -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")
@@ -958,7 +960,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
}
@@ -1,6 +1,7 @@
import type { BooleanRequest } from "@shared/proto/cline/common"
import { Empty } from "@shared/proto/cline/common"
import type { Controller } from "../index"
import { clearOnboardingModelsCache } from "../models/getClineOnboardingModels"
/**
* Sets the welcomeViewCompleted flag to the specified boolean value
@@ -20,5 +21,7 @@ export async function setWelcomeViewCompleted(controller: Controller, request: B
} catch (error) {
console.error("Failed to set welcome view completed:", error)
throw error
} finally {
clearOnboardingModelsCache()
}
}
+59
View File
@@ -79,6 +79,12 @@ export async function executeHook<Name extends keyof Hooks>(options: HookExecuti
}
hookMessageTs = await say("hook", JSON.stringify(hookMetadata))
// Reorder messages immediately so hook UI appears above tool UI
// This must happen right after creating the hook message, before the hook runs
if (hookName === "PreToolUse") {
await reorderHookAndToolMessages(messageStateHandler)
}
// Track active hook execution for cancellation (only if cancellable and message was created)
if (isCancellable && hookMessageTs !== undefined && setActiveHookExecution) {
await setActiveHookExecution({
@@ -224,3 +230,56 @@ async function updateHookMessage(
})
}
}
/**
* Reorders hook and tool messages so hook UI appears before tool UI.
* This is called immediately after a hook message is created.
*
* The algorithm:
* 1. Find the most recent tool message (ask or say with type "tool", "command", "use_mcp_server", or "browser_action_launch")
* 2. Find any hook messages that came after it
* 3. Delete the tool message
* 4. Re-add the tool message at the end (after hook messages)
*/
async function reorderHookAndToolMessages(messageStateHandler: MessageStateHandler): Promise<void> {
const clineMessages = messageStateHandler.getClineMessages()
// Define all message types that represent tool executions with PreToolUse hooks
const toolMessageTypes = ["tool", "command", "use_mcp_server", "browser_action_launch"]
// Find the most recent tool message
let lastToolMessageIndex = -1
for (let i = clineMessages.length - 1; i >= 0; i--) {
const msgType = clineMessages[i].ask || clineMessages[i].say
if (msgType && toolMessageTypes.includes(msgType)) {
lastToolMessageIndex = i
break
}
}
if (lastToolMessageIndex === -1) {
return // No tool message found, nothing to reorder
}
// Check if there are any hook messages after the tool message
let hasHookMessagesAfterTool = false
for (let i = lastToolMessageIndex + 1; i < clineMessages.length; i++) {
if (clineMessages[i].say === "hook" || clineMessages[i].say === "hook_output") {
hasHookMessagesAfterTool = true
break
}
}
if (!hasHookMessagesAfterTool) {
return // No reordering needed
}
// Store the tool message (deep copy to preserve all properties)
const toolMessage = { ...clineMessages[lastToolMessageIndex] }
// Delete the tool message at its current position
await messageStateHandler.deleteClineMessage(lastToolMessageIndex)
// Re-add the tool message at the end (after hook messages)
await messageStateHandler.addToClineMessages(toolMessage)
}
+36 -8
View File
@@ -1,8 +1,40 @@
import type { ApiProviderInfo } from "@/core/api"
import { getDeepPlanningPrompt } from "./commands/deep-planning"
export const newTaskToolResponse = () =>
`<explicit_instructions type="new_task">
export const newTaskToolResponse = (enableNativeToolCalls?: boolean) => {
const xmlExample = enableNativeToolCalls
? ""
: `
Example:
<new_task>
<context>1. Current Work:
[Detailed description]
2. Key Technical Concepts:
- [Concept 1]
- [Concept 2]
- [...]
3. Relevant Files and Code:
- [File Name 1]
- [Summary of why this file is important]
- [Summary of the changes made to this file, if any]
- [Important Code Snippet]
- [File Name 2]
- [Important Code Snippet]
- [...]
4. Problem Solving:
[Detailed description]
5. Pending Tasks and Next Steps:
- [Task 1 details & next steps]
- [Task 2 details & next steps]
- [...]</context>
</new_task>
`
return `<explicit_instructions type="new_task">
The user has explicitly asked you to help them create a new task with preloaded context, which you will generate. The user may have provided instructions or additional information for you to consider when summarizing existing work and creating the context for the new task.
Irrespective of whether additional information or instructions are given, you are ONLY allowed to respond to this message by calling the new_task tool.
@@ -19,15 +51,11 @@ Parameters:
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
${xmlExample}
Below is the the user's input when they indicated that they wanted to create a new task.
</explicit_instructions>\n
`
}
export const condenseToolResponse = (focusChainSettings?: { enabled: boolean }) =>
`<explicit_instructions type="condense">
@@ -250,21 +250,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -216,21 +216,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -224,21 +224,6 @@ Usage:
<command>Your command here (optional)</command>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -250,21 +250,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -232,21 +232,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -198,21 +198,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -208,21 +208,6 @@ Usage:
<command>Your command here (optional)</command>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -232,21 +232,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -250,21 +250,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -216,21 +216,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -224,21 +224,6 @@ Usage:
<command>Your command here (optional)</command>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -250,21 +250,6 @@ Usage:
<task_progress>Checklist here (required if you used task_progress in previous tool uses)</task_progress>
</attempt_completion>
## new_task
Description: Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.
Parameters:
- context: (required) The context to preload the new task with. If applicable based on the current task, this should include:
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.
Usage:
<new_task>
<context>context to preload new task with</context>
</new_task>
## plan_mode_respond
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should ONLY be used when you have already explored the relevant files and are ready to present a concrete plan. DO NOT use this tool to announce what files you're going to read - just read them first. This tool is only available in PLAN MODE. The environment_details will specify the current mode; if it is not PLAN_MODE then you should not use this tool.
However, if while writing your response you realize you actually need to do more exploration before providing a complete plan, you can add the optional needs_more_exploration parameter to indicate this. This allows you to acknowledge that you should have done more exploration first, and signals that your next message will use exploration tools instead.
@@ -144,6 +144,8 @@ This ensures your work aligns with the existing codebase structure and avoids un
This tool is non-blocking, so using it frequently improves user experience and ensures long tasks are completed successfully.
Additionally, you MUST NOT call act_mode_respond more than once in a row. After using act_mode_respond, your next assistant message MUST either call a different tool or perform additional work without using act_mode_respond again. If you attempt to call act_mode_respond consecutively, the tool call will fail with an explicit error and you must choose a different action instead.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
4. **Code Generation Self-Review Loop**: After generating code, evaluate against an internal quality rubric using your reasoning:
@@ -142,6 +142,8 @@ This ensures your work aligns with the existing codebase structure and avoids un
This tool is non-blocking, so using it frequently improves user experience and ensures long tasks are completed successfully.
Additionally, you MUST NOT call act_mode_respond more than once in a row. After using act_mode_respond, your next assistant message MUST either call a different tool or perform additional work without using act_mode_respond again. If you attempt to call act_mode_respond consecutively, the tool call will fail with an explicit error and you must choose a different action instead.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
4. **Code Generation Self-Review Loop**: After generating code, evaluate against an internal quality rubric using your reasoning:
@@ -110,6 +110,8 @@ This ensures your work aligns with the existing codebase structure and avoids un
This tool is non-blocking, so using it frequently improves user experience and ensures long tasks are completed successfully.
Additionally, you MUST NOT call act_mode_respond more than once in a row. After using act_mode_respond, your next assistant message MUST either call a different tool or perform additional work without using act_mode_respond again. If you attempt to call act_mode_respond consecutively, the tool call will fail with an explicit error and you must choose a different action instead.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
4. **Code Generation Self-Review Loop**: After generating code, evaluate against an internal quality rubric using your reasoning:
@@ -144,6 +144,8 @@ This ensures your work aligns with the existing codebase structure and avoids un
This tool is non-blocking, so using it frequently improves user experience and ensures long tasks are completed successfully.
Additionally, you MUST NOT call act_mode_respond more than once in a row. After using act_mode_respond, your next assistant message MUST either call a different tool or perform additional work without using act_mode_respond again. If you attempt to call act_mode_respond consecutively, the tool call will fail with an explicit error and you must choose a different action instead.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
4. **Code Generation Self-Review Loop**: After generating code, evaluate against an internal quality rubric using your reasoning:
@@ -26,7 +26,7 @@ const NATIVE_GPT_5: ClineToolSpec = {
variant: ModelFamily.NATIVE_GPT_5,
id,
name: "act_mode_respond",
description: `Provide a progress update or preamble to the user during ACT MODE execution. This tool allows you to communicate your thought process and planned actions without interrupting the execution flow. After displaying your message, execution automatically continues, allowing you to proceed with subsequent tool calls immediately. This tool is only available in ACT MODE for OpenAI native models.
description: `Provide a progress update or preamble to the user during ACT MODE execution. This tool allows you to communicate your thought process and planned actions without interrupting the execution flow. After displaying your message, execution automatically continues, allowing you to proceed with subsequent tool calls immediately. This tool is only available in ACT MODE. This tool may not be called immediately after a previous act_mode_respond call.
IMPORTANT: Use this tool frequently to create a better user experience. Since it's non-blocking, there's no performance penalty for frequent use.
@@ -40,7 +40,7 @@ Use this tool when:
Do NOT use this tool when you have completed all required actions and are ready to present the final output; in that case, use the attempt_completion tool instead.
After calling this tool, you must call a different tool in your next message to continue execution.`,
CRITICAL CONSTRAINT: You MUST NOT call this tool more than once in a row. After using act_mode_respond, your next assistant message MUST either call a different tool or perform additional work without using act_mode_respond again. If you attempt to call act_mode_respond consecutively, the tool call will fail with an explicit error.`,
parameters: [
{
name: "response",
+1
View File
@@ -109,6 +109,7 @@ export interface SystemPromptContext {
readonly isTesting?: boolean
readonly runtimePlaceholders?: Readonly<Record<string, unknown>>
readonly yoloModeToggled?: boolean
readonly clineWebToolsEnabled?: boolean
readonly isMultiRootEnabled?: boolean
readonly workspaceRoots?: Array<{ path: string; name: string; vcs?: string }>
readonly isSubagentsEnabledAndCliInstalled?: boolean
@@ -59,7 +59,6 @@ export const config = createVariant(ModelFamily.GENERIC)
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
@@ -47,7 +47,6 @@ export const config = createVariant(ModelFamily.GLM)
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
@@ -57,7 +57,6 @@ export const config = createVariant(ModelFamily.GPT_5)
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
@@ -85,6 +85,8 @@ This ensures your work aligns with the existing codebase structure and avoids un
This tool is non-blocking, so using it frequently improves user experience and ensures long tasks are completed successfully.
Additionally, you MUST NOT call act_mode_respond more than once in a row. After using act_mode_respond, your next assistant message MUST either call a different tool or perform additional work without using act_mode_respond again. If you attempt to call act_mode_respond consecutively, the tool call will fail with an explicit error and you must choose a different action instead.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params)${context.yoloModeToggled !== true ? " and instead, ask the user to provide the missing parameters using the ask_followup_question tool" : ""}. DO NOT ask for more information on optional parameters if it is not provided.
4. **Code Generation Self-Review Loop**: After generating code, evaluate against an internal quality rubric using your reasoning:
@@ -62,7 +62,6 @@ export const config = createVariant(ModelFamily.NATIVE_GPT_5)
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
@@ -55,7 +55,6 @@ export const config = createVariant(ModelFamily.NATIVE_NEXT_GEN)
ClineDefaultTool.WEB_FETCH,
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
@@ -60,7 +60,6 @@ export const config = createVariant(ModelFamily.NEXT_GEN)
ClineDefaultTool.MCP_ACCESS,
ClineDefaultTool.ASK,
ClineDefaultTool.ATTEMPT,
ClineDefaultTool.NEW_TASK,
ClineDefaultTool.PLAN_MODE,
ClineDefaultTool.MCP_DOCS,
ClineDefaultTool.TODO,
+6 -1
View File
@@ -2,6 +2,7 @@ import type { ApiProviderInfo } from "@core/api"
import { ClineRulesToggles } from "@shared/cline-rules"
import fs from "fs/promises"
import { telemetryService } from "@/services/telemetry"
import { isNativeToolCallingConfig } from "@/utils/model-utils"
import {
condenseToolResponse,
deepPlanningToolResponse,
@@ -37,12 +38,16 @@ export async function parseSlashCommands(
globalWorkflowToggles: ClineRulesToggles,
ulid: string,
focusChainSettings?: { enabled: boolean },
enableNativeToolCalls?: boolean,
providerInfo?: ApiProviderInfo,
): Promise<{ processedText: string; needsClinerulesFileCheck: boolean }> {
const SUPPORTED_DEFAULT_COMMANDS = ["newtask", "smol", "compact", "newrule", "reportbug", "deep-planning", "subagent"]
// Determine if the current provider/model/setting actually uses native tool calling
const willUseNativeTools = isNativeToolCallingConfig(providerInfo!, enableNativeToolCalls || false)
const commandReplacements: Record<string, string> = {
newtask: newTaskToolResponse(),
newtask: newTaskToolResponse(willUseNativeTools),
smol: condenseToolResponse(focusChainSettings),
compact: condenseToolResponse(focusChainSettings),
newrule: newRuleToolResponse(),
+3
View File
@@ -172,6 +172,8 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
context.globalState.get<GlobalStateAndSettings["strictPlanModeEnabled"]>("strictPlanModeEnabled")
const yoloModeToggled = context.globalState.get<GlobalStateAndSettings["yoloModeToggled"]>("yoloModeToggled")
const useAutoCondense = context.globalState.get<GlobalStateAndSettings["useAutoCondense"]>("useAutoCondense")
const clineWebToolsEnabled =
context.globalState.get<GlobalStateAndSettings["clineWebToolsEnabled"]>("clineWebToolsEnabled")
const isNewUser = context.globalState.get<GlobalStateAndSettings["isNewUser"]>("isNewUser")
const welcomeViewCompleted =
context.globalState.get<GlobalStateAndSettings["welcomeViewCompleted"]>("welcomeViewCompleted")
@@ -629,6 +631,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
strictPlanModeEnabled: strictPlanModeEnabled ?? true,
yoloModeToggled: yoloModeToggled ?? false,
useAutoCondense: useAutoCondense ?? false,
clineWebToolsEnabled: clineWebToolsEnabled ?? true,
isNewUser: isNewUser ?? true,
welcomeViewCompleted,
lastShownAnnouncementId,
+2 -1
View File
@@ -113,6 +113,7 @@ export class ToolExecutor {
private doesLatestTaskCompletionHaveNewChanges: () => Promise<boolean>,
private updateFCListFromToolResponse: (taskProgress: string | undefined) => Promise<void>,
private switchToActMode: () => Promise<boolean>,
private cancelTask: () => Promise<void>,
// Atomic hook state helpers from Task
private setActiveHookExecution: (hookExecution: NonNullable<typeof taskState.activeHookExecution>) => Promise<void>,
@@ -163,7 +164,7 @@ export class ToolExecutor {
saveCheckpoint: this.saveCheckpoint,
postStateToWebview: async () => {},
reinitExistingTaskFromId: async () => {},
cancelTask: async () => {},
cancelTask: this.cancelTask,
updateTaskHistory: async (_: any) => [],
executeCommandTool: this.executeCommandTool,
doesLatestTaskCompletionHaveNewChanges: this.doesLatestTaskCompletionHaveNewChanges,
+59 -31
View File
@@ -67,7 +67,13 @@ import { CLINE_MCP_TOOL_IDENTIFIER } from "@shared/mcp"
import { convertClineMessageToProto } from "@shared/proto-conversions/cline-message"
import { ClineDefaultTool } from "@shared/tools"
import { ClineAskResponse } from "@shared/WebviewMessage"
import { isClaude4PlusModelFamily, isGPT5ModelFamily, isLocalModel, isNextGenModelFamily, isNextGenModelProvider } from "@utils/model-utils"
import {
isClaude4PlusModelFamily,
isGPT5ModelFamily,
isLocalModel,
isNextGenModelFamily,
isNextGenModelProvider,
} from "@utils/model-utils"
import { arePathsEqual, getDesktopDir } from "@utils/path"
import { filterExistingFiles } from "@utils/tabFiltering"
import cloneDeep from "clone-deep"
@@ -540,6 +546,7 @@ export class Task {
() => this.checkpointManager?.doesLatestTaskCompletionHaveNewChanges() ?? Promise.resolve(false),
this.FocusChainManager?.updateFCListFromToolResponse.bind(this.FocusChainManager) || (async () => {}),
this.switchToActModeCallback.bind(this),
this.cancelTask,
// Atomic hook state helpers for ToolExecutor
this.setActiveHookExecution.bind(this),
this.clearActiveHookExecution.bind(this),
@@ -817,10 +824,33 @@ export class Task {
return await this.controller.toggleActModeForYoloMode()
}
/**
* Unified cancellation handler for hook-requested cancellations.
* Ensures state is always saved before aborting, regardless of whether
* the user clicked cancel or the hook returned {cancel: true}.
*
* @param hookName The name of the hook for logging
* @param wasCancelled Whether user clicked cancel (vs hook returning cancel: true)
*/
private async handleHookCancellation(hookName: string, wasCancelled: boolean): Promise<void> {
// ALWAYS save state, regardless of cancellation source
this.taskState.didFinishAbortingStream = true
// Save conversation state to disk
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
await this.messageStateHandler.overwriteApiConversationHistory(this.messageStateHandler.getApiConversationHistory())
// Update UI
await this.postStateToWebview()
// Log for debugging/telemetry
console.log(`[Task ${this.taskId}] ${hookName} hook cancelled (userInitiated: ${wasCancelled})`)
}
private async runUserPromptSubmitHook(
userContent: ClineContent[],
_context: "initial_task" | "resume" | "feedback",
): Promise<{ cancel?: boolean; contextModification?: string; errorMessage?: string }> {
): Promise<{ cancel?: boolean; wasCancelled?: boolean; contextModification?: string; errorMessage?: string }> {
const hooksEnabled = this.stateManager.getGlobalSettingsKey("hooksEnabled")
if (!hooksEnabled) {
@@ -943,20 +973,11 @@ export class Task {
// Handle cancellation from hook
if (taskStartResult.cancel === true) {
// If hook was cancelled by user, save state for resume
if (taskStartResult.wasCancelled) {
// Set flag to allow Controller.cancelTask() to proceed
this.taskState.didFinishAbortingStream = true
// Save BOTH clineMessages AND apiConversationHistory so Controller.cancelTask() can find the task
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
await this.messageStateHandler.overwriteApiConversationHistory(
this.messageStateHandler.getApiConversationHistory(),
)
await this.postStateToWebview()
}
// Always save state regardless of cancellation source
await this.handleHookCancellation("TaskStart", taskStartResult.wasCancelled)
// abortTask will handle cleanup
this.abortTask()
// Let Controller handle the cancellation (it will call abortTask)
await this.cancelTask()
return
}
@@ -972,6 +993,12 @@ export class Task {
}
}
// Defensive check: Verify task wasn't aborted during hook execution before continuing
// Must be OUTSIDE the hooksEnabled block to prevent UserPromptSubmit from running
if (this.taskState.abort) {
return
}
// Run UserPromptSubmit hook for initial task (after TaskStart for UI ordering)
const userPromptHookResult = await this.runUserPromptSubmitHook(userContent, "initial_task")
@@ -980,9 +1007,10 @@ export class Task {
return
}
// Handle hook cancellation - but DON'T call abortTask()
// Controller.cancelTask() already called it, calling again causes double TaskCancel
// Handle hook cancellation
if (userPromptHookResult.cancel === true) {
await this.handleHookCancellation("UserPromptSubmit", userPromptHookResult.wasCancelled ?? false)
await this.cancelTask()
return
}
@@ -1093,19 +1121,11 @@ export class Task {
// Handle cancellation from hook
if (taskResumeResult.cancel === true) {
// If hook was cancelled by user, save state for resume
if (taskResumeResult.wasCancelled) {
// Set flag to allow Controller.cancelTask() to proceed
this.taskState.didFinishAbortingStream = true
// Save BOTH clineMessages AND apiConversationHistory so Controller.cancelTask() can find the task
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
await this.messageStateHandler.overwriteApiConversationHistory(
this.messageStateHandler.getApiConversationHistory(),
)
await this.postStateToWebview()
}
// UNIFIED: Always save state regardless of cancellation source
await this.handleHookCancellation("TaskResume", taskResumeResult.wasCancelled)
// Return without continuing task - Controller.cancelTask() will handle showing resume button
// Let Controller handle the cancellation (it will call abortTask)
await this.cancelTask()
return
}
@@ -1117,6 +1137,13 @@ export class Task {
})
}
}
// Defensive check: Verify task wasn't aborted during hook execution before continuing
// Must be OUTSIDE the hooksEnabled block to prevent UserPromptSubmit from running
if (this.taskState.abort) {
return
}
let responseText: string | undefined
let responseImages: string[] | undefined
let responseFiles: string[] | undefined
@@ -1245,7 +1272,7 @@ export class Task {
// Handle hook cancellation request
if (userPromptHookResult.cancel === true) {
// The hook already updated its status to "cancelled" internally and saved state
this.abortTask()
await this.cancelTask()
return
}
@@ -2047,6 +2074,7 @@ export class Task {
preferredLanguageInstructions,
browserSettings: this.stateManager.getGlobalSettingsKey("browserSettings"),
yoloModeToggled: this.stateManager.getGlobalSettingsKey("yoloModeToggled"),
clineWebToolsEnabled: this.stateManager.getGlobalSettingsKey("clineWebToolsEnabled"),
isMultiRootEnabled: multiRootEnabled,
workspaceRoots,
isSubagentsEnabledAndCliInstalled,
@@ -3235,7 +3263,7 @@ export class Task {
globalWorkflowToggles,
this.ulid,
this.stateManager.getGlobalSettingsKey("focusChainSettings"),
this.getCurrentProviderInfo(),
this.useNativeToolCalls,
)
if (needsCheck) {
@@ -89,6 +89,12 @@ export class ToolHookUtils {
// Handle cancellation from hook
if (preToolResult.cancel === true) {
// Clear the active hook execution state BEFORE calling cancelTask
// This prevents abortTask from trying to "cancel" an already-completed hook
await config.callbacks.clearActiveHookExecution()
// Abort the entire task (consistent with PostToolUse and other hook cancellations)
await config.callbacks.cancelTask()
throw new PreToolUseHookCancellationError(preToolResult.errorMessage || "PreToolUse hook requested cancellation")
}
+1 -1
View File
@@ -110,7 +110,7 @@ export abstract class WebviewProvider {
<link rel="stylesheet" type="text/css" href="${stylesUrl}">
<link href="${codiconsUrl}" rel="stylesheet" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none';
connect-src https://*.posthog.com https://*.cline.bot https://*.firebaseauth.com https://*.firebaseio.com https://*.googleapis.com https://*.firebase.com;
connect-src https://*.posthog.com https://*.cline.bot;
font-src ${this.getCspSource()} data:;
style-src ${this.getCspSource()} 'unsafe-inline';
img-src ${this.getCspSource()} https: data:;
+1 -1
View File
@@ -401,7 +401,7 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
context.secrets.onDidChange(async (event) => {
if (event.key === "clineAccountId" || event.key === "cline:clineAccountId") {
if (event.key === "cline:clineAccountId") {
// Check if the secret was removed (logout) or added/updated (login)
const secretValue = await context.secrets.get(event.key)
const activeWebview = WebviewProvider.getVisibleInstance()
+8 -36
View File
@@ -9,7 +9,6 @@ import { telemetryService } from "@/services/telemetry"
import { openExternal } from "@/utils/env"
import { featureFlagsService } from "../feature-flags"
import { ClineAuthProvider } from "./providers/ClineAuthProvider"
import { FirebaseAuthProvider } from "./providers/FirebaseAuthProvider"
import { IAuthProvider } from "./providers/IAuthProvider"
import { LogoutReason } from "./types"
@@ -65,7 +64,6 @@ export class AuthService {
protected _authenticated: boolean = false
protected _clineAuthInfo: ClineAuthInfo | null = null
protected _provider: IAuthProvider | null = null
protected _fallbackProvider: IAuthProvider | null = null
protected _activeAuthStatusUpdateHandlers = new Set<StreamingResponseHandler<AuthState>>()
protected _handlerToController = new Map<StreamingResponseHandler<AuthState>, Controller>()
protected _controller: Controller
@@ -75,9 +73,7 @@ export class AuthService {
* @param controller - Optional reference to the Controller instance.
*/
protected constructor(controller: Controller) {
// Default to firebase for now
const providerName = featureFlagsService.getWorkOsAuthEnabled() ? "cline" : "firebase"
this._setProvider(providerName)
this._initProvider()
this._controller = controller
}
@@ -120,13 +116,7 @@ export class AuthService {
throw new Error("Auth provider is not set")
}
const token = await this.internalGetAuthToken(this._provider)
if (!token && this._fallbackProvider) {
return this.internalGetAuthToken(this._fallbackProvider)
}
return token
return this.internalGetAuthToken(this._provider)
}
/**
@@ -173,8 +163,7 @@ export class AuthService {
}
// IMPORTANT: Prefix with 'workos:' so backend can route verification to WorkOS provider
const prefix = provider.name === "cline" ? "workos:" : ""
return clineAccountAuthToken ? `${prefix}${clineAccountAuthToken}` : null
return clineAccountAuthToken ? `workos:${clineAccountAuthToken}` : null
} catch (error) {
console.error("Error getting auth token:", error)
return null
@@ -185,19 +174,9 @@ export class AuthService {
return this._clineAuthInfo?.provider === provider.name
}
protected _setProvider(providerName: string): void {
protected _initProvider(): void {
// Only ClineAuthProvider is supported going forward
// Keeping the providerName param for forward compatibility/telemetry
switch (providerName) {
case "cline":
this._provider = new ClineAuthProvider()
this._fallbackProvider = new FirebaseAuthProvider()
break
case "firebase":
default:
this._provider = new FirebaseAuthProvider()
break
}
this._provider = new ClineAuthProvider()
}
/**
@@ -334,13 +313,7 @@ export class AuthService {
throw new Error("Auth provider is not set")
}
const authInfo = await this._provider.retrieveClineAuthInfo(this._controller)
if (!authInfo && this._fallbackProvider) {
return this._fallbackProvider.retrieveClineAuthInfo(this._controller)
}
return authInfo
return this._provider.retrieveClineAuthInfo(this._controller)
}
/**
@@ -408,15 +381,14 @@ export class AuthService {
})
await Promise.all(streamSends)
// Identify the user in telemetry if available
if (this._clineAuthInfo?.userInfo?.id) {
telemetryService.identifyAccount(this._clineAuthInfo.userInfo)
// Reset feature flags to ensure they are fetched for the new/logged in user
featureFlagsService.reset()
featureFlagsService.reset(this._clineAuthInfo?.userInfo?.id)
}
// Poll feature flags to ensure they are up to date for all users
await featureFlagsService.poll()
await featureFlagsService.poll(this._clineAuthInfo?.userInfo?.id)
// Update state in webviews once per unique controller
await Promise.all(Array.from(uniqueControllers).map((c) => c.postStateToWebview()))
+1 -3
View File
@@ -16,9 +16,7 @@ export class AuthServiceMock extends AuthService {
throw new Error("AuthServiceMock should only be used in local environment for testing purposes.")
}
// Support both auth providers, default to firebase for compatibility
const authProvider = process.env.E2E_TEST_AUTH_PROVIDER || "firebase"
this._setProvider(authProvider)
this._initProvider()
this._controller = controller
}
@@ -116,6 +116,7 @@ export class ClineAuthProvider implements IAuthProvider {
const authInfo = await this.refreshToken(storedAuthData.refreshToken)
const newAuthInfoString = JSON.stringify(authInfo)
if (newAuthInfoString !== storedAuthDataString) {
controller.stateManager.setSecret("clineAccountId", undefined) // cleanup old key
controller.stateManager.setSecret("cline:clineAccountId", newAuthInfoString)
}
return authInfo || null
@@ -1,138 +0,0 @@
import axios from "axios"
import { initializeApp } from "firebase/app"
import { GithubAuthProvider, GoogleAuthProvider, getAuth, type OAuthCredential, signInWithCredential, User } from "firebase/auth"
import { jwtDecode } from "jwt-decode"
import { ClineEnv, EnvironmentConfig } from "@/config"
import { Controller } from "@/core/controller"
import { ErrorService } from "@/services/error"
import { getAxiosSettings } from "@/shared/net"
import type { ClineAccountUserInfo, ClineAuthInfo } from "../AuthService"
import { IAuthProvider } from "./IAuthProvider"
export class FirebaseAuthProvider implements IAuthProvider {
readonly name = "firebase"
readonly callbackEndpoint = "/auth"
get config(): EnvironmentConfig {
return ClineEnv.config()
}
async shouldRefreshIdToken(existingIdToken: string, _expiresAt?: number): Promise<boolean> {
const decodedToken = jwtDecode(existingIdToken)
const exp = decodedToken.exp || 0 // 1752297633
const expirationTime = exp * 1000
const currentTime = Date.now()
const fiveMinutesInMs = 5 * 60 * 1000
if (currentTime > expirationTime - fiveMinutesInMs) {
return true // id token is expired or about to be expired
}
return false
}
/**
* Restores the authentication token using a provided token.
* @param token - The authentication token to restore.
* @returns {Promise<User>} A promise that resolves with the authenticated user.
* @throws {Error} Throws an error if the restoration fails.
*/
async retrieveClineAuthInfo(controller: Controller): Promise<ClineAuthInfo | null> {
const userRefreshToken = controller.stateManager.getSecretKey("clineAccountId")
if (!userRefreshToken) {
console.error("No stored authentication credential found.")
return null
}
try {
// Exchange refresh token for new access token using Firebase's secure token endpoint
const { idToken } = await this.refreshToken(userRefreshToken)
if (!idToken) {
throw new Error("No ID token received from refresh token exchange")
}
// Now retrieve the user info from the backend (this was an easy solution to keep providing user profile details like name and email, but we should move to using the fetchMe() function instead)
// Fetch user info from Cline API
// TODO: consolidate with fetchMe() instead of making the call directly here
const userResponse = await axios.get(`${ClineEnv.config().apiBaseUrl}/api/v1/users/me`, {
headers: {
Authorization: `Bearer ${idToken}`,
},
...getAxiosSettings(),
})
// Store user data
const userInfo: ClineAccountUserInfo = userResponse.data.data
return { idToken, userInfo, provider: this.name }
} catch (error) {
ErrorService.get().logException(error)
throw error
}
}
async refreshToken(userRefreshToken: string): Promise<Partial<ClineAuthInfo>> {
// Exchange refresh token for new access token using Firebase's secure token endpoint
// https://stackoverflow.com/questions/38233687/how-to-use-the-firebase-refreshtoken-to-reauthenticate/57119131#57119131
const firebaseApiKey = this.config.firebase.apiKey
const googleAccessTokenResponse = await axios.post(
`https://securetoken.googleapis.com/v1/token?key=${firebaseApiKey}`,
`grant_type=refresh_token&refresh_token=${encodeURIComponent(userRefreshToken)}`,
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
...getAxiosSettings(),
},
)
// This returns an object with access_token, expires_in (3600), id_token (can be used as bearer token to authenticate requests, we'll use this in the future instead of firebase but need to be aware of how we use firebase sdk for e.g. user info like the profile image), project_id, refresh_token, token_type (always Bearer), and user_id
// Store user data
return { idToken: googleAccessTokenResponse.data.id_token }
}
getAuthRequest(callbackUrl: string): Promise<string> {
// Use URL object for more graceful query construction
const authUrl = new URL(`${ClineEnv.config().appBaseUrl}/auth`)
authUrl.searchParams.set("callback_url", callbackUrl)
return Promise.resolve(authUrl.toString())
}
async signIn(controller: Controller, token: string, provider: string): Promise<ClineAuthInfo | null> {
try {
let credential: OAuthCredential
switch (provider) {
case "google":
credential = GoogleAuthProvider.credential(token)
break
case "github":
credential = GithubAuthProvider.credential(token)
break
default:
throw new Error(`Unsupported provider: ${provider}`)
}
// we've received the short-lived tokens from google/github, now we need to sign in to firebase with them
const firebaseConfig = Object.assign({}, this.config.firebase)
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
// this signs the user into firebase sdk internally
const userCredential = (await signInWithCredential(auth, credential)).user
// const userRefreshToken = await userCredential.getIdToken()
// store the long-lived refresh token in secret storage
try {
controller.stateManager.setSecret("clineAccountId", userCredential.refreshToken)
} catch (error) {
ErrorService.get().logMessage("Firebase store token error", "error")
ErrorService.get().logException(error)
throw error
}
// userCredential = await this._signInWithCredential(context, credential)
return await this.retrieveClineAuthInfo(controller)
} catch (error) {
ErrorService.get().logMessage("Firebase sign-in error", "error")
ErrorService.get().logException(error)
throw error
}
}
}
@@ -64,7 +64,7 @@ class NoOpFeatureFlagsProvider implements IFeatureFlagsProvider {
return undefined
}
public async getFeatureFlagPayload(flagName: string): Promise<unknown> {
public async getFeatureFlagPayload(flagName: string) {
Logger.info(`[NoOpFeatureFlagsProvider] getFeatureFlagPayload called with flagName=${flagName}`)
return null
}
@@ -1,5 +1,7 @@
import { clearOnboardingModelsCache, getClineOnboardingModels } from "@/core/controller/models/getClineOnboardingModels"
import type { OnboardingModel } from "@/shared/proto/cline/state"
import { FEATURE_FLAGS, FeatureFlag, FeatureFlagDefaultValue } from "@/shared/services/feature-flags/feature-flags"
import type { IFeatureFlagsProvider } from "./providers/IFeatureFlagsProvider"
import type { FeatureFlagPayload, IFeatureFlagsProvider } from "./providers/IFeatureFlagsProvider"
// Default cache time-to-live (TTL) for feature flags - an hour
const DEFAULT_CACHE_TTL = 60 * 60 * 1000
@@ -17,35 +19,44 @@ export class FeatureFlagsService {
*/
public constructor(private provider: IFeatureFlagsProvider) {}
private cache: Map<FeatureFlag, unknown> = new Map()
private lastCacheUpdateTime: number = 0
private cache: Map<FeatureFlag, FeatureFlagPayload> = new Map()
/**
* Tracks cache update time and user ID for cache validity
*/
private cacheInfo = { updateTime: 0, userId: null as string | null }
/**
* Poll all known feature flags to update their cached values
*/
public async poll(): Promise<void> {
public async poll(userId?: string): Promise<void> {
// Do not update cache if last update was less than an hour ago
const timesNow = Date.now()
if (timesNow - this.lastCacheUpdateTime < DEFAULT_CACHE_TTL) {
return
if (timesNow - this.cacheInfo.updateTime < DEFAULT_CACHE_TTL && this.cache.size) {
// If time is within TTL, only skip if user context (userId) is unchanged.
// If userId changed (including from/to undefined/null), refresh cache.
if (userId && this.cacheInfo.userId === userId) {
return
}
}
this.lastCacheUpdateTime = timesNow
this.cacheInfo = { updateTime: timesNow, userId: userId || null }
for (const flag of FEATURE_FLAGS) {
const flagEnabled = await this.getFeatureFlag(flag).catch(() => false)
this.cache.set(flag, flagEnabled === true)
const payload = await this.getFeatureFlag(flag).catch(() => false)
this.cache.set(flag, payload ?? false)
}
getClineOnboardingModels() // Refresh onboarding models cache if relevant flag changed
}
private async getFeatureFlag(flagName: FeatureFlag): Promise<unknown> {
private async getFeatureFlag(flagName: FeatureFlag): Promise<FeatureFlagPayload | undefined> {
try {
const payload = await this.provider.getFeatureFlagPayload(flagName)
const flagValue = await this.provider.getFeatureFlag(flagName)
const value = flagValue ?? FeatureFlagDefaultValue[flagName]
this.cache.set(flagName, value)
const value = payload ?? flagValue ?? FeatureFlagDefaultValue[flagName] ?? undefined
return value
} catch (error) {
console.error(`Error checking if feature flag ${flagName} is enabled:`, error)
this.cache.set(flagName, false)
return false
return FeatureFlagDefaultValue[flagName] ?? false
}
}
@@ -74,10 +85,6 @@ export class FeatureFlagsService {
return this.cache.get(flagName) === true
}
public getWorkOsAuthEnabled(): boolean {
return this.getBooleanFlagEnabled(FeatureFlag.WORKOS_AUTH)
}
public getDoNothingFlag(): boolean {
return this.getBooleanFlagEnabled(FeatureFlag.DO_NOTHING)
}
@@ -90,8 +97,14 @@ export class FeatureFlagsService {
return this.getBooleanFlagEnabled(FeatureFlag.NATIVE_TOOL_CALLS_NEXT_GEN_MODELS)
}
public getOnboardingEnabled(): boolean {
return this.getBooleanFlagEnabled(FeatureFlag.ONBOARDING)
public getOnboardingOverrides() {
const payload = this.cache.get(FeatureFlag.ONBOARDING_MODELS)
// Check if payload is object
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
return payload.models as unknown as Record<string, OnboardingModel & { hidden?: boolean }>
}
clearOnboardingModelsCache()
return undefined
}
/**
@@ -99,9 +112,9 @@ export class FeatureFlagsService {
* @param flagName The feature flag key
* @returns The feature flag payload or null if not found
*/
public async getPayload(flagName: string): Promise<unknown> {
public async getPayload(flagName: string): Promise<FeatureFlagPayload | null> {
try {
return await this.provider.getFeatureFlagPayload(flagName)
return (await this.provider.getFeatureFlagPayload(flagName)) ?? null
} catch (error) {
console.error(`Error retrieving feature flag payload for ${flagName}:`, error)
return null
@@ -136,9 +149,13 @@ export class FeatureFlagsService {
* Reset the feature flags cache
* Should run on user auth state changes to ensure flags are up-to-date
*/
public reset(): void {
public reset(userId?: string): void {
// Skip known user ID to avoid redundant resets
if (userId && this.cacheInfo.userId === userId) {
return
}
this.cacheInfo = { updateTime: 0, userId: userId || null }
this.cache.clear()
this.lastCacheUpdateTime = 0
}
/**
@@ -13,6 +13,18 @@ export interface FeatureFlagsSettings {
timeout?: number
}
type JsonType =
| string
| number
| boolean
| null
| {
[key: string]: JsonType
}
| Array<JsonType>
| JsonType[]
export type FeatureFlagPayload = string | number | boolean | { [key: string]: JsonType } | JsonType[] | null
/**
* Abstract interface for feature flags providers
* Any feature flags provider must implement this interface
@@ -23,14 +35,14 @@ export interface IFeatureFlagsProvider {
* @param flagName The feature flag key
* @returns Promise resolving to the flag value (boolean, string, or undefined)
*/
getFeatureFlag(flagName: string): Promise<boolean | string | undefined>
getFeatureFlag(flagName: string): Promise<FeatureFlagPayload | undefined>
/**
* Get the feature flag payload for advanced use cases
* @param flagName The feature flag key
* @returns Promise resolving to the feature flag payload or null if not found
*/
getFeatureFlagPayload(flagName: string): Promise<unknown>
getFeatureFlagPayload(flagName: string): Promise<FeatureFlagPayload | undefined>
/**
* Check if the provider is enabled and ready

Some files were not shown because too many files have changed in this diff Show More