mirror of
https://github.com/cline/cline.git
synced 2026-09-02 15:52:29 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3254a5099f | |||
| 4fa16e9c7a |
@@ -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 })
|
||||
}
|
||||
+203
@@ -18,6 +18,206 @@ message WebviewProviderTypeRequest {
|
||||
WebviewProviderType providerType = 2;
|
||||
}
|
||||
|
||||
// Enum for ClineMessage type
|
||||
enum ClineMessageType {
|
||||
ASK = 0;
|
||||
SAY = 1;
|
||||
}
|
||||
|
||||
// Enum for ClineAsk types
|
||||
enum ClineAsk {
|
||||
FOLLOWUP = 0;
|
||||
PLAN_MODE_RESPOND = 1;
|
||||
COMMAND = 2;
|
||||
COMMAND_OUTPUT = 3;
|
||||
COMPLETION_RESULT = 4;
|
||||
TOOL = 5;
|
||||
API_REQ_FAILED = 6;
|
||||
RESUME_TASK = 7;
|
||||
RESUME_COMPLETED_TASK = 8;
|
||||
MISTAKE_LIMIT_REACHED = 9;
|
||||
AUTO_APPROVAL_MAX_REQ_REACHED = 10;
|
||||
BROWSER_ACTION_LAUNCH = 11;
|
||||
USE_MCP_SERVER = 12;
|
||||
NEW_TASK = 13;
|
||||
CONDENSE = 14;
|
||||
REPORT_BUG = 15;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
enum ClineSay {
|
||||
TASK = 0;
|
||||
ERROR = 1;
|
||||
API_REQ_STARTED = 2;
|
||||
API_REQ_FINISHED = 3;
|
||||
TEXT = 4;
|
||||
REASONING = 5;
|
||||
COMPLETION_RESULT_SAY = 6;
|
||||
USER_FEEDBACK = 7;
|
||||
USER_FEEDBACK_DIFF = 8;
|
||||
API_REQ_RETRIED = 9;
|
||||
COMMAND_SAY = 10;
|
||||
COMMAND_OUTPUT_SAY = 11;
|
||||
TOOL_SAY = 12;
|
||||
SHELL_INTEGRATION_WARNING = 13;
|
||||
BROWSER_ACTION_LAUNCH_SAY = 14;
|
||||
BROWSER_ACTION = 15;
|
||||
BROWSER_ACTION_RESULT = 16;
|
||||
MCP_SERVER_REQUEST_STARTED = 17;
|
||||
MCP_SERVER_RESPONSE = 18;
|
||||
USE_MCP_SERVER_SAY = 19;
|
||||
DIFF_ERROR = 20;
|
||||
DELETED_API_REQS = 21;
|
||||
CLINEIGNORE_ERROR = 22;
|
||||
CHECKPOINT_CREATED = 23;
|
||||
LOAD_MCP_DOCUMENTATION = 24;
|
||||
INFO = 25;
|
||||
}
|
||||
|
||||
// Enum for ClineSayTool tool types
|
||||
enum ClineSayToolType {
|
||||
EDITED_EXISTING_FILE = 0;
|
||||
NEW_FILE_CREATED = 1;
|
||||
READ_FILE = 2;
|
||||
LIST_FILES_TOP_LEVEL = 3;
|
||||
LIST_FILES_RECURSIVE = 4;
|
||||
LIST_CODE_DEFINITION_NAMES = 5;
|
||||
SEARCH_FILES = 6;
|
||||
WEB_FETCH = 7;
|
||||
}
|
||||
|
||||
// Enum for browser actions
|
||||
enum BrowserAction {
|
||||
LAUNCH = 0;
|
||||
CLICK = 1;
|
||||
TYPE = 2;
|
||||
SCROLL_DOWN = 3;
|
||||
SCROLL_UP = 4;
|
||||
CLOSE = 5;
|
||||
}
|
||||
|
||||
// Enum for MCP server request types
|
||||
enum McpServerRequestType {
|
||||
USE_MCP_TOOL = 0;
|
||||
ACCESS_MCP_RESOURCE = 1;
|
||||
}
|
||||
|
||||
// Enum for API request cancel reasons
|
||||
enum ClineApiReqCancelReason {
|
||||
STREAMING_FAILED = 0;
|
||||
USER_CANCELLED = 1;
|
||||
RETRIES_EXHAUSTED = 2;
|
||||
}
|
||||
|
||||
// Message for conversation history deleted range
|
||||
message ConversationHistoryDeletedRange {
|
||||
int32 start_index = 1;
|
||||
int32 end_index = 2;
|
||||
}
|
||||
|
||||
// Message for ClineSayTool
|
||||
message ClineSayTool {
|
||||
ClineSayToolType tool = 1;
|
||||
string path = 2;
|
||||
string diff = 3;
|
||||
string content = 4;
|
||||
string regex = 5;
|
||||
string file_pattern = 6;
|
||||
bool operation_is_located_in_workspace = 7;
|
||||
}
|
||||
|
||||
// Message for ClineSayBrowserAction
|
||||
message ClineSayBrowserAction {
|
||||
BrowserAction action = 1;
|
||||
string coordinate = 2;
|
||||
string text = 3;
|
||||
}
|
||||
|
||||
// Message for BrowserActionResult
|
||||
message BrowserActionResult {
|
||||
string screenshot = 1;
|
||||
string logs = 2;
|
||||
string current_url = 3;
|
||||
string current_mouse_position = 4;
|
||||
}
|
||||
|
||||
// Message for ClineAskUseMcpServer
|
||||
message ClineAskUseMcpServer {
|
||||
string server_name = 1;
|
||||
McpServerRequestType type = 2;
|
||||
string tool_name = 3;
|
||||
string arguments = 4;
|
||||
string uri = 5;
|
||||
}
|
||||
|
||||
// Message for ClinePlanModeResponse
|
||||
message ClinePlanModeResponse {
|
||||
string response = 1;
|
||||
repeated string options = 2;
|
||||
string selected = 3;
|
||||
}
|
||||
|
||||
// Message for ClineAskQuestion
|
||||
message ClineAskQuestion {
|
||||
string question = 1;
|
||||
repeated string options = 2;
|
||||
string selected = 3;
|
||||
}
|
||||
|
||||
// Message for ClineAskNewTask
|
||||
message ClineAskNewTask {
|
||||
string context = 1;
|
||||
}
|
||||
|
||||
// Message for API request retry status
|
||||
message ApiReqRetryStatus {
|
||||
int32 attempt = 1;
|
||||
int32 max_attempts = 2;
|
||||
int32 delay_sec = 3;
|
||||
string error_snippet = 4;
|
||||
}
|
||||
|
||||
// Message for ClineApiReqInfo
|
||||
message ClineApiReqInfo {
|
||||
string request = 1;
|
||||
int32 tokens_in = 2;
|
||||
int32 tokens_out = 3;
|
||||
int32 cache_writes = 4;
|
||||
int32 cache_reads = 5;
|
||||
double cost = 6;
|
||||
ClineApiReqCancelReason cancel_reason = 7;
|
||||
string streaming_failed_message = 8;
|
||||
ApiReqRetryStatus retry_status = 9;
|
||||
}
|
||||
|
||||
// Main ClineMessage type
|
||||
message ClineMessage {
|
||||
int64 ts = 1;
|
||||
ClineMessageType type = 2;
|
||||
ClineAsk ask = 3;
|
||||
ClineSay say = 4;
|
||||
string text = 5;
|
||||
string reasoning = 6;
|
||||
repeated string images = 7;
|
||||
repeated string files = 8;
|
||||
bool partial = 9;
|
||||
string last_checkpoint_hash = 10;
|
||||
bool is_checkpoint_checked_out = 11;
|
||||
bool is_operation_outside_workspace = 12;
|
||||
int32 conversation_history_index = 13;
|
||||
ConversationHistoryDeletedRange conversation_history_deleted_range = 14;
|
||||
|
||||
// Additional fields for specific ask/say types
|
||||
ClineSayTool say_tool = 15;
|
||||
ClineSayBrowserAction say_browser_action = 16;
|
||||
BrowserActionResult browser_action_result = 17;
|
||||
ClineAskUseMcpServer ask_use_mcp_server = 18;
|
||||
ClinePlanModeResponse plan_mode_response = 19;
|
||||
ClineAskQuestion ask_question = 20;
|
||||
ClineAskNewTask ask_new_task = 21;
|
||||
ClineApiReqInfo api_req_info = 22;
|
||||
}
|
||||
|
||||
// UiService provides methods for managing UI interactions
|
||||
service UiService {
|
||||
// Scrolls to a specific settings section in the settings view
|
||||
@@ -43,4 +243,7 @@ service UiService {
|
||||
|
||||
// Subscribe to settings button clicked events
|
||||
rpc subscribeToSettingsButtonClicked(WebviewProviderTypeRequest) returns (stream Empty);
|
||||
|
||||
// Subscribe to partial message updates (streaming Cline messages as they're built)
|
||||
rpc subscribeToPartialMessage(EmptyRequest) returns (stream ClineMessage);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Controller } from "../index"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { ClineMessage } from "@shared/proto/ui"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
|
||||
// Keep track of active partial message subscriptions
|
||||
const activePartialMessageSubscriptions = new Set<StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to partial message 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 subscribeToPartialMessage(
|
||||
controller: Controller,
|
||||
request: EmptyRequest,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
// Add this subscription to the active subscriptions
|
||||
activePartialMessageSubscriptions.add(responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activePartialMessageSubscriptions.delete(responseStream)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
if (requestId) {
|
||||
getRequestRegistry().registerRequest(requestId, cleanup, { type: "partial_message_subscription" }, responseStream)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a partial message event to all active subscribers
|
||||
* @param partialMessage The ClineMessage to send
|
||||
*/
|
||||
export async function sendPartialMessageEvent(partialMessage: ClineMessage): Promise<void> {
|
||||
// Send the event to all active subscribers
|
||||
const promises = Array.from(activePartialMessageSubscriptions).map(async (responseStream) => {
|
||||
try {
|
||||
await responseStream(
|
||||
partialMessage,
|
||||
false, // Not the last message
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error sending partial message event:", error)
|
||||
// Remove the subscription if there was an error
|
||||
activePartialMessageSubscriptions.delete(responseStream)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
+10
-10
@@ -72,6 +72,8 @@ import { ClineIgnoreController } from "@core/ignore/ClineIgnoreController"
|
||||
import { parseMentions } from "@core/mentions"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { addUserInstructions, SYSTEM_PROMPT } from "@core/prompts/system"
|
||||
import { sendPartialMessageEvent } from "@core/controller/ui/subscribeToPartialMessage"
|
||||
import { convertClineMessageToProto } from "@shared/proto-conversions/cline-message"
|
||||
import { getContextWindowInfo } from "@core/context/context-management/context-window-utils"
|
||||
import { FileContextTracker } from "@core/context/context-tracking/FileContextTracker"
|
||||
import { ModelContextTracker } from "@core/context/context-tracking/ModelContextTracker"
|
||||
@@ -743,10 +745,8 @@ export class Task {
|
||||
// todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener
|
||||
// await this.saveClineMessagesAndUpdateHistory()
|
||||
// await this.postStateToWebview()
|
||||
await this.postMessageToWebview({
|
||||
type: "partialMessage",
|
||||
partialMessage: lastMessage,
|
||||
})
|
||||
const protoMessage = convertClineMessageToProto(lastMessage)
|
||||
await sendPartialMessageEvent(protoMessage)
|
||||
throw new Error("Current ask promise was ignored 1")
|
||||
} else {
|
||||
// this is a new partial message, so add it with partial state
|
||||
@@ -787,10 +787,8 @@ export class Task {
|
||||
lastMessage.partial = false
|
||||
await this.saveClineMessagesAndUpdateHistory()
|
||||
// await this.postStateToWebview()
|
||||
await this.postMessageToWebview({
|
||||
type: "partialMessage",
|
||||
partialMessage: lastMessage,
|
||||
})
|
||||
const protoMessage = convertClineMessageToProto(lastMessage)
|
||||
await sendPartialMessageEvent(protoMessage)
|
||||
} else {
|
||||
// this is a new partial=false message, so add it like normal
|
||||
this.askResponse = undefined
|
||||
@@ -866,7 +864,8 @@ export class Task {
|
||||
lastMessage.images = images
|
||||
lastMessage.files = files
|
||||
lastMessage.partial = partial
|
||||
await this.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
|
||||
const protoMessage = convertClineMessageToProto(lastMessage)
|
||||
await sendPartialMessageEvent(protoMessage)
|
||||
} else {
|
||||
// this is a new partial message, so add it with partial state
|
||||
const sayTs = Date.now()
|
||||
@@ -896,7 +895,8 @@ export class Task {
|
||||
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
|
||||
await this.saveClineMessagesAndUpdateHistory()
|
||||
// await this.postStateToWebview()
|
||||
await this.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage }) // more performant than an entire postStateToWebview
|
||||
const protoMessage = convertClineMessageToProto(lastMessage)
|
||||
await sendPartialMessageEvent(protoMessage) // more performant than an entire postStateToWebview
|
||||
} else {
|
||||
// this is a new partial=false message, so add it like normal
|
||||
const sayTs = Date.now()
|
||||
|
||||
@@ -37,25 +37,28 @@ function createToolCallTracker(webviewProvider: WebviewProvider): {
|
||||
// Intercept messages to track tool usage
|
||||
const originalPostMessageToWebview = webviewProvider.controller.postMessageToWebview
|
||||
webviewProvider.controller.postMessageToWebview = async (message: ExtensionMessage) => {
|
||||
// Track tool calls
|
||||
if (message.type === "partialMessage" && message.partialMessage?.say === "tool") {
|
||||
const toolName = (message.partialMessage.text as any)?.tool
|
||||
if (toolName) {
|
||||
tracker.toolCalls[toolName] = (tracker.toolCalls[toolName] || 0) + 1
|
||||
}
|
||||
}
|
||||
// NOTE: Tool tracking via partialMessage has been migrated to gRPC streaming
|
||||
// This interceptor is kept for potential future use with other message types
|
||||
|
||||
// Track tool failures
|
||||
if (message.type === "partialMessage" && message.partialMessage?.say === "error") {
|
||||
const errorText = message.partialMessage.text
|
||||
if (errorText && errorText.includes("Error executing tool")) {
|
||||
const match = errorText.match(/Error executing tool: (\w+)/)
|
||||
if (match && match[1]) {
|
||||
const toolName = match[1]
|
||||
tracker.toolFailures[toolName] = (tracker.toolFailures[toolName] || 0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
// Track tool calls - commented out as partialMessage is now handled via gRPC
|
||||
// if (message.type === "partialMessage" && message.partialMessage?.say === "tool") {
|
||||
// const toolName = (message.partialMessage.text as any)?.tool
|
||||
// if (toolName) {
|
||||
// tracker.toolCalls[toolName] = (tracker.toolCalls[toolName] || 0) + 1
|
||||
// }
|
||||
// }
|
||||
|
||||
// Track tool failures - commented out as partialMessage is now handled via gRPC
|
||||
// if (message.type === "partialMessage" && message.partialMessage?.say === "error") {
|
||||
// const errorText = message.partialMessage.text
|
||||
// if (errorText && errorText.includes("Error executing tool")) {
|
||||
// const match = errorText.match(/Error executing tool: (\w+)/)
|
||||
// if (match && match[1]) {
|
||||
// const toolName = match[1]
|
||||
// tracker.toolFailures[toolName] = (tracker.toolFailures[toolName] || 0) + 1
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return originalPostMessageToWebview.call(webviewProvider.controller, message)
|
||||
}
|
||||
@@ -504,22 +507,25 @@ export function createMessageCatcher(webviewProvider: WebviewProvider): vscode.D
|
||||
|
||||
// Intercept outgoing messages from extension to webview
|
||||
webviewProvider.controller.postMessageToWebview = async (message: ExtensionMessage) => {
|
||||
// Check for completion_result message
|
||||
if (message.type === "partialMessage" && message.partialMessage?.say === "completion_result") {
|
||||
// Complete the current task
|
||||
completeTask()
|
||||
}
|
||||
// NOTE: Completion and ask message detection has been migrated to gRPC streaming
|
||||
// This interceptor is kept for potential future use with other message types
|
||||
|
||||
// Check for ask messages that require user intervention
|
||||
if (message.type === "partialMessage" && message.partialMessage?.type === "ask" && !message.partialMessage.partial) {
|
||||
const askType = message.partialMessage.ask as ClineAsk
|
||||
const askText = message.partialMessage.text
|
||||
// Check for completion_result message - commented out as partialMessage is now handled via gRPC
|
||||
// if (message.type === "partialMessage" && message.partialMessage?.say === "completion_result") {
|
||||
// // Complete the current task
|
||||
// completeTask()
|
||||
// }
|
||||
|
||||
// Automatically respond to different types of asks
|
||||
setTimeout(async () => {
|
||||
await autoRespondToAsk(webviewProvider, askType, askText)
|
||||
}, 100) // Small delay to ensure the message is processed first
|
||||
}
|
||||
// Check for ask messages that require user intervention - commented out as partialMessage is now handled via gRPC
|
||||
// if (message.type === "partialMessage" && message.partialMessage?.type === "ask" && !message.partialMessage.partial) {
|
||||
// const askType = message.partialMessage.ask as ClineAsk
|
||||
// const askText = message.partialMessage.text
|
||||
|
||||
// // Automatically respond to different types of asks
|
||||
// setTimeout(async () => {
|
||||
// await autoRespondToAsk(webviewProvider, askType, askText)
|
||||
// }, 100) // Small delay to ensure the message is processed first
|
||||
// }
|
||||
|
||||
return originalPostMessageToWebview.call(webviewProvider.controller, message)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export interface ExtensionMessage {
|
||||
| "lmStudioModels"
|
||||
| "theme"
|
||||
| "workspaceUpdated"
|
||||
| "partialMessage"
|
||||
| "openRouterModels"
|
||||
| "openAiModels"
|
||||
| "requestyModels"
|
||||
@@ -46,7 +45,6 @@ export interface ExtensionMessage {
|
||||
lmStudioModels?: string[]
|
||||
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
|
||||
filePaths?: string[]
|
||||
partialMessage?: ClineMessage
|
||||
openRouterModels?: Record<string, ModelInfo>
|
||||
openAiModels?: string[]
|
||||
requestyModels?: Record<string, ModelInfo>
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
import { ClineMessage as AppClineMessage, ClineAsk as AppClineAsk, ClineSay as AppClineSay } from "@shared/ExtensionMessage"
|
||||
|
||||
import { ClineMessage as ProtoClineMessage, ClineMessageType, ClineAsk, ClineSay } from "@shared/proto/ui"
|
||||
|
||||
// Helper function to convert ClineAsk string to enum
|
||||
function convertClineAskToProtoEnum(ask: AppClineAsk | undefined): ClineAsk | undefined {
|
||||
if (!ask) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const mapping: Record<AppClineAsk, ClineAsk> = {
|
||||
followup: ClineAsk.FOLLOWUP,
|
||||
plan_mode_respond: ClineAsk.PLAN_MODE_RESPOND,
|
||||
command: ClineAsk.COMMAND,
|
||||
command_output: ClineAsk.COMMAND_OUTPUT,
|
||||
completion_result: ClineAsk.COMPLETION_RESULT,
|
||||
tool: ClineAsk.TOOL,
|
||||
api_req_failed: ClineAsk.API_REQ_FAILED,
|
||||
resume_task: ClineAsk.RESUME_TASK,
|
||||
resume_completed_task: ClineAsk.RESUME_COMPLETED_TASK,
|
||||
mistake_limit_reached: ClineAsk.MISTAKE_LIMIT_REACHED,
|
||||
auto_approval_max_req_reached: ClineAsk.AUTO_APPROVAL_MAX_REQ_REACHED,
|
||||
browser_action_launch: ClineAsk.BROWSER_ACTION_LAUNCH,
|
||||
use_mcp_server: ClineAsk.USE_MCP_SERVER,
|
||||
new_task: ClineAsk.NEW_TASK,
|
||||
condense: ClineAsk.CONDENSE,
|
||||
report_bug: ClineAsk.REPORT_BUG,
|
||||
}
|
||||
|
||||
const result = mapping[ask]
|
||||
if (result === undefined) {
|
||||
console.warn(`Unknown ClineAsk value: ${ask}`)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to convert ClineAsk enum to string
|
||||
function convertProtoEnumToClineAsk(ask: ClineAsk): AppClineAsk | undefined {
|
||||
if (ask === ClineAsk.UNRECOGNIZED) {
|
||||
console.warn("Received UNRECOGNIZED ClineAsk enum value")
|
||||
return undefined
|
||||
}
|
||||
|
||||
const mapping: Record<Exclude<ClineAsk, ClineAsk.UNRECOGNIZED>, AppClineAsk> = {
|
||||
[ClineAsk.FOLLOWUP]: "followup",
|
||||
[ClineAsk.PLAN_MODE_RESPOND]: "plan_mode_respond",
|
||||
[ClineAsk.COMMAND]: "command",
|
||||
[ClineAsk.COMMAND_OUTPUT]: "command_output",
|
||||
[ClineAsk.COMPLETION_RESULT]: "completion_result",
|
||||
[ClineAsk.TOOL]: "tool",
|
||||
[ClineAsk.API_REQ_FAILED]: "api_req_failed",
|
||||
[ClineAsk.RESUME_TASK]: "resume_task",
|
||||
[ClineAsk.RESUME_COMPLETED_TASK]: "resume_completed_task",
|
||||
[ClineAsk.MISTAKE_LIMIT_REACHED]: "mistake_limit_reached",
|
||||
[ClineAsk.AUTO_APPROVAL_MAX_REQ_REACHED]: "auto_approval_max_req_reached",
|
||||
[ClineAsk.BROWSER_ACTION_LAUNCH]: "browser_action_launch",
|
||||
[ClineAsk.USE_MCP_SERVER]: "use_mcp_server",
|
||||
[ClineAsk.NEW_TASK]: "new_task",
|
||||
[ClineAsk.CONDENSE]: "condense",
|
||||
[ClineAsk.REPORT_BUG]: "report_bug",
|
||||
}
|
||||
|
||||
return mapping[ask]
|
||||
}
|
||||
|
||||
// Helper function to convert ClineSay string to enum
|
||||
function convertClineSayToProtoEnum(say: AppClineSay | undefined): ClineSay | undefined {
|
||||
if (!say) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const mapping: Record<AppClineSay, ClineSay> = {
|
||||
task: ClineSay.TASK,
|
||||
error: ClineSay.ERROR,
|
||||
api_req_started: ClineSay.API_REQ_STARTED,
|
||||
api_req_finished: ClineSay.API_REQ_FINISHED,
|
||||
text: ClineSay.TEXT,
|
||||
reasoning: ClineSay.REASONING,
|
||||
completion_result: ClineSay.COMPLETION_RESULT_SAY,
|
||||
user_feedback: ClineSay.USER_FEEDBACK,
|
||||
user_feedback_diff: ClineSay.USER_FEEDBACK_DIFF,
|
||||
api_req_retried: ClineSay.API_REQ_RETRIED,
|
||||
command: ClineSay.COMMAND_SAY,
|
||||
command_output: ClineSay.COMMAND_OUTPUT_SAY,
|
||||
tool: ClineSay.TOOL_SAY,
|
||||
shell_integration_warning: ClineSay.SHELL_INTEGRATION_WARNING,
|
||||
browser_action_launch: ClineSay.BROWSER_ACTION_LAUNCH_SAY,
|
||||
browser_action: ClineSay.BROWSER_ACTION,
|
||||
browser_action_result: ClineSay.BROWSER_ACTION_RESULT,
|
||||
mcp_server_request_started: ClineSay.MCP_SERVER_REQUEST_STARTED,
|
||||
mcp_server_response: ClineSay.MCP_SERVER_RESPONSE,
|
||||
use_mcp_server: ClineSay.USE_MCP_SERVER_SAY,
|
||||
diff_error: ClineSay.DIFF_ERROR,
|
||||
deleted_api_reqs: ClineSay.DELETED_API_REQS,
|
||||
clineignore_error: ClineSay.CLINEIGNORE_ERROR,
|
||||
checkpoint_created: ClineSay.CHECKPOINT_CREATED,
|
||||
load_mcp_documentation: ClineSay.LOAD_MCP_DOCUMENTATION,
|
||||
info: ClineSay.INFO,
|
||||
}
|
||||
|
||||
const result = mapping[say]
|
||||
if (result === undefined) {
|
||||
console.warn(`Unknown ClineSay value: ${say}`)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to convert ClineSay enum to string
|
||||
function convertProtoEnumToClineSay(say: ClineSay): AppClineSay | undefined {
|
||||
if (say === ClineSay.UNRECOGNIZED) {
|
||||
console.warn("Received UNRECOGNIZED ClineSay enum value")
|
||||
return undefined
|
||||
}
|
||||
|
||||
const mapping: Record<Exclude<ClineSay, ClineSay.UNRECOGNIZED>, AppClineSay> = {
|
||||
[ClineSay.TASK]: "task",
|
||||
[ClineSay.ERROR]: "error",
|
||||
[ClineSay.API_REQ_STARTED]: "api_req_started",
|
||||
[ClineSay.API_REQ_FINISHED]: "api_req_finished",
|
||||
[ClineSay.TEXT]: "text",
|
||||
[ClineSay.REASONING]: "reasoning",
|
||||
[ClineSay.COMPLETION_RESULT_SAY]: "completion_result",
|
||||
[ClineSay.USER_FEEDBACK]: "user_feedback",
|
||||
[ClineSay.USER_FEEDBACK_DIFF]: "user_feedback_diff",
|
||||
[ClineSay.API_REQ_RETRIED]: "api_req_retried",
|
||||
[ClineSay.COMMAND_SAY]: "command",
|
||||
[ClineSay.COMMAND_OUTPUT_SAY]: "command_output",
|
||||
[ClineSay.TOOL_SAY]: "tool",
|
||||
[ClineSay.SHELL_INTEGRATION_WARNING]: "shell_integration_warning",
|
||||
[ClineSay.BROWSER_ACTION_LAUNCH_SAY]: "browser_action_launch",
|
||||
[ClineSay.BROWSER_ACTION]: "browser_action",
|
||||
[ClineSay.BROWSER_ACTION_RESULT]: "browser_action_result",
|
||||
[ClineSay.MCP_SERVER_REQUEST_STARTED]: "mcp_server_request_started",
|
||||
[ClineSay.MCP_SERVER_RESPONSE]: "mcp_server_response",
|
||||
[ClineSay.USE_MCP_SERVER_SAY]: "use_mcp_server",
|
||||
[ClineSay.DIFF_ERROR]: "diff_error",
|
||||
[ClineSay.DELETED_API_REQS]: "deleted_api_reqs",
|
||||
[ClineSay.CLINEIGNORE_ERROR]: "clineignore_error",
|
||||
[ClineSay.CHECKPOINT_CREATED]: "checkpoint_created",
|
||||
[ClineSay.LOAD_MCP_DOCUMENTATION]: "load_mcp_documentation",
|
||||
[ClineSay.INFO]: "info",
|
||||
}
|
||||
|
||||
return mapping[say]
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert application ClineMessage to proto ClineMessage
|
||||
*/
|
||||
export function convertClineMessageToProto(message: AppClineMessage): ProtoClineMessage {
|
||||
// For sending messages, we need to provide values for required proto fields
|
||||
const askEnum = message.ask ? convertClineAskToProtoEnum(message.ask) : undefined
|
||||
const sayEnum = message.say ? convertClineSayToProtoEnum(message.say) : undefined
|
||||
|
||||
// Determine appropriate enum values based on message type
|
||||
let finalAskEnum: ClineAsk = ClineAsk.FOLLOWUP // Proto default
|
||||
let finalSayEnum: ClineSay = ClineSay.TEXT // Proto default
|
||||
|
||||
if (message.type === "ask") {
|
||||
finalAskEnum = askEnum ?? ClineAsk.FOLLOWUP // Use FOLLOWUP as default for ask messages
|
||||
} else if (message.type === "say") {
|
||||
finalSayEnum = sayEnum ?? ClineSay.TEXT // Use TEXT as default for say messages
|
||||
}
|
||||
|
||||
const protoMessage: ProtoClineMessage = {
|
||||
ts: message.ts,
|
||||
type: message.type === "ask" ? ClineMessageType.ASK : ClineMessageType.SAY,
|
||||
ask: finalAskEnum,
|
||||
say: finalSayEnum,
|
||||
text: message.text ?? "",
|
||||
reasoning: message.reasoning ?? "",
|
||||
images: message.images ?? [],
|
||||
files: message.files ?? [],
|
||||
partial: message.partial ?? false,
|
||||
lastCheckpointHash: message.lastCheckpointHash ?? "",
|
||||
isCheckpointCheckedOut: message.isCheckpointCheckedOut ?? false,
|
||||
isOperationOutsideWorkspace: message.isOperationOutsideWorkspace ?? false,
|
||||
conversationHistoryIndex: message.conversationHistoryIndex ?? 0,
|
||||
conversationHistoryDeletedRange: message.conversationHistoryDeletedRange
|
||||
? {
|
||||
startIndex: message.conversationHistoryDeletedRange[0],
|
||||
endIndex: message.conversationHistoryDeletedRange[1],
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
|
||||
return protoMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert proto ClineMessage to application ClineMessage
|
||||
*/
|
||||
export function convertProtoToClineMessage(protoMessage: ProtoClineMessage): AppClineMessage {
|
||||
const message: AppClineMessage = {
|
||||
ts: protoMessage.ts,
|
||||
type: protoMessage.type === ClineMessageType.ASK ? "ask" : "say",
|
||||
}
|
||||
|
||||
// Convert ask enum to string
|
||||
if (protoMessage.type === ClineMessageType.ASK) {
|
||||
const ask = convertProtoEnumToClineAsk(protoMessage.ask)
|
||||
if (ask !== undefined) {
|
||||
message.ask = ask
|
||||
}
|
||||
}
|
||||
|
||||
// Convert say enum to string
|
||||
if (protoMessage.type === ClineMessageType.SAY) {
|
||||
const say = convertProtoEnumToClineSay(protoMessage.say)
|
||||
if (say !== undefined) {
|
||||
message.say = say
|
||||
}
|
||||
}
|
||||
|
||||
// Convert other fields - preserve empty strings as they may be intentional
|
||||
if (protoMessage.text !== "") {
|
||||
message.text = protoMessage.text
|
||||
}
|
||||
if (protoMessage.reasoning !== "") {
|
||||
message.reasoning = protoMessage.reasoning
|
||||
}
|
||||
if (protoMessage.images.length > 0) {
|
||||
message.images = protoMessage.images
|
||||
}
|
||||
if (protoMessage.files.length > 0) {
|
||||
message.files = protoMessage.files
|
||||
}
|
||||
if (protoMessage.partial) {
|
||||
message.partial = protoMessage.partial
|
||||
}
|
||||
if (protoMessage.lastCheckpointHash !== "") {
|
||||
message.lastCheckpointHash = protoMessage.lastCheckpointHash
|
||||
}
|
||||
if (protoMessage.isCheckpointCheckedOut) {
|
||||
message.isCheckpointCheckedOut = protoMessage.isCheckpointCheckedOut
|
||||
}
|
||||
if (protoMessage.isOperationOutsideWorkspace) {
|
||||
message.isOperationOutsideWorkspace = protoMessage.isOperationOutsideWorkspace
|
||||
}
|
||||
if (protoMessage.conversationHistoryIndex !== 0) {
|
||||
message.conversationHistoryIndex = protoMessage.conversationHistoryIndex
|
||||
}
|
||||
|
||||
// Convert conversationHistoryDeletedRange from object to tuple
|
||||
if (protoMessage.conversationHistoryDeletedRange) {
|
||||
message.conversationHistoryDeletedRange = [
|
||||
protoMessage.conversationHistoryDeletedRange.startIndex,
|
||||
protoMessage.conversationHistoryDeletedRange.endIndex,
|
||||
]
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -2,6 +2,7 @@ import React, { createContext, useCallback, useContext, useEffect, useRef, useSt
|
||||
import { useEvent } from "react-use"
|
||||
import { EmptyRequest } from "@shared/proto/common"
|
||||
import { WebviewProviderType as WebviewProviderTypeEnum, WebviewProviderTypeRequest } from "@shared/proto/ui"
|
||||
import { convertProtoToClineMessage } from "@shared/proto-conversions/cline-message"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
@@ -206,21 +207,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
setFilePaths(message.filePaths ?? [])
|
||||
break
|
||||
}
|
||||
case "partialMessage": {
|
||||
const partialMessage = message.partialMessage!
|
||||
setState((prevState) => {
|
||||
// worth noting it will never be possible for a more up-to-date message to be sent here or in normal messages post since the presentAssistantContent function uses lock
|
||||
const lastIndex = findLastIndex(prevState.clineMessages, (msg) => msg.ts === partialMessage.ts)
|
||||
if (lastIndex !== -1) {
|
||||
const newClineMessages = [...prevState.clineMessages]
|
||||
newClineMessages[lastIndex] = partialMessage
|
||||
return { ...prevState, clineMessages: newClineMessages }
|
||||
}
|
||||
return prevState
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "openRouterModels": {
|
||||
const updatedModels = message.openRouterModels ?? {}
|
||||
setOpenRouterModels({
|
||||
@@ -264,6 +250,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
const chatButtonUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
const accountButtonClickedSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
const settingsButtonClickedSubscriptionRef = useRef<(() => void) | null>(null)
|
||||
const partialMessageUnsubscribeRef = useRef<(() => void) | null>(null)
|
||||
|
||||
// Subscribe to state updates and UI events using the gRPC streaming API
|
||||
useEffect(() => {
|
||||
@@ -412,6 +399,43 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
},
|
||||
)
|
||||
|
||||
// Subscribe to partial message events
|
||||
partialMessageUnsubscribeRef.current = UiServiceClient.subscribeToPartialMessage(EmptyRequest.create({}), {
|
||||
onResponse: (protoMessage) => {
|
||||
try {
|
||||
console.log("[PARTIAL] Received partialMessage event from gRPC stream")
|
||||
|
||||
// Validate critical fields
|
||||
if (!protoMessage.ts || protoMessage.ts <= 0) {
|
||||
console.error("Invalid timestamp in partial message:", protoMessage)
|
||||
return
|
||||
}
|
||||
|
||||
const partialMessage = convertProtoToClineMessage(protoMessage)
|
||||
console.log("[PARTIAL] Partial message:", partialMessage)
|
||||
console.log("\n")
|
||||
setState((prevState) => {
|
||||
// worth noting it will never be possible for a more up-to-date message to be sent here or in normal messages post since the presentAssistantContent function uses lock
|
||||
const lastIndex = findLastIndex(prevState.clineMessages, (msg) => msg.ts === partialMessage.ts)
|
||||
if (lastIndex !== -1) {
|
||||
const newClineMessages = [...prevState.clineMessages]
|
||||
newClineMessages[lastIndex] = partialMessage
|
||||
return { ...prevState, clineMessages: newClineMessages }
|
||||
}
|
||||
return prevState
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to process partial message:", error, protoMessage)
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in partialMessage subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("[DEBUG] partialMessage subscription completed")
|
||||
},
|
||||
})
|
||||
|
||||
// Still send the webviewDidLaunch message for other initialization
|
||||
vscode.postMessage({ type: "webviewDidLaunch" })
|
||||
|
||||
@@ -456,6 +480,10 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
settingsButtonClickedSubscriptionRef.current()
|
||||
settingsButtonClickedSubscriptionRef.current = null
|
||||
}
|
||||
if (partialMessageUnsubscribeRef.current) {
|
||||
partialMessageUnsubscribeRef.current()
|
||||
partialMessageUnsubscribeRef.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user