mirror of
https://github.com/cline/cline.git
synced 2026-09-04 20:02:30 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43b863809a |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: automatically retry on rate limit errors with SAP AI Core provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
feat: preserve reasoning traces for cline/openrouter/anthropic providers to maintain conversation integrity
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Empty Pr to bump changeset
|
||||
@@ -71,5 +71,5 @@ jobs:
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
CLINE_ENVIRONMENT: staging
|
||||
run: npm run publish:marketplace:nightly
|
||||
CLINE_ENVIRONMENT: production
|
||||
run: npm run publish:marketplace:nightly
|
||||
+2
-6
@@ -1,13 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [3.32.0]
|
||||
|
||||
- Added the new code-supernova-1-million stealth model, available for free and delivering a 1 million token context window
|
||||
- Changes to inform Cline about commands that are available on your system
|
||||
|
||||
## [3.31.1]
|
||||
|
||||
- Version bump
|
||||
- Add installed useful CLI tools to environment details
|
||||
- Rename MCP tab 'Installed' to 'Configure'
|
||||
|
||||
## [3.31.0]
|
||||
|
||||
|
||||
+1
-2
@@ -126,8 +126,7 @@
|
||||
{
|
||||
"group": "Customization",
|
||||
"pages": [
|
||||
"features/customization/opening-cline-in-sidebar",
|
||||
"features/customization/disable-terminal-pagers"
|
||||
"features/customization/opening-cline-in-sidebar"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
title: "Disable Terminal Pagers During Cline Sessions"
|
||||
description: "Make CLI output non-interactive when Cline runs commands by detecting the CLINE_ACTIVE environment variable and disabling pagers like less."
|
||||
---
|
||||
|
||||
Many CLI tools (like Git) use a pager such as `less` for interactive, scrollable output. When Cline runs commands in your terminal, that interactivity gets in the way — the pager can pause on the first page and block progress. You can configure your shell so that when a terminal is spawned by Cline, pagers are disabled and output streams through normally.
|
||||
|
||||
## How it works
|
||||
|
||||
Cline sets an environment variable for terminals it opens to run commands:
|
||||
|
||||
- `CLINE_ACTIVE` — non-empty when the shell is running under Cline
|
||||
|
||||
You can detect this variable in your shell startup file and adjust environment variables or aliases only for Cline-run sessions. This keeps your normal interactive terminals unchanged.
|
||||
|
||||
## Quick setup (Zsh/Bash)
|
||||
|
||||
Add the following to your `~/.zshrc`, `~/.bashrc`, or `~/.bash_profile`:
|
||||
|
||||
```bash
|
||||
# Disable pagers when the terminal is launched by Cline
|
||||
if [[ -n "$CLINE_ACTIVE" ]]; then
|
||||
export PAGER=cat
|
||||
export GIT_PAGER=cat
|
||||
export SYSTEMD_PAGER=cat
|
||||
export LESS="-FRX"
|
||||
fi
|
||||
```
|
||||
|
||||
<Note>
|
||||
- `PAGER=cat` ensures generic pager-aware tools print directly to stdout
|
||||
- `GIT_PAGER=cat` prevents Git from invoking `less`
|
||||
- `SYSTEMD_PAGER=cat` disables paging in systemd tools (if present)
|
||||
- `LESS="-FRX"` makes `less` behave more like streaming output if a tool still calls it
|
||||
</Note>
|
||||
|
||||
This configuration only applies when `CLINE_ACTIVE` is set, so your normal terminals keep their usual interactive behavior.
|
||||
|
||||
## Verify
|
||||
|
||||
- Open a task in Cline that runs terminal commands and check:
|
||||
- `echo "$CLINE_ACTIVE"` prints a non-empty value
|
||||
- `git log` or other long outputs should stream without pausing
|
||||
- If changes don't take effect:
|
||||
- Make sure you updated the correct startup file for your shell
|
||||
- Restart VS Code/Cursor so integrated terminals reload your shell config
|
||||
- Confirm your terminal profile sources your `~/.zshrc` or `~/.bashrc`
|
||||
|
||||
## Optional tweaks
|
||||
|
||||
- Prefer command-line options when you don't want to rely on env vars:
|
||||
|
||||
```bash
|
||||
# One-off usage (no aliases)
|
||||
git --no-pager log -n 50 --decorate --oneline
|
||||
systemctl --no-pager status nginx
|
||||
journalctl --no-pager -u nginx -n 200
|
||||
less -FRX README.md
|
||||
```
|
||||
|
||||
- You can also override paging via shell aliases scoped to Cline sessions using options rather than env vars:
|
||||
|
||||
```bash
|
||||
if [[ -n "$CLINE_ACTIVE" ]]; then
|
||||
# Make 'less' non-interactive by default
|
||||
alias less='less -FRX'
|
||||
# Disable paging for common tools via CLI flags
|
||||
alias git='command git --no-pager'
|
||||
alias systemctl='command systemctl --no-pager'
|
||||
alias journalctl='command journalctl --no-pager'
|
||||
fi
|
||||
```
|
||||
|
||||
- If you prefer environment variables, many CLIs also respect a generic or tool-specific pager variable:
|
||||
- Git: `GIT_PAGER=cat`
|
||||
- Systemd: `SYSTEMD_PAGER=cat`
|
||||
- Man pages: `MANPAGER=cat` (not typically needed for Cline-driven commands)
|
||||
|
||||
- Aliases affect the current interactive shell, while environment variables propagate to child processes. Choose the approach that best fits your workflow.
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.32.0",
|
||||
"version": "3.30.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.32.0",
|
||||
"version": "3.30.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.32.0",
|
||||
"version": "3.31.1",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
|
||||
@@ -50,7 +50,7 @@ service FileService {
|
||||
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
|
||||
|
||||
// Opens a task's conversation history file on disk
|
||||
rpc openDiskConversationHistory(StringRequest) returns (Empty);
|
||||
rpc openTaskHistory(StringRequest) returns (Empty);
|
||||
|
||||
// Toggles a workflow on or off
|
||||
rpc toggleWorkflow(ToggleWorkflowRequest) returns (ClineRulesToggles);
|
||||
|
||||
@@ -150,8 +150,6 @@ export class AnthropicHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
let thinkingDeltaAccumulator = ""
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
@@ -184,26 +182,14 @@ export class AnthropicHandler implements ApiHandler {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.content_block.thinking || "",
|
||||
}
|
||||
const thinking = chunk.content_block.thinking
|
||||
const signature = chunk.content_block.signature
|
||||
if (thinking && signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
break
|
||||
case "redacted_thinking":
|
||||
// Content is encrypted, and we don't to pass placeholder text back to the API
|
||||
// Handle redacted thinking blocks - we still mark it as reasoning
|
||||
// but note that the content is encrypted
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: "[Redacted thinking block]",
|
||||
}
|
||||
yield {
|
||||
type: "ant_redacted_thinking",
|
||||
data: chunk.content_block.data,
|
||||
}
|
||||
break
|
||||
case "text":
|
||||
// we may receive multiple text blocks, in which case just insert a line break between them
|
||||
@@ -223,23 +209,10 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
}
|
||||
thinkingDeltaAccumulator += chunk.delta.thinking
|
||||
break
|
||||
case "signature_delta":
|
||||
// It's used when sending the thinking block back to the API
|
||||
// API expects this in completed form, not as array of deltas
|
||||
if (thinkingDeltaAccumulator && chunk.delta.signature) {
|
||||
yield {
|
||||
type: "ant_thinking",
|
||||
thinking: thinkingDeltaAccumulator,
|
||||
signature: chunk.delta.signature,
|
||||
}
|
||||
}
|
||||
break
|
||||
case "text_delta":
|
||||
yield {
|
||||
@@ -247,6 +220,10 @@ export class AnthropicHandler implements ApiHandler {
|
||||
text: chunk.delta.text,
|
||||
}
|
||||
break
|
||||
case "signature_delta":
|
||||
// We don't need to do anything with the signature in the client
|
||||
// It's used when sending the thinking block back to the API
|
||||
break
|
||||
}
|
||||
break
|
||||
case "content_block_stop":
|
||||
|
||||
@@ -162,7 +162,7 @@ export class ClineHandler implements ApiHandler {
|
||||
// @ts-ignore-next-line
|
||||
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
|
||||
if (this.getModel().id === "cline/code-supernova-1-million") {
|
||||
if (this.getModel().id === "cline/code-supernova") {
|
||||
totalCost = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -122,21 +122,6 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// OpenRouter passes reasoning details that we can pass back unmodified in api requests to preserve reasoning traces for model
|
||||
// See: https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning-blocks
|
||||
if (
|
||||
"reasoning_details" in delta &&
|
||||
delta.reasoning_details &&
|
||||
// @ts-ignore-next-line
|
||||
delta.reasoning_details.length && // exists and non-0
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
type: "reasoning_details",
|
||||
reasoning_details: delta.reasoning_details,
|
||||
}
|
||||
}
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ModelInfo, SapAiCoreModelId, sapAiCoreDefaultModelId, sapAiCoreModels }
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
@@ -455,7 +454,6 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
return this.deployments?.some((d) => d.name.split(":")[0].toLowerCase() === modelId.split(":")[0].toLowerCase()) ?? false
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
if (this.options.sapAiCoreUseOrchestrationMode) {
|
||||
yield* this.createMessageWithOrchestration(systemPrompt, messages)
|
||||
|
||||
@@ -115,15 +115,7 @@ export function convertToOpenAiMessages(
|
||||
|
||||
// Process non-tool messages
|
||||
let content: string | undefined
|
||||
const reasoningDetails: any[] = []
|
||||
if (nonToolMessages.length > 0) {
|
||||
nonToolMessages.forEach((part) => {
|
||||
// @ts-ignore-next-line
|
||||
if (part.type === "text" && part.reasoning_details) {
|
||||
// @ts-ignore-next-line
|
||||
reasoningDetails.push(part.reasoning_details)
|
||||
}
|
||||
})
|
||||
content = nonToolMessages
|
||||
.map((part) => {
|
||||
if (part.type === "image") {
|
||||
@@ -150,8 +142,6 @@ export function convertToOpenAiMessages(
|
||||
content,
|
||||
// Cannot be an empty array. API expects an array with minimum length 1, and will respond with an error if it's empty
|
||||
tool_calls: tool_calls.length > 0 ? tool_calls : undefined,
|
||||
// @ts-ignore-next-line
|
||||
reasoning_details: reasoningDetails,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk>
|
||||
export type ApiStreamChunk =
|
||||
| ApiStreamTextChunk
|
||||
| ApiStreamReasoningChunk
|
||||
| ApiStreamReasoningDetailsChunk
|
||||
| ApiStreamAnthropicThinkingChunk
|
||||
| ApiStreamAnthropicRedactedThinkingChunk
|
||||
| ApiStreamUsageChunk
|
||||
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamReasoningChunk | ApiStreamUsageChunk
|
||||
|
||||
export interface ApiStreamTextChunk {
|
||||
type: "text"
|
||||
@@ -17,22 +11,6 @@ export interface ApiStreamReasoningChunk {
|
||||
reasoning: string
|
||||
}
|
||||
|
||||
export interface ApiStreamReasoningDetailsChunk {
|
||||
type: "reasoning_details"
|
||||
reasoning_details: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
}
|
||||
|
||||
export interface ApiStreamAnthropicThinkingChunk {
|
||||
type: "ant_thinking"
|
||||
thinking: string
|
||||
signature: string
|
||||
}
|
||||
|
||||
export interface ApiStreamAnthropicRedactedThinkingChunk {
|
||||
type: "ant_redacted_thinking"
|
||||
data: string
|
||||
}
|
||||
|
||||
export interface ApiStreamUsageChunk {
|
||||
type: "usage"
|
||||
inputTokens: number
|
||||
|
||||
+3
-3
@@ -9,11 +9,11 @@ import { Controller } from ".."
|
||||
* @param request The request message containing the file path in the 'value' field
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function openDiskConversationHistory(_controller: Controller, request: StringRequest): Promise<Empty> {
|
||||
export async function openTaskHistory(_controller: Controller, request: StringRequest): Promise<Empty> {
|
||||
const globalStoragePath = HostProvider.get().globalStorageFsPath
|
||||
const taskConversationHistoryPath = path.join(globalStoragePath, "tasks", request.value, "api_conversation_history.json")
|
||||
const taskHistoryPath = path.join(globalStoragePath, "tasks", request.value, "api_conversation_history.json")
|
||||
if (request.value) {
|
||||
openFileIntegration(taskConversationHistoryPath)
|
||||
openFileIntegration(taskHistoryPath)
|
||||
}
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -242,7 +242,7 @@ export async function refreshOpenRouterModels(
|
||||
* Stealth models are models that are compatible with the OpenRouter API but not listed on the OpenRouter website or API.
|
||||
*/
|
||||
const CLINE_STEALTH_MODELS: Record<string, OpenRouterModelInfo> = {
|
||||
"cline/code-supernova-1-million": OpenRouterModelInfo.create({
|
||||
"cline/code-supernova": OpenRouterModelInfo.create({
|
||||
maxTokens: clineCodeSupernovaModelInfo.maxTokens ?? 0,
|
||||
contextWindow: clineCodeSupernovaModelInfo.contextWindow ?? 0,
|
||||
supportsImages: clineCodeSupernovaModelInfo.supportsImages ?? false,
|
||||
|
||||
@@ -99,7 +99,7 @@ export async function ensureMcpServersDirectoryExists(): Promise<string> {
|
||||
try {
|
||||
await fs.mkdir(mcpServersDir, { recursive: true })
|
||||
} catch (_error) {
|
||||
return path.join(os.homedir(), "Documents", "Cline", "MCP") // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
|
||||
return "~/Documents/Cline/MCP" // in case creating a directory in documents fails for whatever reason (e.g. permissions) - this is fine since this path is only ever used in the system prompt
|
||||
}
|
||||
return mcpServersDir
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ANTHROPIC_MIN_THINKING_BUDGET, ApiProvider, fireworksDefaultModelId, type OcaModelInfo } from "@shared/api"
|
||||
import { ApiProvider, fireworksDefaultModelId, type OcaModelInfo } from "@shared/api"
|
||||
import { ExtensionContext } from "vscode"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@/shared/AutoApprovalSettings"
|
||||
@@ -461,9 +461,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: planModeApiProvider || apiProvider,
|
||||
planModeApiModelId,
|
||||
// undefined means it was never modified, 0 means it was turned off
|
||||
// (having this on by default ensures that <thinking> text does not pollute the user's chat and is instead rendered as reasoning)
|
||||
planModeThinkingBudgetTokens: planModeThinkingBudgetTokens ?? ANTHROPIC_MIN_THINKING_BUDGET,
|
||||
planModeThinkingBudgetTokens,
|
||||
planModeReasoningEffort,
|
||||
planModeVsCodeLmModelSelector,
|
||||
planModeAwsBedrockCustomSelected,
|
||||
@@ -497,7 +495,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
|
||||
// Act mode configurations
|
||||
actModeApiProvider: actModeApiProvider || apiProvider,
|
||||
actModeApiModelId,
|
||||
actModeThinkingBudgetTokens: actModeThinkingBudgetTokens ?? ANTHROPIC_MIN_THINKING_BUDGET,
|
||||
actModeThinkingBudgetTokens,
|
||||
actModeReasoningEffort,
|
||||
actModeVsCodeLmModelSelector,
|
||||
actModeAwsBedrockCustomSelected,
|
||||
|
||||
+2
-35
@@ -1218,6 +1218,7 @@ export class Task {
|
||||
|
||||
if (userFeedback) {
|
||||
await this.say("user_feedback", userFeedback.text, userFeedback.images, userFeedback.files)
|
||||
await this.checkpointManager?.saveCheckpoint()
|
||||
|
||||
let fileContentString = ""
|
||||
if (userFeedback.files && userFeedback.files.length > 0) {
|
||||
@@ -1998,8 +1999,6 @@ export class Task {
|
||||
const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
|
||||
let assistantMessage = ""
|
||||
let reasoningMessage = ""
|
||||
const reasoningDetails = []
|
||||
const antThinkingContent: (Anthropic.Messages.RedactedThinkingBlock | Anthropic.Messages.ThinkingBlock)[] = []
|
||||
this.taskState.isStreaming = true
|
||||
let didReceiveUsageChunk = false
|
||||
try {
|
||||
@@ -2024,24 +2023,6 @@ export class Task {
|
||||
await this.say("reasoning", reasoningMessage, undefined, undefined, true)
|
||||
}
|
||||
break
|
||||
// for cline/openrouter providers
|
||||
case "reasoning_details":
|
||||
reasoningDetails.push(chunk.reasoning_details)
|
||||
break
|
||||
// for anthropic providers
|
||||
case "ant_thinking":
|
||||
antThinkingContent.push({
|
||||
type: "thinking",
|
||||
thinking: chunk.thinking,
|
||||
signature: chunk.signature,
|
||||
})
|
||||
break
|
||||
case "ant_redacted_thinking":
|
||||
antThinkingContent.push({
|
||||
type: "redacted_thinking",
|
||||
data: chunk.data,
|
||||
})
|
||||
break
|
||||
case "text": {
|
||||
if (reasoningMessage && assistantMessage.length === 0) {
|
||||
// complete reasoning message
|
||||
@@ -2171,21 +2152,7 @@ export class Task {
|
||||
|
||||
await this.messageStateHandler.addToApiConversationHistory({
|
||||
role: "assistant",
|
||||
content: [
|
||||
// This is critical for maintaining the model’s reasoning flow and conversation integrity.
|
||||
// "When providing thinking blocks, the entire sequence of consecutive thinking blocks must match the outputs generated by the model during the original request; you cannot rearrange or modify the sequence of these blocks." The signature_delta is used to verify that the thinking was generated by Claude, and the thinking blocks will be ignored if it's incorrect or missing.
|
||||
// https://docs.claude.com/en/docs/build-with-claude/extended-thinking#preserving-thinking-blocks
|
||||
...antThinkingContent,
|
||||
{
|
||||
type: "text",
|
||||
text: assistantMessage,
|
||||
// reasoning_details only exists for cline/openrouter providers
|
||||
// @ts-ignore-next-line
|
||||
reasoning_details: reasoningDetails.length > 0 ? reasoningDetails : undefined,
|
||||
},
|
||||
] as Array<
|
||||
Anthropic.Messages.RedactedThinkingBlock | Anthropic.Messages.ThinkingBlock | Anthropic.Messages.TextBlock
|
||||
>,
|
||||
content: [{ type: "text", text: assistantMessage }],
|
||||
})
|
||||
|
||||
// NOTE: this comment is here for future reference - this was a workaround for userMessageContent not getting set to true. It was due to it not recursively calling for partial blocks when didRejectTool, so it would get stuck waiting for a partial block to complete before it could continue.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ApiHandler } from "@core/api"
|
||||
import { execSync } from "child_process"
|
||||
import { showSystemNotification } from "@/integrations/notifications"
|
||||
import { ClineApiReqCancelReason, ClineApiReqInfo } from "@/shared/ExtensionMessage"
|
||||
import { calculateApiCostAnthropic } from "@/utils/cost"
|
||||
import { MessageStateHandler } from "./message-state"
|
||||
import { execSync } from "child_process"
|
||||
|
||||
export const showNotificationForApprovalIfAutoApprovalEnabled = (
|
||||
message: string,
|
||||
|
||||
Vendored
+1
-2
@@ -60,8 +60,7 @@ export abstract class BaseGrpcClient<TClient> {
|
||||
|
||||
protected getClient(): TClient {
|
||||
if (!this.client || !this.channel) {
|
||||
const channelOptions = { "grpc.enable_http_proxy": 0 }
|
||||
this.channel = createChannel(this.address, undefined, channelOptions)
|
||||
this.channel = createChannel(this.address)
|
||||
this.client = this.createClient(this.channel)
|
||||
}
|
||||
return this.client
|
||||
|
||||
@@ -50,6 +50,7 @@ export class ServiceRegistry {
|
||||
}
|
||||
|
||||
this.methodMetadata[methodName] = { isStreaming, ...metadata }
|
||||
console.log(`Registered ${this.serviceName} method: ${methodName}${isStreaming ? " (streaming)" : ""}`)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-12
@@ -262,7 +262,6 @@ export const CLAUDE_SONNET_4_1M_TIERS = [
|
||||
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
|
||||
export type AnthropicModelId = keyof typeof anthropicModels
|
||||
export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-20250514"
|
||||
export const ANTHROPIC_MIN_THINKING_BUDGET = 1_024
|
||||
export const anthropicModels = {
|
||||
"claude-sonnet-4-20250514:1m": {
|
||||
maxTokens: 8192,
|
||||
@@ -597,7 +596,7 @@ export const openRouterDefaultModelInfo: ModelInfo = {
|
||||
|
||||
// Cline custom model - code-supernova
|
||||
export const clineCodeSupernovaModelInfo: ModelInfo = {
|
||||
contextWindow: 1000000,
|
||||
contextWindow: 200000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
@@ -2510,16 +2509,6 @@ export const nebiusDefaultModelId = "Qwen/Qwen2.5-32B-Instruct-fast" satisfies N
|
||||
export type XAIModelId = keyof typeof xaiModels
|
||||
export const xaiDefaultModelId: XAIModelId = "grok-4"
|
||||
export const xaiModels = {
|
||||
"grok-4-fast-reasoning": {
|
||||
maxTokens: 30000,
|
||||
contextWindow: 2000000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.2,
|
||||
cacheReadsPrice: 0.05,
|
||||
outputPrice: 0.5,
|
||||
description: "xAI's Grok 4 Fast (free) multimodal model with 2M context.",
|
||||
},
|
||||
"grok-4": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 262144,
|
||||
|
||||
@@ -32,8 +32,7 @@ function createHealthClient(address: string) {
|
||||
const healthDef = protoLoader.loadSync(health.protoPath)
|
||||
const grpcObj = grpc.loadPackageDefinition(healthDef) as unknown as any
|
||||
const Health = grpcObj.grpc.health.v1.Health
|
||||
const opts: grpc.ChannelOptions = { "grpc.enable_http_proxy": 0 }
|
||||
return new Health(address, grpc.credentials.createInsecure(), opts)
|
||||
return new Health(address, grpc.credentials.createInsecure())
|
||||
}
|
||||
|
||||
async function checkHealthOnce(client: any): Promise<boolean> {
|
||||
|
||||
+72
-48
@@ -1,65 +1,89 @@
|
||||
import { expect } from "@playwright/test"
|
||||
import { e2e } from "./utils/helpers"
|
||||
import { E2E_WORKSPACE_TYPES, e2e } from "./utils/helpers"
|
||||
|
||||
e2e("Chat - can send messages and switch between modes", async ({ helper, sidebar, page }) => {
|
||||
// Sign in
|
||||
await helper.signin(sidebar)
|
||||
e2e.describe("Chat - can send messages and switch between modes", () => {
|
||||
E2E_WORKSPACE_TYPES.forEach(({ title, workspaceType }) => {
|
||||
e2e.extend({
|
||||
workspaceType,
|
||||
})(title, async ({ helper, sidebar, page }) => {
|
||||
// Sign in
|
||||
await helper.signin(sidebar)
|
||||
|
||||
// Submit a message
|
||||
const inputbox = sidebar.getByTestId("chat-input")
|
||||
await expect(inputbox).toBeVisible()
|
||||
await inputbox.fill("Hello, Cline!")
|
||||
await expect(inputbox).toHaveValue("Hello, Cline!")
|
||||
await sidebar.getByTestId("send-button").click({ delay: 100 })
|
||||
await expect(inputbox).toHaveValue("")
|
||||
// Submit a message
|
||||
const inputbox = sidebar.getByTestId("chat-input")
|
||||
await expect(inputbox).toBeVisible()
|
||||
await inputbox.fill("Hello, Cline!")
|
||||
await expect(inputbox).toHaveValue("Hello, Cline!")
|
||||
await sidebar.getByTestId("send-button").click({ delay: 100 })
|
||||
await expect(inputbox).toHaveValue("")
|
||||
|
||||
// Loading State initially
|
||||
await expect(sidebar.getByText("API Request...")).toBeVisible()
|
||||
// Loading State initially
|
||||
await expect(sidebar.getByText("API Request...")).toBeVisible()
|
||||
|
||||
// Starting a new task should clear the current chat view and show the recent tasks
|
||||
await sidebar.getByRole("button", { name: "New Task" }).click()
|
||||
await expect(sidebar.getByText("Recent Tasks")).toBeVisible()
|
||||
await expect(sidebar.getByText("Hello, Cline!")).toBeVisible()
|
||||
// The request should eventually fail
|
||||
await expect(sidebar.getByText("API Request Failed")).toBeVisible()
|
||||
|
||||
// Makes sure the act and plan switches are working correctly
|
||||
// Aria-checked state should be true for Act and false for Plan
|
||||
const actButton = sidebar.getByRole("switch", { name: "Act" })
|
||||
const planButton = sidebar.getByRole("switch", { name: "Plan" })
|
||||
await expect(inputbox).toBeVisible()
|
||||
|
||||
await expect(actButton).toBeChecked()
|
||||
await expect(planButton).not.toBeChecked()
|
||||
await expect(sidebar.getByRole("button", { name: "Retry" })).toBeVisible()
|
||||
await expect(sidebar.getByRole("button", { name: "Start New Task" })).toBeVisible()
|
||||
|
||||
await actButton.click()
|
||||
await expect(actButton).not.toBeChecked()
|
||||
await expect(planButton).toBeChecked()
|
||||
// Starting a new task should clear the current chat view and show the recent tasks
|
||||
await sidebar.getByRole("button", { name: "Start New Task" }).click()
|
||||
await expect(sidebar.getByText("API Request Failed")).not.toBeVisible()
|
||||
await expect(sidebar.getByText("Recent Tasks")).toBeVisible()
|
||||
await expect(sidebar.getByText("Hello, Cline!")).toBeVisible()
|
||||
|
||||
// === slash commands preserve following text ===
|
||||
await expect(inputbox).toHaveValue("")
|
||||
// Type partial slash command to trigger menu
|
||||
await inputbox.pressSequentially("/new", { delay: 100 })
|
||||
// Makes sure the act and plan switches are working correctly
|
||||
// Aria-checked state should be true for Act and false for Plan
|
||||
const actButton = sidebar.getByRole("switch", { name: "Act" })
|
||||
const planButton = sidebar.getByRole("switch", { name: "Plan" })
|
||||
|
||||
// Wait for menu to be visible and select first option with Tab
|
||||
await inputbox.press("Tab")
|
||||
await expect(inputbox).toHaveValue("/newtask ")
|
||||
await expect(actButton).toBeChecked()
|
||||
await expect(planButton).not.toBeChecked()
|
||||
|
||||
// Add following text to verify it works correctly
|
||||
await inputbox.pressSequentially("following text should be preserved")
|
||||
await expect(inputbox).toHaveValue("/newtask following text should be preserved")
|
||||
await actButton.click()
|
||||
await expect(actButton).not.toBeChecked()
|
||||
await expect(planButton).toBeChecked()
|
||||
|
||||
// === @ mentions preserve following text ===
|
||||
await inputbox.fill("")
|
||||
await expect(inputbox).toHaveValue("")
|
||||
await inputbox.fill("Plan mode submission")
|
||||
await sidebar.getByTestId("send-button").click()
|
||||
|
||||
// Type partial @ mention to trigger menu
|
||||
await inputbox.pressSequentially("@prob")
|
||||
await expect(sidebar.getByText("API Request Failed")).toBeVisible()
|
||||
|
||||
// Wait for menu to be visible and select first option with Tab
|
||||
await inputbox.press("Tab")
|
||||
await expect(inputbox).toHaveValue("@problems ")
|
||||
// === slash commands preserve following text ===
|
||||
await inputbox.fill("")
|
||||
await expect(inputbox).toHaveValue("")
|
||||
await inputbox.focus()
|
||||
|
||||
// Add following text to verify it works correctly
|
||||
await inputbox.pressSequentially("following text should be preserved")
|
||||
await expect(inputbox).toHaveValue("@problems following text should be preserved")
|
||||
// Type partial slash command to trigger menu
|
||||
await inputbox.pressSequentially("/new")
|
||||
|
||||
await page.close()
|
||||
// Wait for menu to be visible and select first option with Tab
|
||||
await inputbox.press("Tab")
|
||||
await expect(inputbox).toHaveValue("/newtask ")
|
||||
|
||||
// Add following text to verify it works correctly
|
||||
await inputbox.pressSequentially("following text should be preserved")
|
||||
await expect(inputbox).toHaveValue("/newtask following text should be preserved")
|
||||
|
||||
// === @ mentions preserve following text ===
|
||||
await inputbox.fill("")
|
||||
await expect(inputbox).toHaveValue("")
|
||||
await inputbox.focus()
|
||||
|
||||
// Type partial @ mention to trigger menu
|
||||
await inputbox.pressSequentially("@prob")
|
||||
|
||||
// Wait for menu to be visible and select first option with Tab
|
||||
await inputbox.press("Tab")
|
||||
await expect(inputbox).toHaveValue("@problems ")
|
||||
|
||||
// Add following text to verify it works correctly
|
||||
await inputbox.pressSequentially("following text should be preserved")
|
||||
await expect(inputbox).toHaveValue("@problems following text should be preserved")
|
||||
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -73,8 +73,8 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
}
|
||||
|
||||
const setCodeSupernova = () => {
|
||||
const modelId = "cline/code-supernova-1-million"
|
||||
// set both plan and act modes to use code-supernova-1-million
|
||||
const modelId = "cline/code-supernova"
|
||||
// set both plan and act modes to use code-supernova
|
||||
handleFieldsChange({
|
||||
planModeOpenRouterModelId: modelId,
|
||||
actModeOpenRouterModelId: modelId,
|
||||
@@ -124,20 +124,20 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
</VSCodeButtonLink>
|
||||
</li>
|
||||
<li>
|
||||
<b>Free Models:</b> Try the new code-supernova-1-million stealth model, or grok-code-fast-1 for free!
|
||||
<b>Continued Free Models:</b> Try grok-code-fast-1 or code-supernova (stealth model 🥷)!
|
||||
<br />
|
||||
{user ? (
|
||||
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap", margin: "5px 0" }}>
|
||||
{!didClickCodeSupernovaButton && (
|
||||
<VSCodeButton appearance="primary" onClick={setCodeSupernova}>
|
||||
Try code-supernova
|
||||
</VSCodeButton>
|
||||
)}
|
||||
{!didClickGrokCodeButton && (
|
||||
<VSCodeButton appearance="primary" onClick={setGrokCodeFast1}>
|
||||
Try grok-code-fast-1
|
||||
</VSCodeButton>
|
||||
)}
|
||||
{!didClickCodeSupernovaButton && (
|
||||
<VSCodeButton appearance="primary" onClick={setCodeSupernova}>
|
||||
Try code-supernova
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<VSCodeButton appearance="primary" onClick={handleShowAccount} style={{ margin: "5px 0" }}>
|
||||
|
||||
@@ -381,7 +381,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
scrollToBottomSmooth: scrollBehavior.scrollToBottomSmooth,
|
||||
disableAutoScrollRef: scrollBehavior.disableAutoScrollRef,
|
||||
showScrollToBottom: scrollBehavior.showScrollToBottom,
|
||||
virtuosoRef: scrollBehavior.virtuosoRef,
|
||||
}}
|
||||
task={task}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Mode } from "@shared/storage/types"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import type React from "react"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { VirtuosoHandle } from "react-virtuoso"
|
||||
import { ButtonActionType, getButtonConfig } from "../../shared/buttonConfig"
|
||||
import type { ChatState, MessageHandlers } from "../../types/chatTypes"
|
||||
|
||||
@@ -17,7 +16,6 @@ interface ActionButtonsProps {
|
||||
scrollToBottomSmooth: () => void
|
||||
disableAutoScrollRef: React.MutableRefObject<boolean>
|
||||
showScrollToBottom: boolean
|
||||
virtuosoRef: React.RefObject<VirtuosoHandle>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,53 +94,41 @@ export const ActionButtons: React.FC<ActionButtonsProps> = ({
|
||||
|
||||
const { showScrollToBottom, scrollToBottomSmooth, disableAutoScrollRef } = scrollBehavior
|
||||
|
||||
const { primaryText, secondaryText, primaryAction, secondaryAction, enableButtons } = buttonConfig
|
||||
const hasButtons = primaryText || secondaryText
|
||||
const isStreaming = task.partial === true
|
||||
const canInteract = enableButtons && !isProcessing
|
||||
|
||||
// Early return for scroll button to avoid unnecessary computation
|
||||
if (showScrollToBottom || !hasButtons) {
|
||||
if (showScrollToBottom) {
|
||||
const handleScrollToBottom = () => {
|
||||
scrollToBottomSmooth()
|
||||
disableAutoScrollRef.current = false
|
||||
}
|
||||
// Show scroll to top button when there are no action buttons
|
||||
const handleScrollToTop = () => {
|
||||
scrollBehavior.virtuosoRef.current?.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth",
|
||||
})
|
||||
disableAutoScrollRef.current = true
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex px-[15px]">
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
aria-label={showScrollToBottom ? "Scroll to bottom" : "Scroll to top"}
|
||||
aria-label="Scroll to bottom"
|
||||
className="text-lg text-[var(--vscode-primaryButton-foreground)] bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_55%,transparent)] rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_90%,transparent)] active:bg-[color-mix(in_srgb,var(--vscode-toolbar-hoverBackground)_70%,transparent)] border-0"
|
||||
onClick={showScrollToBottom ? handleScrollToBottom : handleScrollToTop}
|
||||
onClick={handleScrollToBottom}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault()
|
||||
if (showScrollToBottom) {
|
||||
handleScrollToBottom()
|
||||
} else {
|
||||
handleScrollToTop()
|
||||
}
|
||||
handleScrollToBottom()
|
||||
}
|
||||
}}>
|
||||
{showScrollToBottom ? (
|
||||
<span className="codicon codicon-chevron-down" />
|
||||
) : (
|
||||
<span className="codicon codicon-chevron-up" />
|
||||
)}
|
||||
<span className="codicon codicon-chevron-down" />
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const { primaryText, secondaryText, primaryAction, secondaryAction, enableButtons } = buttonConfig
|
||||
const hasButtons = primaryText || secondaryText
|
||||
const isStreaming = task.partial === true
|
||||
const canInteract = enableButtons && !isProcessing
|
||||
|
||||
if (!hasButtons) {
|
||||
return null
|
||||
}
|
||||
|
||||
const opacity = canInteract || isStreaming ? 1 : 0.5
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,7 @@ import { UiServiceClient } from "@/services/grpc-client"
|
||||
import CopyTaskButton from "./buttons/CopyTaskButton"
|
||||
import DeleteTaskButton from "./buttons/DeleteTaskButton"
|
||||
import NewTaskButton from "./buttons/NewTaskButton"
|
||||
import OpenDiskConversationHistoryButton from "./buttons/OpenDiskConversationHistoryButton"
|
||||
import OpenDiskTaskHistoryButton from "./buttons/OpenDiskTaskHistoryButton"
|
||||
import { CheckpointError } from "./CheckpointError"
|
||||
import ContextWindow from "./ContextWindow"
|
||||
import { FocusChain } from "./FocusChain"
|
||||
@@ -117,9 +117,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
taskSize={currentTaskItem?.size}
|
||||
/>
|
||||
{/* Only visible in development mode */}
|
||||
{IS_DEV && (
|
||||
<OpenDiskConversationHistoryButton className={BUTTON_CLASS} taskId={currentTaskItem?.id} />
|
||||
)}
|
||||
{IS_DEV && <OpenDiskTaskHistoryButton className={BUTTON_CLASS} taskId={currentTaskItem?.id} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { Button, cn } from "@heroui/react"
|
||||
import { StringRequest } from "@shared/proto/cline/common"
|
||||
import { ArrowDownToLineIcon } from "lucide-react"
|
||||
import HeroTooltip from "@/components/common/HeroTooltip"
|
||||
import { FileServiceClient } from "@/services/grpc-client"
|
||||
|
||||
const OpenDiskConversationHistoryButton: React.FC<{
|
||||
taskId?: string
|
||||
className?: string
|
||||
}> = ({ taskId, className }) => {
|
||||
const handleOpenDiskConversationHistory = () => {
|
||||
if (!taskId) {
|
||||
return
|
||||
}
|
||||
|
||||
FileServiceClient.openDiskConversationHistory(StringRequest.create({ value: taskId })).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<HeroTooltip content="Open Conversation History File" placement="right">
|
||||
<Button
|
||||
aria-label="Open Disk Conversation History"
|
||||
className={cn("flex items-center border-0 text-sm font-bold bg-transparent hover:opacity-100", className)}
|
||||
isIconOnly={true}
|
||||
onPress={() => handleOpenDiskConversationHistory()}
|
||||
radius="sm"
|
||||
size="sm">
|
||||
<ArrowDownToLineIcon size="14" />
|
||||
</Button>
|
||||
</HeroTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenDiskConversationHistoryButton
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Button, cn } from "@heroui/react"
|
||||
import { StringRequest } from "@shared/proto/cline/common"
|
||||
import { ArrowDownToLineIcon } from "lucide-react"
|
||||
import { FileServiceClient } from "@/services/grpc-client"
|
||||
|
||||
const OpenDiskTaskHistoryButton: React.FC<{
|
||||
taskId?: string
|
||||
className?: string
|
||||
}> = ({ taskId, className }) => {
|
||||
const handleOpenDiskTaskHistory = () => {
|
||||
if (!taskId) {
|
||||
return
|
||||
}
|
||||
|
||||
FileServiceClient.openTaskHistory(StringRequest.create({ value: taskId })).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
aria-label="Open Disk Task History"
|
||||
className={cn("flex items-center border-0 text-sm font-bold bg-transparent hover:opacity-100", className)}
|
||||
isIconOnly={true}
|
||||
onPress={() => handleOpenDiskTaskHistory()}
|
||||
radius="sm"
|
||||
size="sm"
|
||||
title="Export Task">
|
||||
<ArrowDownToLineIcon size="14" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenDiskTaskHistoryButton
|
||||
@@ -22,7 +22,7 @@ const HeroTooltip: React.FC<HeroTooltipProps> = ({
|
||||
className,
|
||||
showArrow = false,
|
||||
delay = 0,
|
||||
closeDelay = 500,
|
||||
closeDelay = 100,
|
||||
placement = "top",
|
||||
disabled = false,
|
||||
}) => {
|
||||
@@ -46,15 +46,20 @@ const HeroTooltip: React.FC<HeroTooltipProps> = ({
|
||||
return (
|
||||
<Tooltip
|
||||
classNames={{
|
||||
content: "hero-tooltip-content pointer-events-none", // Prevent hovering over tooltip
|
||||
base: "pointer-events-none", // Prevent hovering over tooltip container
|
||||
content: "hero-tooltip-content pointer-events-none", // Prevent hovering over tooltip content
|
||||
}}
|
||||
closeDelay={closeDelay}
|
||||
content={formattedContent} // Immediate close when cursor moves away
|
||||
content={formattedContent}
|
||||
delay={delay}
|
||||
disableAnimation={true}
|
||||
isDisabled={disabled}
|
||||
placement={placement} // Disable animation for immediate appearance/disappearance
|
||||
showArrow={showArrow}>
|
||||
placement={placement}
|
||||
showArrow={showArrow}
|
||||
// Inline style to override any library styles - above classNames aren't applying correctly
|
||||
style={{
|
||||
pointerEvents: "none",
|
||||
}}>
|
||||
{children}
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { Int64Request } from "@shared/proto/cline/common"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useCallback } from "react"
|
||||
import { PlatformType } from "@/config/platform.config"
|
||||
import { usePlatform } from "@/context/PlatformContext"
|
||||
import { StateServiceClient } from "@/services/grpc-client"
|
||||
export const CURRENT_INFO_BANNER_VERSION = 1
|
||||
export const InfoBanner: React.FC = () => {
|
||||
const handleClose = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
StateServiceClient.updateInfoBannerVersion({ value: CURRENT_INFO_BANNER_VERSION }).catch(console.error)
|
||||
const request = Int64Request.create({
|
||||
value: CURRENT_INFO_BANNER_VERSION,
|
||||
})
|
||||
StateServiceClient.updateInfoBannerVersion(request).catch(console.error)
|
||||
}, [])
|
||||
if (usePlatform().type === PlatformType.VSCODE) {
|
||||
return (
|
||||
<a
|
||||
className="bg-banner-background px-3 py-2 flex flex-col gap-1 shrink-0 mb-1 relative text-sm m-4 no-underline transition-colors hover:brightness-120"
|
||||
href="https://docs.cline.bot/features/customization/opening-cline-in-sidebar"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: "var(--vscode-foreground)" }}
|
||||
target="_blank">
|
||||
<h3 className="m-0">💡 Cline in the Right Sidebar</h3>
|
||||
<p className="m-0">
|
||||
Keep your files visible when chatting with Cline. Drag the Cline icon to the right sidebar panel for a better
|
||||
experience. <span className="text-link cursor-pointer">See how →</span>
|
||||
</p>
|
||||
|
||||
{/* Close button */}
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
data-testid="info-banner-close-button"
|
||||
onClick={handleClose}
|
||||
style={{ position: "absolute", top: "8px", right: "8px" }}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
return null
|
||||
return (
|
||||
<a
|
||||
className="bg-banner-background px-3 py-2 flex flex-col gap-1 shrink-0 mb-1 relative text-sm m-4 no-underline transition-colors hover:brightness-120"
|
||||
href="https://docs.cline.bot/features/customization/opening-cline-in-sidebar"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: "var(--vscode-foreground)" }}
|
||||
target="_blank">
|
||||
<h3 className="m-0">💡 Cline in the Right Sidebar</h3>
|
||||
<p className="m-0">
|
||||
Keep your files visible when chatting with Cline. Drag the Cline icon to the right sidebar panel for a better
|
||||
experience. <span className="text-link cursor-pointer">See how →</span>
|
||||
</p>
|
||||
|
||||
{/* Close button */}
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
data-testid="info-banner-close-button"
|
||||
onClick={handleClose}
|
||||
style={{ position: "absolute", top: "8px", right: "8px" }}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoBanner
|
||||
|
||||
@@ -61,7 +61,7 @@ const featuredModels = [
|
||||
label: "Free",
|
||||
},
|
||||
{
|
||||
id: "cline/code-supernova-1-million",
|
||||
id: "cline/code-supernova",
|
||||
description: "Stealth coding model with image support",
|
||||
label: "Free",
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ANTHROPIC_MIN_THINKING_BUDGET, anthropicModels, geminiDefaultModelId, geminiModels } from "@shared/api"
|
||||
import { anthropicModels, geminiDefaultModelId, geminiModels } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from "react"
|
||||
@@ -8,6 +8,7 @@ import { getModeSpecificFields } from "./utils/providerUtils"
|
||||
import { useApiConfigurationHandlers } from "./utils/useApiConfigurationHandlers"
|
||||
|
||||
// Constants
|
||||
const DEFAULT_MIN_VALID_TOKENS = 1024
|
||||
const MAX_PERCENTAGE = 0.8
|
||||
const THUMB_SIZE = 16
|
||||
|
||||
@@ -142,7 +143,7 @@ const ThinkingBudgetSlider = ({ maxBudget, currentMode }: ThinkingBudgetSliderPr
|
||||
|
||||
const handleToggleChange = (event: any) => {
|
||||
const isChecked = (event.target as HTMLInputElement).checked
|
||||
const newThinkingBudgetValue = isChecked ? ANTHROPIC_MIN_THINKING_BUDGET : 0
|
||||
const newThinkingBudgetValue = isChecked ? DEFAULT_MIN_VALID_TOKENS : 0
|
||||
setIsEnabled(isChecked)
|
||||
setLocalValue(newThinkingBudgetValue)
|
||||
|
||||
@@ -168,16 +169,16 @@ const ThinkingBudgetSlider = ({ maxBudget, currentMode }: ThinkingBudgetSliderPr
|
||||
</LabelContainer>
|
||||
<RangeInput
|
||||
$max={maxSliderValue}
|
||||
$min={ANTHROPIC_MIN_THINKING_BUDGET}
|
||||
$min={DEFAULT_MIN_VALID_TOKENS}
|
||||
$value={localValue}
|
||||
aria-describedby="thinking-budget-description"
|
||||
aria-label={`Thinking budget: ${localValue.toLocaleString()} tokens`}
|
||||
aria-valuemax={maxSliderValue}
|
||||
aria-valuemin={ANTHROPIC_MIN_THINKING_BUDGET}
|
||||
aria-valuemin={DEFAULT_MIN_VALID_TOKENS}
|
||||
aria-valuenow={localValue}
|
||||
id="thinking-budget-slider"
|
||||
max={maxSliderValue}
|
||||
min={ANTHROPIC_MIN_THINKING_BUDGET}
|
||||
min={DEFAULT_MIN_VALID_TOKENS}
|
||||
onChange={handleSliderChange}
|
||||
onMouseUp={handleSliderComplete}
|
||||
onTouchEnd={handleSliderComplete}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import platformConfigs from "./platform-configs.json"
|
||||
|
||||
export interface PlatformConfig {
|
||||
type: PlatformType
|
||||
messageEncoding: MessageEncoding
|
||||
showNavbar: boolean
|
||||
postMessage: PostMessageFunction
|
||||
@@ -11,24 +10,6 @@ export interface PlatformConfig {
|
||||
supportsTerminalMentions: boolean
|
||||
}
|
||||
|
||||
export enum PlatformType {
|
||||
VSCODE = 0,
|
||||
STANDALONE = 1,
|
||||
}
|
||||
|
||||
function stringToPlatformType(name: string): PlatformType {
|
||||
const mapping: Record<string, PlatformType> = {
|
||||
vscode: PlatformType.VSCODE,
|
||||
standalone: PlatformType.STANDALONE,
|
||||
}
|
||||
if (name in mapping) {
|
||||
return mapping[name]
|
||||
}
|
||||
console.error("Unknown platform:", name)
|
||||
// Default to VSCode for unknown types
|
||||
return PlatformType.VSCODE
|
||||
}
|
||||
|
||||
// Internal type for JSON structure (not exported)
|
||||
type PlatformConfigJson = {
|
||||
messageEncoding: "none" | "json"
|
||||
@@ -95,9 +76,7 @@ const selectedConfig = configs[__PLATFORM__]
|
||||
console.log("[PLATFORM_CONFIG] Build platform:", __PLATFORM__)
|
||||
|
||||
// Build the platform config with injected functions
|
||||
// Callers should use this in the situations where the react component is not available.
|
||||
export const PLATFORM_CONFIG: PlatformConfig = {
|
||||
type: stringToPlatformType(__PLATFORM__),
|
||||
messageEncoding: selectedConfig.messageEncoding,
|
||||
showNavbar: selectedConfig.showNavbar,
|
||||
postMessage: postMessageStrategies[selectedConfig.postMessageHandler],
|
||||
|
||||
Reference in New Issue
Block a user