mirror of
https://github.com/cline/cline.git
synced 2026-09-05 05:02:27 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3254a5099f | |||
| 4fa16e9c7a |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixed issue where telemetry warning popup was created for every new Cline window
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Prioritize active files in file context menu
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Migrate settingsButtonClicked to protobus
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Context menu is default to File option on start up
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
the response of the mcps is displayed with a collapsible which allows to focus on the model responses.
|
||||
Vendored
+1
-2
@@ -52,8 +52,7 @@
|
||||
"env": {
|
||||
"GRPC_TRACE": "all",
|
||||
"GRPC_VERBOSITY": "DEBUG",
|
||||
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules",
|
||||
"CLINE_DIR": "${userHome}/.cline-standalone"
|
||||
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules"
|
||||
},
|
||||
"program": "standalone.js"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.17.11]
|
||||
|
||||
- Add support for Gemini 2.5 Pro Preview 06-05 model to Vertex AI and Google Gemini providers
|
||||
|
||||
## [3.17.10]
|
||||
|
||||
- Add support for Qwen 3 series models with thinking mode options (Thanks @Jonny-china!)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
import { StreamingResponseHandler } from "./host-grpc-handler"
|
||||
import { handleUriServiceRequest, handleUriServiceStreamingRequest } from "./uri/index"
|
||||
import { handleWatchServiceRequest, handleWatchServiceStreamingRequest } from "./watch/index"
|
||||
|
||||
/**
|
||||
* Configuration for a host service handler
|
||||
*/
|
||||
export interface HostServiceHandlerConfig {
|
||||
requestHandler: (method: string, message: any) => Promise<any>
|
||||
streamingHandler: (
|
||||
method: string,
|
||||
message: any,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of host service names to their handler configurations
|
||||
*/
|
||||
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {
|
||||
"host.UriService": {
|
||||
requestHandler: handleUriServiceRequest,
|
||||
streamingHandler: handleUriServiceStreamingRequest,
|
||||
},
|
||||
"host.WatchService": {
|
||||
requestHandler: handleWatchServiceRequest,
|
||||
streamingHandler: handleWatchServiceStreamingRequest,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../host-grpc-service"
|
||||
import { StreamingResponseHandler } from "../host-grpc-handler"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create uri service registry
|
||||
const uriService = createServiceRegistry("uri")
|
||||
|
||||
// Export the method handler types and registration function
|
||||
export type UriMethodHandler = ServiceMethodHandler
|
||||
export type UriStreamingMethodHandler = StreamingMethodHandler
|
||||
export const registerMethod = uriService.registerMethod
|
||||
|
||||
// Export the request handlers
|
||||
export const handleUriServiceRequest = uriService.handleRequest
|
||||
export const handleUriServiceStreamingRequest = uriService.handleStreamingRequest
|
||||
export const isStreamingMethod = uriService.isStreamingMethod
|
||||
|
||||
// Register all uri methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,16 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { file } from "./file"
|
||||
import { joinPath } from "./joinPath"
|
||||
import { parse } from "./parse"
|
||||
|
||||
// Register all uri service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("file", file)
|
||||
registerMethod("joinPath", joinPath)
|
||||
registerMethod("parse", parse)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../host-grpc-service"
|
||||
import { StreamingResponseHandler } from "../host-grpc-handler"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create watch service registry
|
||||
const watchService = createServiceRegistry("watch")
|
||||
|
||||
// Export the method handler types and registration function
|
||||
export type WatchMethodHandler = ServiceMethodHandler
|
||||
export type WatchStreamingMethodHandler = StreamingMethodHandler
|
||||
export const registerMethod = watchService.registerMethod
|
||||
|
||||
// Export the request handlers
|
||||
export const handleWatchServiceRequest = watchService.handleRequest
|
||||
export const handleWatchServiceStreamingRequest = watchService.handleStreamingRequest
|
||||
export const isStreamingMethod = watchService.isStreamingMethod
|
||||
|
||||
// Register all watch methods
|
||||
registerAllMethods()
|
||||
@@ -0,0 +1,15 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"
|
||||
import { subscribeToFile } from "./subscribeToFile"
|
||||
|
||||
// Streaming methods for this service
|
||||
export const streamingMethods = ["subscribeToFile"]
|
||||
|
||||
// Register all watch service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("subscribeToFile", subscribeToFile, { isStreaming: true })
|
||||
}
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.17.11",
|
||||
"version": "3.17.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.17.11",
|
||||
"version": "3.17.10",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.12.4",
|
||||
|
||||
+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.17.11",
|
||||
"version": "3.17.10",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
|
||||
@@ -16,9 +16,6 @@ service McpService {
|
||||
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
|
||||
rpc refreshMcpMarketplace(EmptyRequest) returns (McpMarketplaceCatalog);
|
||||
rpc openMcpSettings(EmptyRequest) returns (Empty);
|
||||
|
||||
// Subscribe to MCP marketplace catalog updates
|
||||
rpc subscribeToMcpMarketplaceCatalog(EmptyRequest) returns (stream McpMarketplaceCatalog);
|
||||
}
|
||||
|
||||
message ToggleMcpServerRequest {
|
||||
|
||||
+8
-35
@@ -20,8 +20,6 @@ service ModelsService {
|
||||
rpc refreshOpenAiModels(OpenAiModelsRequest) returns (StringArray);
|
||||
// Refreshes and returns Requesty models
|
||||
rpc refreshRequestyModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Subscribe to OpenRouter models updates
|
||||
rpc subscribeToOpenRouterModels(EmptyRequest) returns (stream OpenRouterCompatibleModelInfo);
|
||||
}
|
||||
|
||||
// List of VS Code LM models
|
||||
@@ -37,42 +35,17 @@ message VsCodeLmModel {
|
||||
string id = 4;
|
||||
}
|
||||
|
||||
// Price tier for tiered pricing models
|
||||
message PriceTier {
|
||||
int32 token_limit = 1; // Upper limit (inclusive) of input tokens for this price
|
||||
double price = 2; // Price per million tokens for this tier
|
||||
}
|
||||
|
||||
// Thinking configuration for models that support thinking/reasoning
|
||||
message ThinkingConfig {
|
||||
optional int32 max_budget = 1; // Max allowed thinking budget tokens
|
||||
optional double output_price = 2; // Output price per million tokens when budget > 0
|
||||
repeated PriceTier output_price_tiers = 3; // Optional: Tiered output price when budget > 0
|
||||
}
|
||||
|
||||
// Model tier for tiered pricing structures
|
||||
message ModelTier {
|
||||
int32 context_window = 1;
|
||||
optional double input_price = 2;
|
||||
optional double output_price = 3;
|
||||
optional double cache_writes_price = 4;
|
||||
optional double cache_reads_price = 5;
|
||||
}
|
||||
|
||||
// For OpenRouterCompatibleModelInfo structure in OpenRouterModels
|
||||
message OpenRouterModelInfo {
|
||||
optional int32 max_tokens = 1;
|
||||
optional int32 context_window = 2;
|
||||
optional bool supports_images = 3;
|
||||
int32 max_tokens = 1;
|
||||
int32 context_window = 2;
|
||||
bool supports_images = 3;
|
||||
bool supports_prompt_cache = 4;
|
||||
optional double input_price = 5;
|
||||
optional double output_price = 6;
|
||||
optional double cache_writes_price = 7;
|
||||
optional double cache_reads_price = 8;
|
||||
optional string description = 9;
|
||||
optional ThinkingConfig thinking_config = 10;
|
||||
optional bool supports_global_endpoint = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
double input_price = 5;
|
||||
double output_price = 6;
|
||||
double cache_writes_price = 7;
|
||||
double cache_reads_price = 8;
|
||||
string description = 9;
|
||||
}
|
||||
|
||||
// Shared response message for model information
|
||||
|
||||
@@ -246,10 +246,4 @@ service UiService {
|
||||
|
||||
// Subscribe to partial message updates (streaming Cline messages as they're built)
|
||||
rpc subscribeToPartialMessage(EmptyRequest) returns (stream ClineMessage);
|
||||
|
||||
// Subscribe to theme change events
|
||||
rpc subscribeToTheme(EmptyRequest) returns (stream String);
|
||||
|
||||
// Initialize webview when it launches
|
||||
rpc initializeWebview(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
+104
-15
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import axios from "axios"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
|
||||
import fs from "fs/promises"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
@@ -12,8 +13,12 @@ import { EmptyRequest } from "@shared/proto/common"
|
||||
import { buildApiHandler } from "@api/index"
|
||||
import { cleanupLegacyCheckpoints } from "@integrations/checkpoints/CheckpointMigration"
|
||||
import { downloadTask } from "@integrations/misc/export-markdown"
|
||||
import { fetchOpenGraphData } from "@integrations/misc/link-preview"
|
||||
import { handleFileServiceRequest } from "./file"
|
||||
import { getTheme } from "@integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "@integrations/workspace/WorkspaceTracker"
|
||||
import { ClineAccountService } from "@services/account/ClineAccountService"
|
||||
import { BrowserSession } from "@services/browser/BrowserSession"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
@@ -21,31 +26,39 @@ import { ChatContent } from "@shared/ChatContent"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { ExtensionMessage, ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { McpMarketplaceCatalog } from "@shared/mcp"
|
||||
import { McpDownloadResponse, McpMarketplaceCatalog, McpServer } from "@shared/mcp"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { WebviewMessage } from "@shared/WebviewMessage"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { getWorkingState } from "@utils/git"
|
||||
import { extractCommitMessage } from "@integrations/git/commit-message-generator"
|
||||
import { ensureMcpServersDirectoryExists, ensureSettingsDirectoryExists, GlobalFileNames } from "../storage/disk"
|
||||
import { getTotalTasksSize } from "@utils/storage"
|
||||
import {
|
||||
ensureMcpServersDirectoryExists,
|
||||
ensureSettingsDirectoryExists,
|
||||
GlobalFileNames,
|
||||
ensureWorkflowsDirectoryExists,
|
||||
} from "../storage/disk"
|
||||
import {
|
||||
getAllExtensionState,
|
||||
getGlobalState,
|
||||
getSecret,
|
||||
getWorkspaceState,
|
||||
resetExtensionState,
|
||||
storeSecret,
|
||||
updateApiConfiguration,
|
||||
updateGlobalState,
|
||||
updateWorkspaceState,
|
||||
} from "../storage/state"
|
||||
import { Task } from "../task"
|
||||
import { Task, cwd } from "../task"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { sendStateUpdate } from "./state/subscribeToState"
|
||||
import { sendAddToInputEvent } from "./ui/subscribeToAddToInput"
|
||||
import { sendAuthCallbackEvent } from "./account/subscribeToAuthCallback"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { sendOpenRouterModelsEvent } from "./models/subscribeToOpenRouterModels"
|
||||
import { OpenRouterCompatibleModelInfo } from "@/shared/proto/models"
|
||||
import { sendChatButtonClickedEvent } from "./ui/subscribeToChatButtonClicked"
|
||||
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
|
||||
import { refreshExternalRulesToggles } from "@core/context/instructions/user-instructions/external-rules"
|
||||
import { refreshWorkflowToggles } from "@core/context/instructions/user-instructions/workflows"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -207,6 +220,71 @@ export class Controller {
|
||||
await this.setUserInfo(message.user || undefined)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "webviewDidLaunch":
|
||||
this.postStateToWebview()
|
||||
this.workspaceTracker?.populateFilePaths() // don't await
|
||||
getTheme().then((theme) =>
|
||||
this.postMessageToWebview({
|
||||
type: "theme",
|
||||
text: JSON.stringify(theme),
|
||||
}),
|
||||
)
|
||||
// post last cached models in case the call to endpoint fails
|
||||
this.readOpenRouterModels().then((openRouterModels) => {
|
||||
if (openRouterModels) {
|
||||
this.postMessageToWebview({
|
||||
type: "openRouterModels",
|
||||
openRouterModels,
|
||||
})
|
||||
}
|
||||
})
|
||||
// gui relies on model info to be up-to-date to provide the most accurate pricing, so we need to fetch the latest details on launch.
|
||||
// we do this for all users since many users switch between api providers and if they were to switch back to openrouter it would be showing outdated model info if we hadn't retrieved the latest at this point
|
||||
// (see normalizeApiConfiguration > openrouter)
|
||||
// Prefetch marketplace and OpenRouter models
|
||||
|
||||
getGlobalState(this.context, "mcpMarketplaceCatalog").then((mcpMarketplaceCatalog) => {
|
||||
if (mcpMarketplaceCatalog) {
|
||||
this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: mcpMarketplaceCatalog as McpMarketplaceCatalog,
|
||||
})
|
||||
}
|
||||
})
|
||||
this.silentlyRefreshMcpMarketplace()
|
||||
handleModelsServiceRequest(this, "refreshOpenRouterModels", EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
// update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
if (apiConfiguration.openRouterModelId && response.models[apiConfiguration.openRouterModelId]) {
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"openRouterModelInfo",
|
||||
response.models[apiConfiguration.openRouterModelId],
|
||||
)
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize telemetry service with user's current setting
|
||||
this.getStateToPostToWebview().then((state) => {
|
||||
const { telemetrySetting } = state
|
||||
const isOptedIn = telemetrySetting !== "disabled"
|
||||
telemetryService.updateTelemetryState(isOptedIn)
|
||||
})
|
||||
break
|
||||
case "newTask":
|
||||
// Code that should run in response to the hello message command
|
||||
//vscode.window.showInformationMessage(message.text!)
|
||||
|
||||
// Send a message to our webview.
|
||||
// You can send any JSON serializable data.
|
||||
// Could also do this in extension .ts
|
||||
//this.postMessageToWebview({ type: "text", text: `Extension: ${Date.now()}` })
|
||||
// initializing new instance of Cline will make sure that any agentically running promises in old instance don't affect our new task. this essentially creates a fresh slate for the new task
|
||||
await this.initTask(message.text, message.images, message.files)
|
||||
break
|
||||
case "apiConfiguration":
|
||||
if (message.apiConfiguration) {
|
||||
await updateApiConfiguration(this.context, message.apiConfiguration)
|
||||
@@ -317,10 +395,6 @@ export class Controller {
|
||||
await updateGlobalState(this.context, "mcpMarketplaceEnabled", message.mcpMarketplaceEnabled)
|
||||
}
|
||||
|
||||
if (typeof message.mcpResponsesCollapsed === "boolean") {
|
||||
await updateGlobalState(this.context, "mcpResponsesCollapsed", message.mcpResponsesCollapsed)
|
||||
}
|
||||
|
||||
// chat settings (including preferredLanguage and openAIReasoningEffort)
|
||||
if (message.chatSettings) {
|
||||
await updateGlobalState(this.context, "chatSettings", message.chatSettings)
|
||||
@@ -685,6 +759,10 @@ export class Controller {
|
||||
console.error("Failed to fetch MCP marketplace:", error)
|
||||
if (!silent) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
error: errorMessage,
|
||||
})
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
}
|
||||
return undefined
|
||||
@@ -730,7 +808,10 @@ export class Controller {
|
||||
try {
|
||||
const catalog = await this.fetchMcpMarketplaceFromApi(true)
|
||||
if (catalog) {
|
||||
await sendMcpMarketplaceCatalogEvent(catalog)
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: catalog,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to silently refresh MCP marketplace:", error)
|
||||
@@ -758,17 +839,27 @@ export class Controller {
|
||||
| McpMarketplaceCatalog
|
||||
| undefined
|
||||
if (!forceRefresh && cachedCatalog?.items) {
|
||||
await sendMcpMarketplaceCatalogEvent(cachedCatalog)
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: cachedCatalog,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const catalog = await this.fetchMcpMarketplaceFromApi(false)
|
||||
if (catalog) {
|
||||
await sendMcpMarketplaceCatalogEvent(catalog)
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: catalog,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to handle cached MCP marketplace:", error)
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
error: errorMessage,
|
||||
})
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
}
|
||||
}
|
||||
@@ -1093,7 +1184,6 @@ export class Controller {
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
isNewUser,
|
||||
mcpResponsesCollapsed,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
const localClineRulesToggles =
|
||||
@@ -1139,7 +1229,6 @@ export class Controller {
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
isNewUser,
|
||||
mcpResponsesCollapsed,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { McpMarketplaceCatalog } from "@shared/proto/mcp"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
|
||||
// Keep track of active subscriptions
|
||||
const activeMcpMarketplaceSubscriptions = new Set<StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to MCP marketplace catalog updates
|
||||
* @param controller The controller instance
|
||||
* @param request The empty request
|
||||
* @param responseStream The streaming response handler
|
||||
* @param requestId The ID of the request (passed by the gRPC handler)
|
||||
*/
|
||||
export async function subscribeToMcpMarketplaceCatalog(
|
||||
controller: Controller,
|
||||
request: EmptyRequest,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
// Add this subscription to the active subscriptions
|
||||
activeMcpMarketplaceSubscriptions.add(responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activeMcpMarketplaceSubscriptions.delete(responseStream)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
if (requestId) {
|
||||
getRequestRegistry().registerRequest(requestId, cleanup, { type: "mcp_marketplace_subscription" }, responseStream)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an MCP marketplace catalog event to all active subscribers
|
||||
*/
|
||||
export async function sendMcpMarketplaceCatalogEvent(catalog: McpMarketplaceCatalog): Promise<void> {
|
||||
// Send the event to all active subscribers
|
||||
const promises = Array.from(activeMcpMarketplaceSubscriptions).map(async (responseStream) => {
|
||||
try {
|
||||
await responseStream(
|
||||
catalog,
|
||||
false, // Not the last message
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error sending MCP marketplace catalog event:", error)
|
||||
// Remove the subscription if there was an error
|
||||
activeMcpMarketplaceSubscriptions.delete(responseStream)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
@@ -15,11 +15,11 @@ import { GlobalFileNames } from "@core/storage/disk"
|
||||
*/
|
||||
export async function refreshOpenRouterModels(
|
||||
controller: Controller,
|
||||
_request: EmptyRequest,
|
||||
request: EmptyRequest,
|
||||
): Promise<OpenRouterCompatibleModelInfo> {
|
||||
const openRouterModelsFilePath = path.join(await ensureCacheDirectoryExists(controller), GlobalFileNames.openRouterModels)
|
||||
|
||||
let models: Record<string, OpenRouterModelInfo> = {}
|
||||
let models: Record<string, Partial<OpenRouterModelInfo>> = {}
|
||||
try {
|
||||
const response = await axios.get("https://openrouter.ai/api/v1/models")
|
||||
|
||||
@@ -32,20 +32,15 @@ export async function refreshOpenRouterModels(
|
||||
return undefined
|
||||
}
|
||||
for (const rawModel of rawModels) {
|
||||
const modelInfo = OpenRouterModelInfo.create({
|
||||
maxTokens: rawModel.top_provider?.max_completion_tokens ?? 0,
|
||||
contextWindow: rawModel.context_length ?? 0,
|
||||
supportsImages: rawModel.architecture?.modality?.includes("image") ?? false,
|
||||
const modelInfo: Partial<OpenRouterModelInfo> = {
|
||||
maxTokens: rawModel.top_provider?.max_completion_tokens,
|
||||
contextWindow: rawModel.context_length,
|
||||
supportsImages: rawModel.architecture?.modality?.includes("image"),
|
||||
supportsPromptCache: false,
|
||||
inputPrice: parsePrice(rawModel.pricing?.prompt) ?? 0,
|
||||
outputPrice: parsePrice(rawModel.pricing?.completion) ?? 0,
|
||||
cacheWritesPrice: 0,
|
||||
cacheReadsPrice: 0,
|
||||
description: rawModel.description ?? "",
|
||||
thinkingConfig: rawModel.thinking_config ?? undefined,
|
||||
supportsGlobalEndpoint: rawModel.supports_global_endpoint ?? undefined,
|
||||
tiers: rawModel.tiers ?? [],
|
||||
})
|
||||
inputPrice: parsePrice(rawModel.pricing?.prompt),
|
||||
outputPrice: parsePrice(rawModel.pricing?.completion),
|
||||
description: rawModel.description,
|
||||
}
|
||||
|
||||
switch (rawModel.id) {
|
||||
case "anthropic/claude-sonnet-4":
|
||||
@@ -123,7 +118,7 @@ export async function refreshOpenRouterModels(
|
||||
console.error("Invalid response from OpenRouter API")
|
||||
}
|
||||
await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
|
||||
console.log("OpenRouter models fetched and saved", JSON.stringify(models).slice(0, 300))
|
||||
console.log("OpenRouter models fetched and saved", models)
|
||||
} catch (error) {
|
||||
console.error("Error fetching OpenRouter models:", error)
|
||||
|
||||
@@ -134,13 +129,30 @@ export async function refreshOpenRouterModels(
|
||||
}
|
||||
}
|
||||
|
||||
return OpenRouterCompatibleModelInfo.create({ models })
|
||||
// Convert the Record<string, Partial<OpenRouterModelInfo>> to Record<string, OpenRouterModelInfo>
|
||||
// by filling in any missing required fields with defaults
|
||||
const typedModels: Record<string, OpenRouterModelInfo> = {}
|
||||
for (const [key, model] of Object.entries(models)) {
|
||||
typedModels[key] = {
|
||||
maxTokens: model.maxTokens ?? 0,
|
||||
contextWindow: model.contextWindow ?? 0,
|
||||
supportsImages: model.supportsImages ?? false,
|
||||
supportsPromptCache: model.supportsPromptCache ?? false,
|
||||
inputPrice: model.inputPrice ?? 0,
|
||||
outputPrice: model.outputPrice ?? 0,
|
||||
cacheWritesPrice: model.cacheWritesPrice ?? 0,
|
||||
cacheReadsPrice: model.cacheReadsPrice ?? 0,
|
||||
description: model.description ?? "",
|
||||
}
|
||||
}
|
||||
|
||||
return OpenRouterCompatibleModelInfo.create({ models: typedModels })
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads cached OpenRouter models from disk
|
||||
*/
|
||||
async function readOpenRouterModels(controller: Controller): Promise<Record<string, OpenRouterModelInfo> | undefined> {
|
||||
async function readOpenRouterModels(controller: Controller): Promise<Record<string, Partial<OpenRouterModelInfo>> | undefined> {
|
||||
const openRouterModelsFilePath = path.join(await ensureCacheDirectoryExists(controller), GlobalFileNames.openRouterModels)
|
||||
const fileExists = await fileExistsAtPath(openRouterModelsFilePath)
|
||||
if (fileExists) {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { OpenRouterCompatibleModelInfo } from "@shared/proto/models"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
|
||||
// Keep track of active OpenRouter models subscriptions
|
||||
const activeOpenRouterModelsSubscriptions = new Set<StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to OpenRouter models events
|
||||
* @param controller The controller instance
|
||||
* @param request The empty request
|
||||
* @param responseStream The streaming response handler
|
||||
* @param requestId The ID of the request (passed by the gRPC handler)
|
||||
*/
|
||||
export async function subscribeToOpenRouterModels(
|
||||
controller: Controller,
|
||||
request: EmptyRequest,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
console.log("[DEBUG] set up OpenRouter models subscription")
|
||||
|
||||
// Add this subscription to the active subscriptions
|
||||
activeOpenRouterModelsSubscriptions.add(responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activeOpenRouterModelsSubscriptions.delete(responseStream)
|
||||
console.log("[DEBUG] Cleaned up OpenRouter models subscription")
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
if (requestId) {
|
||||
getRequestRegistry().registerRequest(requestId, cleanup, { type: "openRouterModels_subscription" }, responseStream)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an OpenRouter models event to all active subscribers
|
||||
* @param models The OpenRouter models to send
|
||||
*/
|
||||
export async function sendOpenRouterModelsEvent(models: OpenRouterCompatibleModelInfo): Promise<void> {
|
||||
// Send the event to all active subscribers
|
||||
const promises = Array.from(activeOpenRouterModelsSubscriptions).map(async (responseStream) => {
|
||||
try {
|
||||
await responseStream(
|
||||
models,
|
||||
false, // Not the last message
|
||||
)
|
||||
console.log("[DEBUG] sending OpenRouter models event")
|
||||
} catch (error) {
|
||||
console.error("Error sending OpenRouter models event:", error)
|
||||
// Remove the subscription if there was an error
|
||||
activeOpenRouterModelsSubscriptions.delete(responseStream)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import type { Controller } from "../index"
|
||||
import { EmptyRequest, Empty } from "@shared/proto/common"
|
||||
import { handleModelsServiceRequest } from "../models"
|
||||
import { getAllExtensionState, getGlobalState, updateGlobalState } from "../../storage/state"
|
||||
import { sendOpenRouterModelsEvent } from "../models/subscribeToOpenRouterModels"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "../mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
|
||||
import { OpenRouterCompatibleModelInfo } from "@/shared/proto/models"
|
||||
import { McpMarketplaceCatalog } from "@shared/mcp"
|
||||
|
||||
/**
|
||||
* Initialize webview when it launches
|
||||
* @param controller The controller instance
|
||||
* @param request The empty request
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function initializeWebview(controller: Controller, request: EmptyRequest): Promise<Empty> {
|
||||
try {
|
||||
// Populate file paths for workspace tracker (don't await)
|
||||
controller.workspaceTracker?.populateFilePaths()
|
||||
|
||||
// Post last cached models in case the call to endpoint fails
|
||||
controller.readOpenRouterModels().then((openRouterModels) => {
|
||||
if (openRouterModels) {
|
||||
sendOpenRouterModelsEvent(OpenRouterCompatibleModelInfo.create({ models: openRouterModels }))
|
||||
}
|
||||
})
|
||||
|
||||
// Refresh OpenRouter models from API
|
||||
handleModelsServiceRequest(controller, "refreshOpenRouterModels", EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
// Update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const { apiConfiguration } = await getAllExtensionState(controller.context)
|
||||
if (apiConfiguration.openRouterModelId && response.models[apiConfiguration.openRouterModelId]) {
|
||||
await updateGlobalState(
|
||||
controller.context,
|
||||
"openRouterModelInfo",
|
||||
response.models[apiConfiguration.openRouterModelId],
|
||||
)
|
||||
await controller.postStateToWebview()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// GUI relies on model info to be up-to-date to provide the most accurate pricing, so we need to fetch the latest details on launch.
|
||||
// We do this for all users since many users switch between api providers and if they were to switch back to openrouter it would be showing outdated model info if we hadn't retrieved the latest at this point
|
||||
// (see normalizeApiConfiguration > openrouter)
|
||||
// Prefetch marketplace and OpenRouter models
|
||||
|
||||
// Send cached MCP marketplace catalog if available
|
||||
getGlobalState(controller.context, "mcpMarketplaceCatalog").then((mcpMarketplaceCatalog) => {
|
||||
if (mcpMarketplaceCatalog) {
|
||||
sendMcpMarketplaceCatalogEvent(mcpMarketplaceCatalog as McpMarketplaceCatalog)
|
||||
}
|
||||
})
|
||||
|
||||
// Silently refresh MCP marketplace catalog
|
||||
controller.silentlyRefreshMcpMarketplace()
|
||||
|
||||
// Initialize telemetry service with user's current setting
|
||||
controller.getStateToPostToWebview().then((state) => {
|
||||
const { telemetrySetting } = state
|
||||
const isOptedIn = telemetrySetting !== "disabled"
|
||||
telemetryService.updateTelemetryState(isOptedIn)
|
||||
})
|
||||
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize webview:", error)
|
||||
// Return empty response even on error to not break the frontend
|
||||
return Empty.create({})
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { EmptyRequest, String } from "@shared/proto/common"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
import { getTheme } from "@integrations/theme/getTheme"
|
||||
|
||||
// Keep track of active theme subscriptions
|
||||
const activeThemeSubscriptions = new Set<StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to theme change events
|
||||
* @param controller The controller instance
|
||||
* @param request The empty request
|
||||
* @param responseStream The streaming response handler
|
||||
* @param requestId The ID of the request (passed by the gRPC handler)
|
||||
*/
|
||||
export async function subscribeToTheme(
|
||||
controller: Controller,
|
||||
request: EmptyRequest,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
// Add this subscription to the active subscriptions
|
||||
activeThemeSubscriptions.add(responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activeThemeSubscriptions.delete(responseStream)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
if (requestId) {
|
||||
getRequestRegistry().registerRequest(requestId, cleanup, { type: "theme_subscription" }, responseStream)
|
||||
}
|
||||
|
||||
// Send the current theme immediately upon subscription
|
||||
const theme = await getTheme()
|
||||
if (theme) {
|
||||
try {
|
||||
const themeEvent = String.create({
|
||||
value: JSON.stringify(theme),
|
||||
})
|
||||
await responseStream(
|
||||
themeEvent,
|
||||
false, // Not the last message
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error sending initial theme:", error)
|
||||
activeThemeSubscriptions.delete(responseStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a theme event to all active subscribers
|
||||
* @param themeJson The JSON-stringified theme data
|
||||
*/
|
||||
export async function sendThemeEvent(themeJson: string): Promise<void> {
|
||||
// Send the event to all active subscribers
|
||||
const promises = Array.from(activeThemeSubscriptions).map(async (responseStream) => {
|
||||
try {
|
||||
const event = String.create({
|
||||
value: themeJson,
|
||||
})
|
||||
await responseStream(
|
||||
event,
|
||||
false, // Not the last message
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error sending theme event:", error)
|
||||
// Remove the subscription if there was an error
|
||||
activeThemeSubscriptions.delete(responseStream)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
@@ -91,7 +91,6 @@ export type GlobalStateKey =
|
||||
| "favoritedModelIds"
|
||||
| "requestTimeoutMs"
|
||||
| "shellIntegrationTimeout"
|
||||
| "mcpResponsesCollapsed"
|
||||
| "terminalReuseEnabled"
|
||||
| "isNewUser"
|
||||
|
||||
|
||||
@@ -164,7 +164,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
shellIntegrationTimeout,
|
||||
enableCheckpointsSettingRaw,
|
||||
mcpMarketplaceEnabledRaw,
|
||||
mcpResponsesCollapsedRaw,
|
||||
globalWorkflowToggles,
|
||||
terminalReuseEnabled,
|
||||
] = await Promise.all([
|
||||
@@ -256,7 +255,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
|
||||
getGlobalState(context, "enableCheckpointsSetting") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "mcpMarketplaceEnabled") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "mcpResponsesCollapsed") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "globalWorkflowToggles") as Promise<ClineRulesToggles | undefined>,
|
||||
getGlobalState(context, "terminalReuseEnabled") as Promise<boolean | undefined>,
|
||||
])
|
||||
@@ -279,7 +277,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
|
||||
const mcpMarketplaceEnabled = await migrateMcpMarketplaceEnableSetting(mcpMarketplaceEnabledRaw)
|
||||
const enableCheckpointsSetting = await migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw)
|
||||
const mcpResponsesCollapsed = mcpResponsesCollapsedRaw ?? false
|
||||
|
||||
// Plan/Act separate models setting is a boolean indicating whether the user wants to use different models for plan and act. Existing users expect this to be enabled, while we want new users to opt in to this being disabled by default.
|
||||
// On win11 state sometimes initializes as empty string instead of undefined
|
||||
@@ -390,7 +387,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
mcpMarketplaceEnabled: mcpMarketplaceEnabled,
|
||||
mcpResponsesCollapsed: mcpResponsesCollapsed,
|
||||
telemetrySetting: telemetrySetting || "unset",
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting: enableCheckpointsSetting,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { findLast } from "@shared/array"
|
||||
import { readFile } from "fs/promises"
|
||||
import path from "node:path"
|
||||
import { WebviewProviderType } from "@/shared/webview/types"
|
||||
import { sendThemeEvent } from "@core/controller/ui/subscribeToTheme"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -140,11 +139,11 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
async (e) => {
|
||||
if (e && e.affectsConfiguration("workbench.colorTheme")) {
|
||||
// Send theme update via gRPC subscription
|
||||
const theme = await getTheme()
|
||||
if (theme) {
|
||||
await sendThemeEvent(JSON.stringify(theme))
|
||||
}
|
||||
// Sends latest theme name to webview
|
||||
await this.controller.postMessageToWebview({
|
||||
type: "theme",
|
||||
text: JSON.stringify(await getTheme()),
|
||||
})
|
||||
}
|
||||
if (e && e.affectsConfiguration("cline.mcpMarketplace.enabled")) {
|
||||
// Update state when marketplace tab setting changes
|
||||
|
||||
@@ -10,14 +10,6 @@ class WorkspaceTracker {
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private filePaths: Set<string> = new Set()
|
||||
|
||||
private get activeFiles() {
|
||||
return new Set(
|
||||
vscode.window.tabGroups.activeTabGroup.tabs
|
||||
.filter((tab) => tab.input instanceof vscode.TabInputText)
|
||||
.map((tab) => (tab.input as vscode.TabInputText).uri.fsPath),
|
||||
)
|
||||
}
|
||||
|
||||
constructor(private readonly postMessageToWebview: (message: ExtensionMessage) => Promise<void>) {
|
||||
this.postMessageToWebview = postMessageToWebview
|
||||
this.registerListeners()
|
||||
@@ -44,9 +36,6 @@ class WorkspaceTracker {
|
||||
// Listen for file renaming
|
||||
this.disposables.push(vscode.workspace.onDidRenameFiles(this.onFilesRenamed.bind(this)))
|
||||
|
||||
// Listen for tab groups changes
|
||||
this.disposables.push(vscode.window.tabGroups.onDidChangeTabs(this.workspaceDidUpdate.bind(this)))
|
||||
|
||||
/*
|
||||
An event that is emitted when a workspace folder is added or removed.
|
||||
**Note:** this event will not fire if the first workspace folder is added, removed or changed,
|
||||
@@ -97,7 +86,7 @@ class WorkspaceTracker {
|
||||
}
|
||||
this.postMessageToWebview({
|
||||
type: "workspaceUpdated",
|
||||
filePaths: Array.from(new Set([...this.activeFiles, ...this.filePaths])).map((file) => {
|
||||
filePaths: Array.from(this.filePaths).map((file) => {
|
||||
const relativePath = path.relative(cwd, file).toPosix()
|
||||
return file.endsWith("/") ? relativePath + "/" : relativePath
|
||||
}),
|
||||
|
||||
@@ -138,20 +138,17 @@ class TelemetryService {
|
||||
if (globalTelemetryEnabled) {
|
||||
this.telemetryEnabled = didUserOptIn
|
||||
} else {
|
||||
// Only show warning if user has opted in to Cline telemetry but VS Code telemetry is disabled
|
||||
if (didUserOptIn) {
|
||||
void vscode.window
|
||||
.showWarningMessage(
|
||||
"Anonymous Cline error and usage reporting is enabled, but VSCode telemetry is disabled. To enable error and usage reporting for this extension, enable VSCode telemetry in settings.",
|
||||
"Open Settings",
|
||||
)
|
||||
.then((selection) => {
|
||||
if (selection === "Open Settings") {
|
||||
void vscode.commands.executeCommand("workbench.action.openSettings", "telemetry.telemetryLevel")
|
||||
}
|
||||
})
|
||||
}
|
||||
this.telemetryEnabled = false
|
||||
// Show warning to user that global telemetry is disabled
|
||||
void vscode.window
|
||||
.showWarningMessage(
|
||||
"VSCode telemetry is disabled. To enable telemetry for this extension, first enable VSCode telemetry in settings.",
|
||||
"Open Settings",
|
||||
)
|
||||
.then((selection) => {
|
||||
if (selection === "Open Settings") {
|
||||
void vscode.commands.executeCommand("workbench.action.openSettings", "telemetry.telemetryLevel")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Update PostHog client state based on telemetry preference
|
||||
|
||||
@@ -19,11 +19,14 @@ export interface ExtensionMessage {
|
||||
| "selectedImages"
|
||||
| "ollamaModels"
|
||||
| "lmStudioModels"
|
||||
| "theme"
|
||||
| "workspaceUpdated"
|
||||
| "openRouterModels"
|
||||
| "openAiModels"
|
||||
| "requestyModels"
|
||||
| "mcpServers"
|
||||
| "relinquishControl"
|
||||
| "mcpMarketplaceCatalog"
|
||||
| "mcpDownloadDetails"
|
||||
| "commitSearchResults"
|
||||
| "openGraphData"
|
||||
@@ -42,6 +45,7 @@ export interface ExtensionMessage {
|
||||
lmStudioModels?: string[]
|
||||
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
|
||||
filePaths?: string[]
|
||||
openRouterModels?: Record<string, ModelInfo>
|
||||
openAiModels?: string[]
|
||||
requestyModels?: Record<string, ModelInfo>
|
||||
mcpServers?: McpServer[]
|
||||
@@ -123,7 +127,6 @@ export interface ExtensionState {
|
||||
globalWorkflowToggles: ClineRulesToggles
|
||||
localCursorRulesToggles: ClineRulesToggles
|
||||
localWindsurfRulesToggles: ClineRulesToggles
|
||||
mcpResponsesCollapsed?: boolean
|
||||
}
|
||||
|
||||
export interface ClineMessage {
|
||||
|
||||
@@ -9,6 +9,8 @@ import { McpViewTab } from "./mcp"
|
||||
export interface WebviewMessage {
|
||||
type:
|
||||
| "apiConfiguration"
|
||||
| "webviewDidLaunch"
|
||||
| "newTask"
|
||||
| "condense"
|
||||
| "reportBug"
|
||||
| "requestVsCodeLmModels"
|
||||
@@ -51,7 +53,6 @@ export interface WebviewMessage {
|
||||
planActSeparateModelsSetting?: boolean
|
||||
enableCheckpointsSetting?: boolean
|
||||
mcpMarketplaceEnabled?: boolean
|
||||
mcpResponsesCollapsed?: boolean
|
||||
telemetrySetting?: TelemetrySetting
|
||||
customInstructionsSetting?: string
|
||||
mentionsRequestId?: string
|
||||
|
||||
@@ -575,30 +575,6 @@ export const vertexModels = {
|
||||
},
|
||||
],
|
||||
},
|
||||
"gemini-2.5-pro-preview-06-05": {
|
||||
maxTokens: 65536,
|
||||
contextWindow: 1_048_576,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsGlobalEndpoint: true,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 15,
|
||||
cacheReadsPrice: 0.31,
|
||||
tiers: [
|
||||
{
|
||||
contextWindow: 200000,
|
||||
inputPrice: 1.25,
|
||||
outputPrice: 10,
|
||||
cacheReadsPrice: 0.31,
|
||||
},
|
||||
{
|
||||
contextWindow: Infinity,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 15,
|
||||
cacheReadsPrice: 0.625,
|
||||
},
|
||||
],
|
||||
},
|
||||
"gemini-2.5-flash-preview-04-17": {
|
||||
maxTokens: 65536,
|
||||
contextWindow: 1_048_576,
|
||||
@@ -743,30 +719,6 @@ export const geminiModels = {
|
||||
},
|
||||
],
|
||||
},
|
||||
"gemini-2.5-pro-preview-06-05": {
|
||||
maxTokens: 65536,
|
||||
contextWindow: 1_048_576,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsGlobalEndpoint: true,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 15,
|
||||
cacheReadsPrice: 0.31,
|
||||
tiers: [
|
||||
{
|
||||
contextWindow: 200000,
|
||||
inputPrice: 1.25,
|
||||
outputPrice: 10,
|
||||
cacheReadsPrice: 0.31,
|
||||
},
|
||||
{
|
||||
contextWindow: Infinity,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 15,
|
||||
cacheReadsPrice: 0.625,
|
||||
},
|
||||
],
|
||||
},
|
||||
"gemini-2.5-flash-preview-05-20": {
|
||||
maxTokens: 65536,
|
||||
contextWindow: 1_048_576,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by proto/build-proto.js
|
||||
|
||||
import { createGrpcClient } from "./host-grpc-client-base"
|
||||
import { UriServiceDefinition } from "@shared/proto/host/uri"
|
||||
import { WatchServiceDefinition } from "@shared/proto/host/watch"
|
||||
|
||||
const UriServiceClient = createGrpcClient(UriServiceDefinition)
|
||||
const WatchServiceClient = createGrpcClient(WatchServiceDefinition)
|
||||
|
||||
export { UriServiceClient, WatchServiceClient }
|
||||
@@ -32,11 +32,12 @@ function main() {
|
||||
const host = "127.0.0.1:50051"
|
||||
server.bindAsync(host, grpc.ServerCredentials.createInsecure(), (err) => {
|
||||
if (err) {
|
||||
log(`Error: Failed to bind to ${host}, port may be unavailable. ${err.message}`)
|
||||
log(`Error: Failed to bind to ${host}, port may be unavailable ${err.message}`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
server.start()
|
||||
log(`gRPC server listening on ${host}`)
|
||||
}
|
||||
server.start()
|
||||
log(`gRPC server listening on ${host}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const outputChannel: vscode.OutputChannel = {
|
||||
}
|
||||
|
||||
function postMessage(message: ExtensionMessage): Promise<boolean> {
|
||||
log("postMessage stub called:", JSON.stringify(message).slice(0, 200))
|
||||
log("postMessage stub called:", message)
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import { URI } from "vscode-uri"
|
||||
|
||||
import path from "path"
|
||||
import { mkdirSync } from "fs"
|
||||
import type { Extension, ExtensionContext } from "vscode"
|
||||
import { ExtensionKind, ExtensionMode } from "vscode"
|
||||
import { outputChannel, postMessage } from "./vscode-context-stubs"
|
||||
import { EnvironmentVariableCollection, MementoStore, readJson, SecretStore } from "./vscode-context-utils"
|
||||
import { log } from "./utils"
|
||||
|
||||
if (!process.env.CLINE_DIR) {
|
||||
console.warn("Environment variable CLINE_DIR was not set.")
|
||||
process.exit(1)
|
||||
}
|
||||
const DATA_DIR = path.join(process.env.CLINE_DIR, "data")
|
||||
mkdirSync(DATA_DIR, { recursive: true })
|
||||
log("Using settings dir:", DATA_DIR)
|
||||
|
||||
const EXTENSION_DIR = path.join(process.env.CLINE_DIR, "core")
|
||||
const DATA_DIR = process.env.DATA_DIR ?? "."
|
||||
const EXTENSION_DIR = process.env.EXTENSION_DIR ?? "."
|
||||
const EXTENSION_MODE = process.env.IS_DEV === "true" ? ExtensionMode.Development : ExtensionMode.Production
|
||||
|
||||
const extension: Extension<void> = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "cline-standalone",
|
||||
"name": "Cline standalone",
|
||||
"version": "1.0.0",
|
||||
"main": "standalone.js",
|
||||
"dependencies": {
|
||||
|
||||
@@ -53,7 +53,6 @@ vscode.window = {
|
||||
tabGroups: {
|
||||
all: [],
|
||||
close: async () => {},
|
||||
onDidChangeTabs: createStub("vscode.env.tabGroups.onDidChangeTabs"),
|
||||
},
|
||||
withProgress: async (_options, task) => {
|
||||
console.log("Stubbed withProgress")
|
||||
|
||||
@@ -816,7 +816,7 @@ vscode.TextDocumentSaveReason = { Manual: 0, AfterDelay: 0, FocusOut: 0 }
|
||||
vscode.workspace = {}
|
||||
vscode.workspace.fs = createStub("vscode.workspace.fs")
|
||||
vscode.workspace.rootPath = createStub("vscode.workspace.rootPath")
|
||||
vscode.workspace.workspaceFolders = []
|
||||
vscode.workspace.workspaceFolders = createStub("vscode.workspace.workspaceFolders")
|
||||
vscode.workspace.name = createStub("vscode.workspace.name")
|
||||
vscode.workspace.workspaceFile = createStub("vscode.workspace.workspaceFile")
|
||||
vscode.workspace.onDidChangeWorkspaceFolders = createStub("vscode.workspace.onDidChangeWorkspaceFolders")
|
||||
@@ -894,22 +894,10 @@ vscode.workspace.onWillDeleteFiles = createStub("vscode.workspace.onWillDeleteFi
|
||||
vscode.workspace.onDidDeleteFiles = createStub("vscode.workspace.onDidDeleteFiles")
|
||||
vscode.workspace.onWillRenameFiles = createStub("vscode.workspace.onWillRenameFiles")
|
||||
vscode.workspace.onDidRenameFiles = createStub("vscode.workspace.onDidRenameFiles")
|
||||
|
||||
const workspaceConfigStore = {}
|
||||
vscode.workspace.getConfiguration = function (section) {
|
||||
return {
|
||||
get: (key, defaultValue) => {
|
||||
return workspaceConfigStore[`${section}.${key}`] ?? defaultValue
|
||||
},
|
||||
update: (key, value, global) => {
|
||||
workspaceConfigStore[`${section}.${key}`] = value
|
||||
},
|
||||
has: (key) => {
|
||||
return `${section}.${key}` in workspaceConfigStore
|
||||
},
|
||||
}
|
||||
vscode.workspace.getConfiguration = function (section, scope) {
|
||||
console.log("Called stubbed function: vscode.workspace.getConfiguration")
|
||||
return createStub("unknown")
|
||||
}
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration = createStub("vscode.workspace.onDidChangeConfiguration")
|
||||
vscode.workspace.registerTaskProvider = function (type, provider) {
|
||||
console.log("Called stubbed function: vscode.workspace.registerTaskProvider")
|
||||
|
||||
@@ -10,7 +10,6 @@ import { FileServiceClient, StateServiceClient } from "@/services/grpc-client"
|
||||
import {
|
||||
ContextMenuOptionType,
|
||||
getContextMenuOptions,
|
||||
getContextMenuOptionIndex,
|
||||
insertMention,
|
||||
insertMentionDirectly,
|
||||
removeMention,
|
||||
@@ -61,9 +60,6 @@ const getImageDimensions = (dataUrl: string): Promise<{ width: number; height: n
|
||||
})
|
||||
}
|
||||
|
||||
// Set to "File" option by default
|
||||
const DEFAULT_CONTEXT_MENU_OPTION = getContextMenuOptionIndex(ContextMenuOptionType.File)
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
inputValue: string
|
||||
activeQuote: string | null
|
||||
@@ -534,7 +530,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
if (event.key === "Escape") {
|
||||
// event.preventDefault()
|
||||
setSelectedType(null)
|
||||
setSelectedMenuIndex(DEFAULT_CONTEXT_MENU_OPTION)
|
||||
setSelectedMenuIndex(3) // File by default
|
||||
return
|
||||
}
|
||||
|
||||
@@ -774,7 +770,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
})
|
||||
}, 200) // 200ms debounce
|
||||
} else {
|
||||
setSelectedMenuIndex(DEFAULT_CONTEXT_MENU_OPTION)
|
||||
setSelectedMenuIndex(3) // Set to "File" option by default
|
||||
}
|
||||
} else {
|
||||
setSearchQuery("")
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React, { useEffect, useState, useCallback } from "react"
|
||||
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" // Import ProgressRing
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import LinkPreview from "./LinkPreview"
|
||||
import ImagePreview from "./ImagePreview"
|
||||
import styled from "styled-components"
|
||||
@@ -30,10 +28,6 @@ const ResponseHeader = styled.div`
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleSwitch = styled.div`
|
||||
@@ -117,9 +111,7 @@ interface UrlMatch {
|
||||
}
|
||||
|
||||
const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText }) => {
|
||||
const { mcpResponsesCollapsed } = useExtensionState() // Get setting from context
|
||||
const [isExpanded, setIsExpanded] = useState(!mcpResponsesCollapsed) // Initialize with context setting
|
||||
const [isLoading, setIsLoading] = useState(false) // Initial loading state for rich content
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [displayMode, setDisplayMode] = useState<"rich" | "plain">(() => {
|
||||
// Get saved preference from localStorage, default to 'rich'
|
||||
const savedMode = localStorage.getItem("mcpDisplayMode")
|
||||
@@ -132,11 +124,14 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
|
||||
const toggleDisplayMode = useCallback(() => {
|
||||
const newMode = displayMode === "rich" ? "plain" : "rich"
|
||||
|
||||
// Force an immediate re-render
|
||||
setForceUpdateCounter((prev) => prev + 1)
|
||||
|
||||
// Update display mode and save preference
|
||||
setDisplayMode(newMode)
|
||||
localStorage.setItem("mcpDisplayMode", newMode)
|
||||
|
||||
// If switching to plain mode, cancel any ongoing processing
|
||||
if (newMode === "plain") {
|
||||
console.log("Switching to plain mode - cancelling URL processing")
|
||||
@@ -144,23 +139,13 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
} else {
|
||||
// If switching to rich mode, the useEffect will re-run and fetch data
|
||||
console.log("Switching to rich mode - will start URL processing")
|
||||
setUrlMatches([])
|
||||
}
|
||||
}, [displayMode])
|
||||
|
||||
const toggleExpand = useCallback(() => {
|
||||
setIsExpanded((prev) => !prev)
|
||||
}, [])
|
||||
|
||||
// Effect to update isExpanded if mcpResponsesCollapsed changes from context
|
||||
useEffect(() => {
|
||||
setIsExpanded(!mcpResponsesCollapsed)
|
||||
}, [])
|
||||
|
||||
// Find all URLs in the text and determine if they're images
|
||||
useEffect(() => {
|
||||
// Skip all processing if in plain mode
|
||||
if (!isExpanded || displayMode === "plain") {
|
||||
if (displayMode === "plain") {
|
||||
setIsLoading(false)
|
||||
setUrlMatches([]) // Clear any existing matches when in plain mode
|
||||
return
|
||||
@@ -168,10 +153,12 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
|
||||
// Use a direct boolean for cancellation that's scoped to this effect run
|
||||
let processingCanceled = false
|
||||
|
||||
const processResponse = async () => {
|
||||
console.log("Processing MCP response for URL extraction")
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const text = responseText || ""
|
||||
const matches: UrlMatch[] = []
|
||||
@@ -280,24 +267,12 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
processingCanceled = true
|
||||
console.log("Cleaning up URL processing")
|
||||
}
|
||||
}, [responseText, displayMode, forceUpdateCounter, isExpanded])
|
||||
}, [responseText, displayMode, forceUpdateCounter])
|
||||
|
||||
// Function to render content based on display mode
|
||||
const renderContent = () => {
|
||||
if (!isExpanded) {
|
||||
return null // Don't render content if not expanded
|
||||
}
|
||||
|
||||
if (isLoading && displayMode === "rich") {
|
||||
return (
|
||||
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50px" }}>
|
||||
<VSCodeProgressRing />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// For plain text mode, just show the text
|
||||
if (displayMode === "plain") {
|
||||
if (displayMode === "plain" || isLoading) {
|
||||
return <UrlText>{responseText}</UrlText>
|
||||
}
|
||||
|
||||
@@ -312,7 +287,7 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
}
|
||||
|
||||
// For rich display mode, show the text with embedded content
|
||||
if (displayMode === "rich") {
|
||||
if (!isLoading) {
|
||||
// We already know displayMode is "rich" if we get here
|
||||
// Create an array of text segments and embedded content
|
||||
const segments: JSX.Element[] = []
|
||||
@@ -410,48 +385,30 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
try {
|
||||
return (
|
||||
<ResponseContainer>
|
||||
<ResponseHeader
|
||||
onClick={toggleExpand}
|
||||
style={{
|
||||
borderBottom: isExpanded ? "1px dashed var(--vscode-editorGroup-border)" : "none",
|
||||
marginBottom: isExpanded ? "8px" : "0px",
|
||||
}}>
|
||||
<div className="header-title">
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"} header-icon`}></span>
|
||||
Response
|
||||
</div>
|
||||
<div style={{ minWidth: isExpanded ? "auto" : "0", visibility: isExpanded ? "visible" : "hidden" }}>
|
||||
<ToggleSwitch onClick={(e) => e.stopPropagation()}>
|
||||
<span className="toggle-label">{displayMode === "rich" ? "Rich Display" : "Plain Text"}</span>
|
||||
<div
|
||||
className={`toggle-container ${displayMode === "rich" ? "active" : ""}`}
|
||||
onClick={toggleDisplayMode}>
|
||||
<div className="toggle-handle"></div>
|
||||
</div>
|
||||
</ToggleSwitch>
|
||||
</div>
|
||||
<ResponseHeader>
|
||||
<span className="header-title">Response</span>
|
||||
<ToggleSwitch>
|
||||
<span className="toggle-label">{displayMode === "rich" ? "Rich Display" : "Plain Text"}</span>
|
||||
<div className={`toggle-container ${displayMode === "rich" ? "active" : ""}`} onClick={toggleDisplayMode}>
|
||||
<div className="toggle-handle"></div>
|
||||
</div>
|
||||
</ToggleSwitch>
|
||||
</ResponseHeader>
|
||||
|
||||
{isExpanded && <div className="response-content">{renderContent()}</div>}
|
||||
<div className="response-content">{renderContent()}</div>
|
||||
</ResponseContainer>
|
||||
)
|
||||
} catch (error) {
|
||||
console.log("Error rendering MCP response - falling back to plain text") // Restored comment
|
||||
// Fallback for critical rendering errors
|
||||
console.log("Error rendering MCP response - falling back to plain text")
|
||||
return (
|
||||
<ResponseContainer>
|
||||
<ResponseHeader onClick={toggleExpand}>
|
||||
<div className="header-title">
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"} header-icon`}></span>
|
||||
Response (Error)
|
||||
</div>
|
||||
<ResponseHeader>
|
||||
<span className="header-title">Response</span>
|
||||
</ResponseHeader>
|
||||
{isExpanded && (
|
||||
<div className="response-content">
|
||||
<div style={{ color: "var(--vscode-errorForeground)" }}>Error parsing response:</div>
|
||||
<UrlText>{responseText}</UrlText>
|
||||
</div>
|
||||
)}
|
||||
<div className="response-content">
|
||||
<div>Error parsing response:</div>
|
||||
<UrlText>{responseText}</UrlText>
|
||||
</div>
|
||||
</ResponseContainer>
|
||||
)
|
||||
}
|
||||
|
||||
+13
-14
@@ -14,7 +14,8 @@ import { vscode } from "@/utils/vscode"
|
||||
import McpMarketplaceCard from "./McpMarketplaceCard"
|
||||
import McpSubmitCard from "./McpSubmitCard"
|
||||
const McpMarketplaceView = () => {
|
||||
const { mcpServers, mcpMarketplaceCatalog } = useExtensionState()
|
||||
const { mcpServers } = useExtensionState()
|
||||
const [items, setItems] = useState<McpMarketplaceItem[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
@@ -22,8 +23,6 @@ const McpMarketplaceView = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
||||
const [sortBy, setSortBy] = useState<"newest" | "stars" | "name" | "downloadCount">("downloadCount")
|
||||
|
||||
const items = mcpMarketplaceCatalog?.items || []
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const uniqueCategories = new Set(items.map((item) => item.category))
|
||||
return Array.from(uniqueCategories).sort()
|
||||
@@ -59,7 +58,16 @@ const McpMarketplaceView = () => {
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const message = event.data
|
||||
if (message.type === "mcpDownloadDetails") {
|
||||
if (message.type === "mcpMarketplaceCatalog") {
|
||||
if (message.error) {
|
||||
setError(message.error)
|
||||
} else {
|
||||
setItems(message.mcpMarketplaceCatalog?.items || [])
|
||||
setError(null)
|
||||
}
|
||||
setIsLoading(false)
|
||||
setIsRefreshing(false)
|
||||
} else if (message.type === "mcpDownloadDetails") {
|
||||
if (message.error) {
|
||||
setError(message.error)
|
||||
}
|
||||
@@ -68,7 +76,7 @@ const McpMarketplaceView = () => {
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
|
||||
// Fetch marketplace catalog on initial load
|
||||
// Fetch marketplace catalog
|
||||
fetchMarketplace()
|
||||
|
||||
return () => {
|
||||
@@ -76,15 +84,6 @@ const McpMarketplaceView = () => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Update loading state when catalog arrives
|
||||
if (mcpMarketplaceCatalog?.items) {
|
||||
setIsLoading(false)
|
||||
setIsRefreshing(false)
|
||||
setError(null)
|
||||
}
|
||||
}, [mcpMarketplaceCatalog])
|
||||
|
||||
const fetchMarketplace = (forceRefresh: boolean = false) => {
|
||||
if (forceRefresh) {
|
||||
setIsRefreshing(true)
|
||||
|
||||
@@ -9,8 +9,6 @@ const FeatureSettingsSection = () => {
|
||||
setEnableCheckpointsSetting,
|
||||
mcpMarketplaceEnabled,
|
||||
setMcpMarketplaceEnabled,
|
||||
mcpResponsesCollapsed,
|
||||
setMcpResponsesCollapsed,
|
||||
chatSettings,
|
||||
setChatSettings,
|
||||
} = useExtensionState()
|
||||
@@ -44,19 +42,6 @@ const FeatureSettingsSection = () => {
|
||||
Enables the MCP Marketplace tab for discovering and installing MCP servers.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<VSCodeCheckbox
|
||||
checked={mcpResponsesCollapsed}
|
||||
onChange={(e: any) => {
|
||||
const checked = e.target.checked === true
|
||||
setMcpResponsesCollapsed(checked)
|
||||
}}>
|
||||
Collapse MCP Responses
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-xs text-[var(--vscode-descriptionForeground)]">
|
||||
Sets the default display mode for MCP response panels
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<label
|
||||
htmlFor="openai-reasoning-effort-dropdown"
|
||||
|
||||
@@ -130,8 +130,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
setShellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
setTerminalReuseEnabled,
|
||||
mcpResponsesCollapsed,
|
||||
setMcpResponsesCollapsed,
|
||||
setApiConfiguration,
|
||||
} = useExtensionState()
|
||||
|
||||
@@ -143,7 +141,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting,
|
||||
mcpMarketplaceEnabled,
|
||||
mcpResponsesCollapsed,
|
||||
chatSettings,
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
@@ -189,7 +186,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
mcpMarketplaceEnabled,
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
mcpResponsesCollapsed,
|
||||
apiConfiguration: apiConfigurationToSubmit,
|
||||
})
|
||||
|
||||
@@ -212,7 +208,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
planActSeparateModelsSetting !== originalState.current.planActSeparateModelsSetting ||
|
||||
enableCheckpointsSetting !== originalState.current.enableCheckpointsSetting ||
|
||||
mcpMarketplaceEnabled !== originalState.current.mcpMarketplaceEnabled ||
|
||||
mcpResponsesCollapsed !== originalState.current.mcpResponsesCollapsed ||
|
||||
JSON.stringify(chatSettings) !== JSON.stringify(originalState.current.chatSettings) ||
|
||||
shellIntegrationTimeout !== originalState.current.shellIntegrationTimeout ||
|
||||
terminalReuseEnabled !== originalState.current.terminalReuseEnabled
|
||||
@@ -225,7 +220,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting,
|
||||
mcpMarketplaceEnabled,
|
||||
mcpResponsesCollapsed,
|
||||
chatSettings,
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
@@ -266,9 +260,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
if (typeof setTerminalReuseEnabled === "function") {
|
||||
setTerminalReuseEnabled(originalState.current.terminalReuseEnabled ?? true)
|
||||
}
|
||||
if (typeof setMcpResponsesCollapsed === "function") {
|
||||
setMcpResponsesCollapsed(originalState.current.mcpResponsesCollapsed ?? false)
|
||||
}
|
||||
// Close settings view
|
||||
onDone()
|
||||
}
|
||||
@@ -286,7 +277,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
setApiConfiguration,
|
||||
setEnableCheckpointsSetting,
|
||||
setMcpMarketplaceEnabled,
|
||||
setMcpResponsesCollapsed,
|
||||
])
|
||||
|
||||
// Handle confirmation dialog actions
|
||||
|
||||
@@ -18,10 +18,9 @@ import {
|
||||
requestyDefaultModelInfo,
|
||||
} from "../../../src/shared/api"
|
||||
import { McpMarketplaceCatalog, McpServer, McpViewTab } from "../../../src/shared/mcp"
|
||||
import { ModelsServiceClient, StateServiceClient, UiServiceClient, McpServiceClient } from "../services/grpc-client"
|
||||
import { ModelsServiceClient, StateServiceClient, UiServiceClient } from "../services/grpc-client"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { OpenRouterCompatibleModelInfo } from "@shared/proto/models"
|
||||
|
||||
interface ExtensionStateContextType extends ExtensionState {
|
||||
didHydrateState: boolean
|
||||
@@ -52,7 +51,6 @@ interface ExtensionStateContextType extends ExtensionState {
|
||||
setPlanActSeparateModelsSetting: (value: boolean) => void
|
||||
setEnableCheckpointsSetting: (value: boolean) => void
|
||||
setMcpMarketplaceEnabled: (value: boolean) => void
|
||||
setMcpResponsesCollapsed: (value: boolean) => void
|
||||
setShellIntegrationTimeout: (value: number) => void
|
||||
setTerminalReuseEnabled: (value: boolean) => void
|
||||
setChatSettings: (value: ChatSettings) => void
|
||||
@@ -180,7 +178,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
shellIntegrationTimeout: 4000, // default timeout for shell integration
|
||||
terminalReuseEnabled: true, // default to enabled for backward compatibility
|
||||
isNewUser: false,
|
||||
mcpResponsesCollapsed: false, // Default value (expanded), will be overwritten by extension state
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
@@ -200,10 +197,24 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
switch (message.type) {
|
||||
case "theme": {
|
||||
if (message.text) {
|
||||
setTheme(convertTextMateToHljs(JSON.parse(message.text)))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "workspaceUpdated": {
|
||||
setFilePaths(message.filePaths ?? [])
|
||||
break
|
||||
}
|
||||
case "openRouterModels": {
|
||||
const updatedModels = message.openRouterModels ?? {}
|
||||
setOpenRouterModels({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model
|
||||
...updatedModels,
|
||||
})
|
||||
break
|
||||
}
|
||||
case "openAiModels": {
|
||||
const updatedModels = message.openAiModels ?? []
|
||||
setOpenAiModels(updatedModels)
|
||||
@@ -221,6 +232,12 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
setMcpServers(message.mcpServers ?? [])
|
||||
break
|
||||
}
|
||||
case "mcpMarketplaceCatalog": {
|
||||
if (message.mcpMarketplaceCatalog) {
|
||||
setMcpMarketplaceCatalog(message.mcpMarketplaceCatalog)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -234,9 +251,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
const accountButtonClickedSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
const settingsButtonClickedSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
const partialMessageUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
const mcpMarketplaceUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
const themeSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
const openRouterModelsUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
|
||||
// Subscribe to state updates and UI events using the gRPC streaming API
|
||||
useEffect(() => {
|
||||
@@ -422,67 +436,8 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
},
|
||||
})
|
||||
|
||||
// Subscribe to MCP marketplace catalog updates
|
||||
mcpMarketplaceUnsubscribeRef.current = McpServiceClient.subscribeToMcpMarketplaceCatalog(EmptyRequest.create({}), {
|
||||
onResponse: (catalog) => {
|
||||
console.log("[DEBUG] Received MCP marketplace catalog update from gRPC stream")
|
||||
setMcpMarketplaceCatalog(catalog)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in MCP marketplace catalog subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("MCP marketplace catalog subscription completed")
|
||||
},
|
||||
})
|
||||
|
||||
// Subscribe to theme changes
|
||||
themeSubscriptionRef.current = UiServiceClient.subscribeToTheme(EmptyRequest.create({}), {
|
||||
onResponse: (response) => {
|
||||
if (response.value) {
|
||||
try {
|
||||
const themeData = JSON.parse(response.value)
|
||||
setTheme(convertTextMateToHljs(themeData))
|
||||
console.log("[DEBUG] Received theme update from gRPC stream")
|
||||
} catch (error) {
|
||||
console.error("Error parsing theme data:", error)
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in theme subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("Theme subscription completed")
|
||||
},
|
||||
})
|
||||
|
||||
// Subscribe to OpenRouter models updates
|
||||
openRouterModelsUnsubscribeRef.current = ModelsServiceClient.subscribeToOpenRouterModels(EmptyRequest.create({}), {
|
||||
onResponse: (response: OpenRouterCompatibleModelInfo) => {
|
||||
console.log("[DEBUG] Received OpenRouter models update from gRPC stream")
|
||||
const models = response.models
|
||||
setOpenRouterModels({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model
|
||||
...models,
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in OpenRouter models subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("OpenRouter models subscription completed")
|
||||
},
|
||||
})
|
||||
|
||||
// Initialize webview using gRPC
|
||||
UiServiceClient.initializeWebview(EmptyRequest.create({}))
|
||||
.then(() => {
|
||||
console.log("[DEBUG] Webview initialization completed via gRPC")
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to initialize webview via gRPC:", error)
|
||||
})
|
||||
// Still send the webviewDidLaunch message for other initialization
|
||||
vscode.postMessage({ type: "webviewDidLaunch" })
|
||||
|
||||
// Set up account button clicked subscription
|
||||
accountButtonClickedSubscriptionRef.current = UiServiceClient.subscribeToAccountButtonClicked(EmptyRequest.create(), {
|
||||
@@ -529,28 +484,15 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
partialMessageUnsubscribeRef.current()
|
||||
partialMessageUnsubscribeRef.current = null
|
||||
}
|
||||
if (mcpMarketplaceUnsubscribeRef.current) {
|
||||
mcpMarketplaceUnsubscribeRef.current()
|
||||
mcpMarketplaceUnsubscribeRef.current = null
|
||||
}
|
||||
if (themeSubscriptionRef.current) {
|
||||
themeSubscriptionRef.current()
|
||||
themeSubscriptionRef.current = null
|
||||
}
|
||||
if (openRouterModelsUnsubscribeRef.current) {
|
||||
openRouterModelsUnsubscribeRef.current()
|
||||
openRouterModelsUnsubscribeRef.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const refreshOpenRouterModels = useCallback(() => {
|
||||
ModelsServiceClient.refreshOpenRouterModels(EmptyRequest.create({}))
|
||||
.then((response: OpenRouterCompatibleModelInfo) => {
|
||||
const models = response.models
|
||||
.then((res) => {
|
||||
setOpenRouterModels({
|
||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo, // in case the extension sent a model list without the default model
|
||||
...models,
|
||||
...res.models,
|
||||
})
|
||||
})
|
||||
.catch((error: Error) => console.error("Failed to refresh OpenRouter models:", error))
|
||||
@@ -624,12 +566,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
...prevState,
|
||||
mcpMarketplaceEnabled: value,
|
||||
})),
|
||||
setMcpResponsesCollapsed: (value) => {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
mcpResponsesCollapsed: value,
|
||||
}))
|
||||
},
|
||||
setShowAnnouncement,
|
||||
setShouldShowAnnouncement: (value) =>
|
||||
setState((prevState) => ({
|
||||
@@ -664,7 +600,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
planActSeparateModelsSetting: state.planActSeparateModelsSetting,
|
||||
enableCheckpointsSetting: state.enableCheckpointsSetting,
|
||||
mcpMarketplaceEnabled: state.mcpMarketplaceEnabled,
|
||||
mcpResponsesCollapsed: state.mcpResponsesCollapsed,
|
||||
})
|
||||
},
|
||||
setGlobalClineRulesToggles: (toggles) =>
|
||||
|
||||
@@ -75,19 +75,6 @@ export interface ContextMenuQueryItem {
|
||||
description?: string
|
||||
}
|
||||
|
||||
const DEFAULT_CONTEXT_MENU_OPTIONS = [
|
||||
ContextMenuOptionType.URL,
|
||||
ContextMenuOptionType.Problems,
|
||||
ContextMenuOptionType.Terminal,
|
||||
ContextMenuOptionType.Git,
|
||||
ContextMenuOptionType.Folder,
|
||||
ContextMenuOptionType.File,
|
||||
]
|
||||
|
||||
export function getContextMenuOptionIndex(option: ContextMenuOptionType) {
|
||||
return DEFAULT_CONTEXT_MENU_OPTIONS.findIndex((item) => item === option)
|
||||
}
|
||||
|
||||
export function getContextMenuOptions(
|
||||
query: string,
|
||||
selectedType: ContextMenuOptionType | null = null,
|
||||
@@ -127,7 +114,14 @@ export function getContextMenuOptions(
|
||||
return commits.length > 0 ? [workingChanges, ...commits] : [workingChanges]
|
||||
}
|
||||
|
||||
return DEFAULT_CONTEXT_MENU_OPTIONS.map((type) => ({ type }))
|
||||
return [
|
||||
{ type: ContextMenuOptionType.URL },
|
||||
{ type: ContextMenuOptionType.Problems },
|
||||
{ type: ContextMenuOptionType.Terminal },
|
||||
{ type: ContextMenuOptionType.Git },
|
||||
{ type: ContextMenuOptionType.Folder },
|
||||
{ type: ContextMenuOptionType.File },
|
||||
]
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase()
|
||||
|
||||
Reference in New Issue
Block a user