diff --git a/.changeset/wise-spies-type.md b/.changeset/wise-spies-type.md new file mode 100644 index 0000000000..8c1e83fc18 --- /dev/null +++ b/.changeset/wise-spies-type.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Clean up types related to tools diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 91885a2876..69278bd125 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -12,20 +12,14 @@ import getFolderSize from "get-folder-size" import { serializeError } from "serialize-error" import * as vscode from "vscode" +// schemas import { TokenUsage } from "../schemas" + +// api import { ApiHandler, buildApiHandler } from "../api" import { ApiStream } from "../api/transform/stream" -import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider" -import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../services/checkpoints" -import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" -import { fetchInstructionsTool } from "./tools/fetchInstructionsTool" -import { listFilesTool } from "./tools/listFilesTool" -import { readFileTool } from "./tools/readFileTool" -import { ExitCodeDetails, TerminalProcess } from "../integrations/terminal/TerminalProcess" -import { Terminal } from "../integrations/terminal/Terminal" -import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry" -import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" -import { listFiles } from "../services/glob/list-files" + +// shared import { ApiConfiguration } from "../shared/api" import { findLastIndex } from "../shared/array" import { combineApiRequests } from "../shared/combineApiRequests" @@ -42,26 +36,35 @@ import { getApiMetrics } from "../shared/getApiMetrics" import { HistoryItem } from "../shared/HistoryItem" import { ClineAskResponse } from "../shared/WebviewMessage" import { GlobalFileNames } from "../shared/globalFileNames" -import { defaultModeSlug, getModeBySlug, getFullModeDetails } from "../shared/modes" +import { defaultModeSlug, getModeBySlug, getFullModeDetails, isToolAllowedForMode } from "../shared/modes" import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments" +import { formatLanguage } from "../shared/language" +import { ToolParamName, ToolName, ToolResponse } from "../shared/tools" + +// services +import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" +import { listFiles } from "../services/glob/list-files" +import { BrowserSession } from "../services/browser/BrowserSession" +import { McpHub } from "../services/mcp/McpHub" +import { telemetryService } from "../services/telemetry/TelemetryService" +import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../services/checkpoints" + +// integrations +import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider" +import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" +import { ExitCodeDetails, TerminalProcess } from "../integrations/terminal/TerminalProcess" +import { Terminal } from "../integrations/terminal/Terminal" +import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry" + +// utils import { calculateApiCostAnthropic } from "../utils/cost" import { fileExistsAtPath } from "../utils/fs" -import { arePathsEqual } from "../utils/path" -import { parseMentions } from "./mentions" -import { FileContextTracker } from "./context-tracking/FileContextTracker" -import { RooIgnoreController } from "./ignore/RooIgnoreController" -import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message" -import { formatResponse } from "./prompts/responses" -import { SYSTEM_PROMPT } from "./prompts/system" -import { truncateConversationIfNeeded } from "./sliding-window" -import { ClineProvider } from "./webview/ClineProvider" -import { BrowserSession } from "../services/browser/BrowserSession" -import { formatLanguage } from "../shared/language" -import { McpHub } from "../services/mcp/McpHub" -import { DiffStrategy, getDiffStrategy } from "./diff/DiffStrategy" -import { telemetryService } from "../services/telemetry/TelemetryService" -import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validator" -import { getWorkspacePath } from "../utils/path" +import { arePathsEqual, getWorkspacePath } from "../utils/path" + +// tools +import { fetchInstructionsTool } from "./tools/fetchInstructionsTool" +import { listFilesTool } from "./tools/listFilesTool" +import { readFileTool } from "./tools/readFileTool" import { writeToFileTool } from "./tools/writeToFileTool" import { applyDiffTool } from "./tools/applyDiffTool" import { insertContentTool } from "./tools/insertContentTool" @@ -78,7 +81,20 @@ import { attemptCompletionTool } from "./tools/attemptCompletionTool" import { newTaskTool } from "./tools/newTaskTool" import { appendToFileTool } from "./tools/appendToFileTool" -export type ToolResponse = string | Array +// prompts +import { formatResponse } from "./prompts/responses" +import { SYSTEM_PROMPT } from "./prompts/system" + +// ... everything else +import { parseMentions } from "./mentions" +import { FileContextTracker } from "./context-tracking/FileContextTracker" +import { RooIgnoreController } from "./ignore/RooIgnoreController" +import { type AssistantMessageContent, parseAssistantMessage } from "./assistant-message" +import { truncateConversationIfNeeded } from "./sliding-window" +import { ClineProvider } from "./webview/ClineProvider" +import { DiffStrategy, getDiffStrategy } from "./diff/DiffStrategy" +import { validateToolUse } from "./mode-validator" + type UserContent = Array export type ClineEvents = { @@ -573,7 +589,7 @@ export class Cline extends EventEmitter { } } - async sayAndCreateMissingParamError(toolName: ToolUseName, paramName: string, relPath?: string) { + async sayAndCreateMissingParamError(toolName: ToolName, paramName: string, relPath?: string) { await this.say( "error", `Roo tried to use ${toolName}${ diff --git a/src/core/__tests__/mode-validator.test.ts b/src/core/__tests__/mode-validator.test.ts index fee41971c6..66b23ff2ed 100644 --- a/src/core/__tests__/mode-validator.test.ts +++ b/src/core/__tests__/mode-validator.test.ts @@ -1,5 +1,5 @@ import { isToolAllowedForMode, getModeConfig, modes, ModeConfig } from "../../shared/modes" -import { TOOL_GROUPS } from "../../shared/tool-groups" +import { TOOL_GROUPS } from "../../shared/tools" import { validateToolUse } from "../mode-validator" const [codeMode, architectMode, askMode] = modes.map((mode) => mode.slug) diff --git a/src/core/__tests__/read-file-maxReadFileLine.test.ts b/src/core/__tests__/read-file-maxReadFileLine.test.ts index bbbbcb37eb..3a3f7e97bb 100644 --- a/src/core/__tests__/read-file-maxReadFileLine.test.ts +++ b/src/core/__tests__/read-file-maxReadFileLine.test.ts @@ -1,11 +1,11 @@ import * as path from "path" + import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" import { extractTextFromFile, addLineNumbers } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { isBinaryFile } from "isbinaryfile" -import { ReadFileToolUse } from "../assistant-message" -import { Cline } from "../Cline" +import { ReadFileToolUse } from "../../shared/tools" // Mock dependencies jest.mock("../../integrations/misc/line-counter") diff --git a/src/core/__tests__/read-file-xml.test.ts b/src/core/__tests__/read-file-xml.test.ts index 6b995d18b8..46ca065514 100644 --- a/src/core/__tests__/read-file-xml.test.ts +++ b/src/core/__tests__/read-file-xml.test.ts @@ -1,11 +1,11 @@ import * as path from "path" + import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" -import { extractTextFromFile, addLineNumbers } from "../../integrations/misc/extract-text" +import { extractTextFromFile } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { isBinaryFile } from "isbinaryfile" -import { ReadFileToolUse } from "../assistant-message" -import { Cline } from "../Cline" +import { ReadFileToolUse } from "../../shared/tools" // Mock dependencies jest.mock("../../integrations/misc/line-counter") diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index 0a1bec32f2..c53e88ed96 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -1,156 +1 @@ -export type AssistantMessageContent = TextContent | ToolUse - -export { parseAssistantMessage } from "./parse-assistant-message" - -export interface TextContent { - type: "text" - content: string - partial: boolean -} - -export const toolUseNames = [ - "execute_command", - "read_file", - "write_to_file", - "append_to_file", - "apply_diff", - "insert_content", - "search_and_replace", - "search_files", - "list_files", - "list_code_definition_names", - "browser_action", - "use_mcp_tool", - "access_mcp_resource", - "ask_followup_question", - "attempt_completion", - "switch_mode", - "new_task", - "fetch_instructions", -] as const - -// Converts array of tool call names into a union type ("execute_command" | "read_file" | ...) -export type ToolUseName = (typeof toolUseNames)[number] - -export const toolParamNames = [ - "command", - "path", - "content", - "line_count", - "regex", - "file_pattern", - "recursive", - "action", - "url", - "coordinate", - "text", - "server_name", - "tool_name", - "arguments", - "uri", - "question", - "result", - "diff", - "start_line", - "end_line", - "mode_slug", - "reason", - "operations", - "mode", - "message", - "cwd", - "follow_up", - "task", - "size", -] as const - -export type ToolParamName = (typeof toolParamNames)[number] - -export interface ToolUse { - type: "tool_use" - name: ToolUseName - // params is a partial record, allowing only some or none of the possible parameters to be used - params: Partial> - partial: boolean -} - -export interface ExecuteCommandToolUse extends ToolUse { - name: "execute_command" - // Pick, "command"> makes "command" required, but Partial<> makes it optional - params: Partial, "command" | "cwd">> -} - -export interface ReadFileToolUse extends ToolUse { - name: "read_file" - params: Partial, "path" | "start_line" | "end_line">> -} - -export interface FetchInstructionsToolUse extends ToolUse { - name: "fetch_instructions" - params: Partial, "task">> -} - -export interface WriteToFileToolUse extends ToolUse { - name: "write_to_file" - params: Partial, "path" | "content" | "line_count">> -} - -export interface AppendToFileToolUse extends ToolUse { - name: "append_to_file" - params: Partial, "path" | "content">> -} - -export interface InsertCodeBlockToolUse extends ToolUse { - name: "insert_content" - params: Partial, "path" | "operations">> -} - -export interface SearchFilesToolUse extends ToolUse { - name: "search_files" - params: Partial, "path" | "regex" | "file_pattern">> -} - -export interface ListFilesToolUse extends ToolUse { - name: "list_files" - params: Partial, "path" | "recursive">> -} - -export interface ListCodeDefinitionNamesToolUse extends ToolUse { - name: "list_code_definition_names" - params: Partial, "path">> -} - -export interface BrowserActionToolUse extends ToolUse { - name: "browser_action" - params: Partial, "action" | "url" | "coordinate" | "text" | "size">> -} - -export interface UseMcpToolToolUse extends ToolUse { - name: "use_mcp_tool" - params: Partial, "server_name" | "tool_name" | "arguments">> -} - -export interface AccessMcpResourceToolUse extends ToolUse { - name: "access_mcp_resource" - params: Partial, "server_name" | "uri">> -} - -export interface AskFollowupQuestionToolUse extends ToolUse { - name: "ask_followup_question" - params: Partial, "question" | "follow_up">> -} - -export interface AttemptCompletionToolUse extends ToolUse { - name: "attempt_completion" - params: Partial, "result" | "command">> -} - -export interface SwitchModeToolUse extends ToolUse { - name: "switch_mode" - params: Partial, "mode_slug" | "reason">> -} - -export interface NewTaskToolUse extends ToolUse { - name: "new_task" - params: Partial, "mode" | "message">> -} +export { type AssistantMessageContent, parseAssistantMessage } from "./parse-assistant-message" diff --git a/src/core/assistant-message/parse-assistant-message.ts b/src/core/assistant-message/parse-assistant-message.ts index e38e8f6458..aa97873701 100644 --- a/src/core/assistant-message/parse-assistant-message.ts +++ b/src/core/assistant-message/parse-assistant-message.ts @@ -1,12 +1,6 @@ -import { - AssistantMessageContent, - TextContent, - ToolUse, - ToolParamName, - toolParamNames, - toolUseNames, - ToolUseName, -} from "." +import { TextContent, ToolUse, ToolParamName, toolParamNames, toolNames, ToolName } from "../../shared/tools" + +export type AssistantMessageContent = TextContent | ToolUse export function parseAssistantMessage(assistantMessage: string) { let contentBlocks: AssistantMessageContent[] = [] @@ -84,13 +78,13 @@ export function parseAssistantMessage(assistantMessage: string) { // no currentToolUse let didStartToolUse = false - const possibleToolUseOpeningTags = toolUseNames.map((name) => `<${name}>`) + const possibleToolUseOpeningTags = toolNames.map((name) => `<${name}>`) for (const toolUseOpeningTag of possibleToolUseOpeningTags) { if (accumulator.endsWith(toolUseOpeningTag)) { // start of a new tool use currentToolUse = { type: "tool_use", - name: toolUseOpeningTag.slice(1, -1) as ToolUseName, + name: toolUseOpeningTag.slice(1, -1) as ToolName, params: {}, partial: true, } diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 5ba1825bac..a9d4ba6560 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -1,8 +1,9 @@ +import { distance } from "fastest-levenshtein" + import { DiffStrategy, DiffResult } from "../types" import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" -import { distance } from "fastest-levenshtein" import { ToolProgressStatus } from "../../../shared/ExtensionMessage" -import { ToolUse } from "../../assistant-message" +import { ToolUse } from "../../../shared/tools" import { normalizeString } from "../../../utils/text-normalization" const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches diff --git a/src/core/diff/types.ts b/src/core/diff/types.ts index 68097710fb..0cb5686ecb 100644 --- a/src/core/diff/types.ts +++ b/src/core/diff/types.ts @@ -2,8 +2,8 @@ * Interface for implementing different diff strategies */ +import { ToolUse } from "../../shared/tools" import { ToolProgressStatus } from "../../shared/ExtensionMessage" -import { ToolUse } from "../assistant-message" export type DiffResult = | { success: true; content: string; failParts?: DiffResult[] } diff --git a/src/core/mode-validator.ts b/src/core/mode-validator.ts index 415c06cc36..8a9ac881c7 100644 --- a/src/core/mode-validator.ts +++ b/src/core/mode-validator.ts @@ -1,8 +1,5 @@ +import { ToolName } from "../shared/tools" import { Mode, isToolAllowedForMode, ModeConfig } from "../shared/modes" -import { ToolName } from "../shared/tool-groups" - -export { isToolAllowedForMode } -export type { ToolName } export function validateToolUse( toolName: ToolName, diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 2fee9b78ad..642b9fd652 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -18,7 +18,7 @@ import { getNewTaskDescription } from "./new-task" import { DiffStrategy } from "../../diff/DiffStrategy" import { McpHub } from "../../../services/mcp/McpHub" import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes" -import { ToolName, TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tool-groups" +import { ToolName, TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools" import { ToolArgs } from "./types" // Map of tool names to their description functions diff --git a/src/core/tools/__tests__/appendToFileTool.test.ts b/src/core/tools/__tests__/appendToFileTool.test.ts index ec61d7f0b4..b16921abdb 100644 --- a/src/core/tools/__tests__/appendToFileTool.test.ts +++ b/src/core/tools/__tests__/appendToFileTool.test.ts @@ -1,13 +1,12 @@ // npx jest src/core/tools/__tests__/appendToFileTool.test.ts import { describe, expect, it, jest, beforeEach } from "@jest/globals" + import { appendToFileTool } from "../appendToFileTool" import { Cline } from "../../Cline" -import { ToolUse } from "../../assistant-message" import { formatResponse } from "../../prompts/responses" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../types" -import { ClineSayTool, ClineAsk } from "../../../shared/ExtensionMessage" -import { RecordSource } from "../../context-tracking/FileContextTrackerTypes" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" +import { ClineAsk } from "../../../shared/ExtensionMessage" import { FileContextTracker } from "../../context-tracking/FileContextTracker" import { DiffViewProvider } from "../../../integrations/editor/DiffViewProvider" import { RooIgnoreController } from "../../ignore/RooIgnoreController" diff --git a/src/core/tools/__tests__/executeCommandTool.test.ts b/src/core/tools/__tests__/executeCommandTool.test.ts index 2c975b7809..859d79ad7f 100644 --- a/src/core/tools/__tests__/executeCommandTool.test.ts +++ b/src/core/tools/__tests__/executeCommandTool.test.ts @@ -1,12 +1,11 @@ // npx jest src/core/tools/__tests__/executeCommandTool.test.ts import { describe, expect, it, jest, beforeEach } from "@jest/globals" + import { executeCommandTool } from "../executeCommandTool" import { Cline } from "../../Cline" -import { ToolUse } from "../../assistant-message" import { formatResponse } from "../../prompts/responses" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../types" -import { ClineAsk } from "../../../schemas" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" // Mock dependencies jest.mock("../../Cline") diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts index 94bea9062c..ced110f8b6 100644 --- a/src/core/tools/accessMcpResourceTool.ts +++ b/src/core/tools/accessMcpResourceTool.ts @@ -1,7 +1,5 @@ import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" -import { RemoveClosingTag } from "./types" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult } from "./types" +import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" import { Cline } from "../Cline" import { formatResponse } from "../prompts/responses" diff --git a/src/core/tools/appendToFileTool.ts b/src/core/tools/appendToFileTool.ts index d0d0b9dd3c..a812677ae8 100644 --- a/src/core/tools/appendToFileTool.ts +++ b/src/core/tools/appendToFileTool.ts @@ -1,18 +1,16 @@ -import * as vscode from "vscode" +import path from "path" +import delay from "delay" import { Cline } from "../Cline" import { ClineSayTool } from "../../shared/ExtensionMessage" -import { ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" -import path from "path" import { fileExistsAtPath } from "../../utils/fs" import { addLineNumbers, stripLineNumbers } from "../../integrations/misc/extract-text" import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { everyLineHasLineNumbers } from "../../integrations/misc/extract-text" -import delay from "delay" import { unescapeHtmlEntities } from "../../utils/text-normalization" export async function appendToFileTool( diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index 43de5af988..433d23a42b 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -1,17 +1,18 @@ -import { ClineSayTool } from "../../shared/ExtensionMessage" -import { getReadablePath } from "../../utils/path" -import { ToolUse } from "../assistant-message" -import { Cline } from "../Cline" -import { RemoveClosingTag } from "./types" -import { formatResponse } from "../prompts/responses" -import { AskApproval, HandleError, PushToolResult } from "./types" -import { fileExistsAtPath } from "../../utils/fs" -import { addLineNumbers } from "../../integrations/misc/extract-text" import path from "path" import fs from "fs/promises" + +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import { Cline } from "../Cline" +import { ToolUse, RemoveClosingTag } from "../../shared/tools" +import { formatResponse } from "../prompts/responses" +import { AskApproval, HandleError, PushToolResult } from "../../shared/tools" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers } from "../../integrations/misc/extract-text" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { telemetryService } from "../../services/telemetry/TelemetryService" import { unescapeHtmlEntities } from "../../utils/text-normalization" + export async function applyDiffTool( cline: Cline, block: ToolUse, diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts index 5ed06e2403..2e7263ad58 100644 --- a/src/core/tools/askFollowupQuestionTool.ts +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -1,6 +1,5 @@ import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { parseXml } from "../../utils/xml" diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index 437e803d31..891673969e 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -1,18 +1,18 @@ -import { ToolResponse } from "../Cline" +import Anthropic from "@anthropic-ai/sdk" -import { ToolUse } from "../assistant-message" import { Cline } from "../Cline" import { + ToolResponse, + ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolDescription, AskFinishSubTaskApproval, -} from "./types" +} from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { telemetryService } from "../../services/telemetry/TelemetryService" -import Anthropic from "@anthropic-ai/sdk" export async function attemptCompletionTool( cline: Cline, diff --git a/src/core/tools/browserActionTool.ts b/src/core/tools/browserActionTool.ts index 406a9f1fad..c3f02821c1 100644 --- a/src/core/tools/browserActionTool.ts +++ b/src/core/tools/browserActionTool.ts @@ -1,6 +1,5 @@ import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { BrowserAction, BrowserActionResult, diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 5a70c657ac..8c54200bd7 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -1,6 +1,5 @@ import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { unescapeHtmlEntities } from "../../utils/text-normalization" diff --git a/src/core/tools/fetchInstructionsTool.ts b/src/core/tools/fetchInstructionsTool.ts index 3e185301db..eaa27737e9 100644 --- a/src/core/tools/fetchInstructionsTool.ts +++ b/src/core/tools/fetchInstructionsTool.ts @@ -1,9 +1,8 @@ import { Cline } from "../Cline" import { fetchInstructions } from "../prompts/instructions/instructions" import { ClineSayTool } from "../../shared/ExtensionMessage" -import { ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" -import { AskApproval, HandleError, PushToolResult } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../shared/tools" export async function fetchInstructionsTool( cline: Cline, diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index 24cf6c57b6..f05407f502 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -1,15 +1,15 @@ +import delay from "delay" +import fs from "fs/promises" + import { getReadablePath } from "../../utils/path" import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { ClineSayTool } from "../../shared/ExtensionMessage" import path from "path" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" import { insertGroups } from "../diff/insert-groups" -import delay from "delay" -import fs from "fs/promises" export async function insertContentTool( cline: Cline, diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts index 6d6a2db3e9..8487367e2b 100644 --- a/src/core/tools/listCodeDefinitionNamesTool.ts +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -1,11 +1,10 @@ -import { ToolUse } from "../assistant-message" -import { HandleError, PushToolResult, RemoveClosingTag } from "./types" -import { Cline } from "../Cline" -import { AskApproval } from "./types" -import { ClineSayTool } from "../../shared/ExtensionMessage" -import { getReadablePath } from "../../utils/path" import path from "path" import fs from "fs/promises" + +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { Cline } from "../Cline" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" import { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index efc9226e0a..a010191f75 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -1,11 +1,12 @@ import * as path from "path" + import { Cline } from "../Cline" import { ClineSayTool } from "../../shared/ExtensionMessage" -import { ToolParamName, ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" import { listFiles } from "../../services/glob/list-files" import { getReadablePath } from "../../utils/path" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" + /** * Implements the list_files tool. * diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts index 57e290c26b..d6c94dd838 100644 --- a/src/core/tools/newTaskTool.ts +++ b/src/core/tools/newTaskTool.ts @@ -1,10 +1,9 @@ -import { ToolUse } from "../assistant-message" -import { HandleError, PushToolResult, RemoveClosingTag } from "./types" +import delay from "delay" + +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { Cline } from "../Cline" -import { AskApproval } from "./types" import { defaultModeSlug, getModeBySlug } from "../../shared/modes" import { formatResponse } from "../prompts/responses" -import delay from "delay" export async function newTaskTool( cline: Cline, diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index fdb74109c3..022ec4321c 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -1,10 +1,11 @@ import path from "path" +import { isBinaryFile } from "isbinaryfile" + import { Cline } from "../Cline" import { ClineSayTool } from "../../shared/ExtensionMessage" -import { ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" import { t } from "../../i18n" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { getReadablePath } from "../../utils/path" @@ -12,7 +13,6 @@ import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" import { extractTextFromFile, addLineNumbers } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" -import { isBinaryFile } from "isbinaryfile" export async function readFileTool( cline: Cline, diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 6996c9361e..7b88405e37 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -1,13 +1,13 @@ +import path from "path" +import fs from "fs/promises" + import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" -import path from "path" import { fileExistsAtPath } from "../../utils/fs" import { addLineNumbers } from "../../integrations/misc/extract-text" -import fs from "fs/promises" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" export async function searchAndReplaceTool( diff --git a/src/core/tools/searchFilesTool.ts b/src/core/tools/searchFilesTool.ts index e3659da9a1..3cf651a0db 100644 --- a/src/core/tools/searchFilesTool.ts +++ b/src/core/tools/searchFilesTool.ts @@ -1,9 +1,9 @@ +import path from "path" + import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { ClineSayTool } from "../../shared/ExtensionMessage" import { getReadablePath } from "../../utils/path" -import path from "path" import { regexSearchFiles } from "../../services/ripgrep" export async function searchFilesTool( diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts index 48e6e59fe0..595eb04290 100644 --- a/src/core/tools/switchModeTool.ts +++ b/src/core/tools/switchModeTool.ts @@ -1,11 +1,10 @@ -import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" -import { formatResponse } from "../prompts/responses" -import { defaultModeSlug } from "../../shared/modes" -import { getModeBySlug } from "../../shared/modes" import delay from "delay" +import { Cline } from "../Cline" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { formatResponse } from "../prompts/responses" +import { defaultModeSlug, getModeBySlug } from "../../shared/modes" + export async function switchModeTool( cline: Cline, block: ToolUse, diff --git a/src/core/tools/types.ts b/src/core/tools/types.ts deleted file mode 100644 index 5b027241f6..0000000000 --- a/src/core/tools/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ClineAsk, ToolProgressStatus } from "../../schemas" -import { ToolParamName } from "../assistant-message" -import { ToolResponse } from "../Cline" - -export type AskApproval = ( - type: ClineAsk, - partialMessage?: string, - progressStatus?: ToolProgressStatus, -) => Promise - -export type HandleError = (action: string, error: Error) => Promise - -export type PushToolResult = (content: ToolResponse) => void - -export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string - -export type AskFinishSubTaskApproval = () => Promise - -export type ToolDescription = () => string diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts index 699f693a13..f89a2938b7 100644 --- a/src/core/tools/useMcpToolTool.ts +++ b/src/core/tools/useMcpToolTool.ts @@ -1,6 +1,5 @@ import { Cline } from "../Cline" -import { ToolUse } from "../assistant-message" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index 6a012537f6..89dd010254 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -1,18 +1,16 @@ +import path from "path" +import delay from "delay" import * as vscode from "vscode" import { Cline } from "../Cline" import { ClineSayTool } from "../../shared/ExtensionMessage" -import { ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" -import path from "path" import { fileExistsAtPath } from "../../utils/fs" -import { addLineNumbers, stripLineNumbers } from "../../integrations/misc/extract-text" +import { addLineNumbers, stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/misc/extract-text" import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" -import { everyLineHasLineNumbers } from "../../integrations/misc/extract-text" -import delay from "delay" import { detectCodeOmission } from "../../integrations/editor/detect-omission" import { unescapeHtmlEntities } from "../../utils/text-normalization" diff --git a/src/services/telemetry/PostHogClient.ts b/src/services/telemetry/PostHogClient.ts new file mode 100644 index 0000000000..c968d17d01 --- /dev/null +++ b/src/services/telemetry/PostHogClient.ts @@ -0,0 +1,147 @@ +import { PostHog } from "posthog-node" +import * as vscode from "vscode" + +import { logger } from "../../utils/logging" + +// This forward declaration is needed to avoid circular dependencies +export interface ClineProviderInterface { + // Gets telemetry properties to attach to every event + getTelemetryProperties(): Promise> +} + +/** + * PostHogClient handles telemetry event tracking for the Roo Code extension + * Uses PostHog analytics to track user interactions and system events + * Respects user privacy settings and VSCode's global telemetry configuration + */ +export class PostHogClient { + public static readonly EVENTS = { + TASK: { + CREATED: "Task Created", + RESTARTED: "Task Reopened", + COMPLETED: "Task Completed", + CONVERSATION_MESSAGE: "Conversation Message", + MODE_SWITCH: "Mode Switched", + TOOL_USED: "Tool Used", + CHECKPOINT_CREATED: "Checkpoint Created", + CHECKPOINT_RESTORED: "Checkpoint Restored", + CHECKPOINT_DIFFED: "Checkpoint Diffed", + CODE_ACTION_USED: "Code Action Used", + PROMPT_ENHANCED: "Prompt Enhanced", + }, + ERRORS: { + SCHEMA_VALIDATION_ERROR: "Schema Validation Error", + DIFF_APPLICATION_ERROR: "Diff Application Error", + CONSECUTIVE_MISTAKE_ERROR: "Consecutive Mistake Error", + }, + } + + private static instance: PostHogClient + private client: PostHog + private distinctId: string = vscode.env.machineId + private telemetryEnabled: boolean = false + private providerRef: WeakRef | null = null + + private constructor() { + this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { host: "https://us.i.posthog.com" }) + } + + /** + * Updates the telemetry state based on user preferences and VSCode settings + * Only enables telemetry if both VSCode global telemetry is enabled and user has opted in + * @param didUserOptIn Whether the user has explicitly opted into telemetry + */ + public updateTelemetryState(didUserOptIn: boolean): void { + this.telemetryEnabled = false + + // First check global telemetry level - telemetry should only be enabled when level is "all" + const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get("telemetryLevel", "all") + const globalTelemetryEnabled = telemetryLevel === "all" + + // We only enable telemetry if global vscode telemetry is enabled + if (globalTelemetryEnabled) { + this.telemetryEnabled = didUserOptIn + } + + // Update PostHog client state based on telemetry preference + if (this.telemetryEnabled) { + this.client.optIn() + } else { + this.client.optOut() + } + } + + /** + * Gets or creates the singleton instance of PostHogClient + * @returns The PostHogClient instance + */ + public static getInstance(): PostHogClient { + if (!PostHogClient.instance) { + PostHogClient.instance = new PostHogClient() + } + + return PostHogClient.instance + } + + /** + * Sets the ClineProvider reference to use for global properties + * @param provider A ClineProvider instance to use + */ + public setProvider(provider: ClineProviderInterface): void { + this.providerRef = new WeakRef(provider) + logger.debug("PostHogClient: ClineProvider reference set") + } + + /** + * Captures a telemetry event if telemetry is enabled + * @param event The event to capture with its properties + */ + public async capture(event: { event: string; properties?: any }): Promise { + // Only send events if telemetry is enabled + if (this.telemetryEnabled) { + // Get global properties from ClineProvider if available + let globalProperties: Record = {} + const provider = this.providerRef?.deref() + + if (provider) { + try { + // Get the telemetry properties directly from the provider + globalProperties = await provider.getTelemetryProperties() + } catch (error) { + // Log error but continue with capturing the event + logger.error( + `Error getting telemetry properties: ${error instanceof Error ? error.message : String(error)}`, + ) + } + } + + // Merge global properties with event-specific properties + // Event properties take precedence in case of conflicts + const mergedProperties = { + ...globalProperties, + ...(event.properties || {}), + } + + this.client.capture({ + distinctId: this.distinctId, + event: event.event, + properties: mergedProperties, + }) + } + } + + /** + * Checks if telemetry is currently enabled + * @returns Whether telemetry is enabled + */ + public isTelemetryEnabled(): boolean { + return this.telemetryEnabled + } + + /** + * Shuts down the PostHog client + */ + public async shutdown(): Promise { + await this.client.shutdown() + } +} diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts index 863f78ac93..c37c9d8ee4 100644 --- a/src/services/telemetry/TelemetryService.ts +++ b/src/services/telemetry/TelemetryService.ts @@ -1,152 +1,7 @@ -import { PostHog } from "posthog-node" -import * as vscode from "vscode" import { ZodError } from "zod" import { logger } from "../../utils/logging" - -// This forward declaration is needed to avoid circular dependencies -interface ClineProviderInterface { - // Gets telemetry properties to attach to every event - getTelemetryProperties(): Promise> -} - -/** - * PostHogClient handles telemetry event tracking for the Roo Code extension - * Uses PostHog analytics to track user interactions and system events - * Respects user privacy settings and VSCode's global telemetry configuration - */ -class PostHogClient { - public static readonly EVENTS = { - TASK: { - CREATED: "Task Created", - RESTARTED: "Task Reopened", - COMPLETED: "Task Completed", - CONVERSATION_MESSAGE: "Conversation Message", - MODE_SWITCH: "Mode Switched", - TOOL_USED: "Tool Used", - CHECKPOINT_CREATED: "Checkpoint Created", - CHECKPOINT_RESTORED: "Checkpoint Restored", - CHECKPOINT_DIFFED: "Checkpoint Diffed", - CODE_ACTION_USED: "Code Action Used", - PROMPT_ENHANCED: "Prompt Enhanced", - }, - ERRORS: { - SCHEMA_VALIDATION_ERROR: "Schema Validation Error", - DIFF_APPLICATION_ERROR: "Diff Application Error", - CONSECUTIVE_MISTAKE_ERROR: "Consecutive Mistake Error", - }, - } - - private static instance: PostHogClient - private client: PostHog - private distinctId: string = vscode.env.machineId - private telemetryEnabled: boolean = false - private providerRef: WeakRef | null = null - - private constructor() { - this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { - host: "https://us.i.posthog.com", - }) - } - - /** - * Updates the telemetry state based on user preferences and VSCode settings - * Only enables telemetry if both VSCode global telemetry is enabled and user has opted in - * @param didUserOptIn Whether the user has explicitly opted into telemetry - */ - public updateTelemetryState(didUserOptIn: boolean): void { - this.telemetryEnabled = false - - // First check global telemetry level - telemetry should only be enabled when level is "all" - const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get("telemetryLevel", "all") - const globalTelemetryEnabled = telemetryLevel === "all" - - // We only enable telemetry if global vscode telemetry is enabled - if (globalTelemetryEnabled) { - this.telemetryEnabled = didUserOptIn - } - - // Update PostHog client state based on telemetry preference - if (this.telemetryEnabled) { - this.client.optIn() - } else { - this.client.optOut() - } - } - - /** - * Gets or creates the singleton instance of PostHogClient - * @returns The PostHogClient instance - */ - public static getInstance(): PostHogClient { - if (!PostHogClient.instance) { - PostHogClient.instance = new PostHogClient() - } - return PostHogClient.instance - } - - /** - * Sets the ClineProvider reference to use for global properties - * @param provider A ClineProvider instance to use - */ - public setProvider(provider: ClineProviderInterface): void { - this.providerRef = new WeakRef(provider) - logger.debug("PostHogClient: ClineProvider reference set") - } - - /** - * Captures a telemetry event if telemetry is enabled - * @param event The event to capture with its properties - */ - public async capture(event: { event: string; properties?: any }): Promise { - // Only send events if telemetry is enabled - if (this.telemetryEnabled) { - // Get global properties from ClineProvider if available - let globalProperties: Record = {} - const provider = this.providerRef?.deref() - - if (provider) { - try { - // Get the telemetry properties directly from the provider - globalProperties = await provider.getTelemetryProperties() - } catch (error) { - // Log error but continue with capturing the event - logger.error( - `Error getting telemetry properties: ${error instanceof Error ? error.message : String(error)}`, - ) - } - } - - // Merge global properties with event-specific properties - // Event properties take precedence in case of conflicts - const mergedProperties = { - ...globalProperties, - ...(event.properties || {}), - } - - this.client.capture({ - distinctId: this.distinctId, - event: event.event, - properties: mergedProperties, - }) - } - } - - /** - * Checks if telemetry is currently enabled - * @returns Whether telemetry is enabled - */ - public isTelemetryEnabled(): boolean { - return this.telemetryEnabled - } - - /** - * Shuts down the PostHog client - */ - public async shutdown(): Promise { - await this.client.shutdown() - } -} +import { PostHogClient, ClineProviderInterface } from "./PostHogClient" /** * TelemetryService wrapper class that defers PostHogClient initialization @@ -155,7 +10,6 @@ class PostHogClient { class TelemetryService { private client: PostHogClient | null = null private initialized = false - private providerRef: WeakRef | null = null /** * Initialize the telemetry service with the PostHog client @@ -179,12 +33,11 @@ class TelemetryService { * @param provider A ClineProvider instance to use */ public setProvider(provider: ClineProviderInterface): void { - // Keep a weak reference to avoid memory leaks - this.providerRef = new WeakRef(provider) // If client is initialized, pass the provider reference - if (this.isReady()) { + if (this.isReady) { this.client!.setProvider(provider) } + logger.debug("TelemetryService: ClineProvider reference set") } @@ -193,7 +46,7 @@ class TelemetryService { * Checks if the service is initialized before performing any operation * @returns Whether the service is ready to use */ - private isReady(): boolean { + private get isReady(): boolean { return this.initialized && this.client !== null } @@ -202,7 +55,10 @@ class TelemetryService { * @param didUserOptIn Whether the user has explicitly opted into telemetry */ public updateTelemetryState(didUserOptIn: boolean): void { - if (!this.isReady()) return + if (!this.isReady) { + return + } + this.client!.updateTelemetryState(didUserOptIn) } @@ -211,7 +67,10 @@ class TelemetryService { * @param event The event to capture with its properties */ public capture(event: { event: string; properties?: any }): void { - if (!this.isReady()) return + if (!this.isReady) { + return + } + this.client!.capture(event) } @@ -238,24 +97,15 @@ class TelemetryService { } public captureConversationMessage(taskId: string, source: "user" | "assistant"): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CONVERSATION_MESSAGE, { - taskId, - source, - }) + this.captureEvent(PostHogClient.EVENTS.TASK.CONVERSATION_MESSAGE, { taskId, source }) } public captureModeSwitch(taskId: string, newMode: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.MODE_SWITCH, { - taskId, - newMode, - }) + this.captureEvent(PostHogClient.EVENTS.TASK.MODE_SWITCH, { taskId, newMode }) } public captureToolUsage(taskId: string, tool: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.TOOL_USED, { - taskId, - tool, - }) + this.captureEvent(PostHogClient.EVENTS.TASK.TOOL_USED, { taskId, tool }) } public captureCheckpointCreated(taskId: string): void { @@ -271,36 +121,24 @@ class TelemetryService { } public captureCodeActionUsed(actionType: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.CODE_ACTION_USED, { - actionType, - }) + this.captureEvent(PostHogClient.EVENTS.TASK.CODE_ACTION_USED, { actionType }) } public capturePromptEnhanced(taskId?: string): void { - this.captureEvent(PostHogClient.EVENTS.TASK.PROMPT_ENHANCED, { - ...(taskId && { taskId }), - }) + this.captureEvent(PostHogClient.EVENTS.TASK.PROMPT_ENHANCED, { ...(taskId && { taskId }) }) } public captureSchemaValidationError({ schemaName, error }: { schemaName: string; error: ZodError }): void { - this.captureEvent(PostHogClient.EVENTS.ERRORS.SCHEMA_VALIDATION_ERROR, { - schemaName, - // https://zod.dev/ERROR_HANDLING?id=formatting-errors - error: error.format(), - }) + // https://zod.dev/ERROR_HANDLING?id=formatting-errors + this.captureEvent(PostHogClient.EVENTS.ERRORS.SCHEMA_VALIDATION_ERROR, { schemaName, error: error.format() }) } public captureDiffApplicationError(taskId: string, consecutiveMistakeCount: number): void { - this.captureEvent(PostHogClient.EVENTS.ERRORS.DIFF_APPLICATION_ERROR, { - taskId, - consecutiveMistakeCount, - }) + this.captureEvent(PostHogClient.EVENTS.ERRORS.DIFF_APPLICATION_ERROR, { taskId, consecutiveMistakeCount }) } public captureConsecutiveMistakeError(taskId: string): void { - this.captureEvent(PostHogClient.EVENTS.ERRORS.CONSECUTIVE_MISTAKE_ERROR, { - taskId, - }) + this.captureEvent(PostHogClient.EVENTS.ERRORS.CONSECUTIVE_MISTAKE_ERROR, { taskId }) } /** @@ -308,15 +146,17 @@ class TelemetryService { * @returns Whether telemetry is enabled */ public isTelemetryEnabled(): boolean { - if (!this.isReady()) return false - return this.client!.isTelemetryEnabled() + return this.isReady && this.client!.isTelemetryEnabled() } /** * Shuts down the PostHog client */ public async shutdown(): Promise { - if (!this.isReady()) return + if (!this.isReady) { + return + } + await this.client!.shutdown() } } diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 0f51b4e50f..9aa4d2eb86 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode" import { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts } from "../schemas" -import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups" +import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tools" import { addCustomInstructions } from "../core/prompts/sections/custom-instructions" export type Mode = string diff --git a/src/shared/tool-groups.ts b/src/shared/tool-groups.ts deleted file mode 100644 index f124f8bd2e..0000000000 --- a/src/shared/tool-groups.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { ToolGroup } from "../schemas" - -// Define tool group configuration -export type ToolGroupConfig = { - tools: readonly string[] - alwaysAvailable?: boolean // Whether this group is always available and shouldn't show in prompts view -} - -// Map of tool slugs to their display names -export const TOOL_DISPLAY_NAMES = { - execute_command: "run commands", - read_file: "read files", - fetch_instructions: "fetch instructions", - write_to_file: "write files", - append_to_file: "append to files", - apply_diff: "apply changes", - search_files: "search files", - list_files: "list files", - list_code_definition_names: "list definitions", - browser_action: "use a browser", - use_mcp_tool: "use mcp tools", - access_mcp_resource: "access mcp resources", - ask_followup_question: "ask questions", - attempt_completion: "complete tasks", - switch_mode: "switch modes", - new_task: "create new task", -} as const - -export type { ToolGroup } - -// Define available tool groups -export const TOOL_GROUPS: Record = { - read: { - tools: ["read_file", "fetch_instructions", "search_files", "list_files", "list_code_definition_names"], - }, - edit: { - tools: ["apply_diff", "write_to_file", "append_to_file", "insert_content", "search_and_replace"], - }, - browser: { - tools: ["browser_action"], - }, - command: { - tools: ["execute_command"], - }, - mcp: { - tools: ["use_mcp_tool", "access_mcp_resource"], - }, - modes: { - tools: ["switch_mode", "new_task"], - alwaysAvailable: true, - }, -} - -// Tools that are always available to all modes -export const ALWAYS_AVAILABLE_TOOLS = [ - "ask_followup_question", - "attempt_completion", - "switch_mode", - "new_task", -] as const - -// Tool name types for type safety -export type ToolName = keyof typeof TOOL_DISPLAY_NAMES - -// Tool helper functions -export function getToolName(toolConfig: string | readonly [ToolName, ...any[]]): ToolName { - return typeof toolConfig === "string" ? (toolConfig as ToolName) : toolConfig[0] -} - -export function getToolOptions(toolConfig: string | readonly [ToolName, ...any[]]): any { - return typeof toolConfig === "string" ? undefined : toolConfig[1] -} diff --git a/src/shared/tools.ts b/src/shared/tools.ts new file mode 100644 index 0000000000..7dd12893a3 --- /dev/null +++ b/src/shared/tools.ts @@ -0,0 +1,236 @@ +import { Anthropic } from "@anthropic-ai/sdk" + +import { ClineAsk, ToolProgressStatus, ToolGroup } from "../schemas" + +export type ToolResponse = string | Array + +export type AskApproval = ( + type: ClineAsk, + partialMessage?: string, + progressStatus?: ToolProgressStatus, +) => Promise + +export type HandleError = (action: string, error: Error) => Promise + +export type PushToolResult = (content: ToolResponse) => void + +export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string + +export type AskFinishSubTaskApproval = () => Promise + +export type ToolDescription = () => string + +export interface TextContent { + type: "text" + content: string + partial: boolean +} + +export const toolNames = [ + "execute_command", + "read_file", + "write_to_file", + "append_to_file", + "apply_diff", + "insert_content", + "search_and_replace", + "search_files", + "list_files", + "list_code_definition_names", + "browser_action", + "use_mcp_tool", + "access_mcp_resource", + "ask_followup_question", + "attempt_completion", + "switch_mode", + "new_task", + "fetch_instructions", +] as const + +export type ToolName = (typeof toolNames)[number] + +export const toolParamNames = [ + "command", + "path", + "content", + "line_count", + "regex", + "file_pattern", + "recursive", + "action", + "url", + "coordinate", + "text", + "server_name", + "tool_name", + "arguments", + "uri", + "question", + "result", + "diff", + "start_line", + "end_line", + "mode_slug", + "reason", + "operations", + "mode", + "message", + "cwd", + "follow_up", + "task", + "size", +] as const + +export type ToolParamName = (typeof toolParamNames)[number] + +export interface ToolUse { + type: "tool_use" + name: ToolName + // params is a partial record, allowing only some or none of the possible parameters to be used + params: Partial> + partial: boolean +} + +export interface ExecuteCommandToolUse extends ToolUse { + name: "execute_command" + // Pick, "command"> makes "command" required, but Partial<> makes it optional + params: Partial, "command" | "cwd">> +} + +export interface ReadFileToolUse extends ToolUse { + name: "read_file" + params: Partial, "path" | "start_line" | "end_line">> +} + +export interface FetchInstructionsToolUse extends ToolUse { + name: "fetch_instructions" + params: Partial, "task">> +} + +export interface WriteToFileToolUse extends ToolUse { + name: "write_to_file" + params: Partial, "path" | "content" | "line_count">> +} + +export interface InsertCodeBlockToolUse extends ToolUse { + name: "insert_content" + params: Partial, "path" | "operations">> +} + +export interface SearchFilesToolUse extends ToolUse { + name: "search_files" + params: Partial, "path" | "regex" | "file_pattern">> +} + +export interface ListFilesToolUse extends ToolUse { + name: "list_files" + params: Partial, "path" | "recursive">> +} + +export interface ListCodeDefinitionNamesToolUse extends ToolUse { + name: "list_code_definition_names" + params: Partial, "path">> +} + +export interface BrowserActionToolUse extends ToolUse { + name: "browser_action" + params: Partial, "action" | "url" | "coordinate" | "text" | "size">> +} + +export interface UseMcpToolToolUse extends ToolUse { + name: "use_mcp_tool" + params: Partial, "server_name" | "tool_name" | "arguments">> +} + +export interface AccessMcpResourceToolUse extends ToolUse { + name: "access_mcp_resource" + params: Partial, "server_name" | "uri">> +} + +export interface AskFollowupQuestionToolUse extends ToolUse { + name: "ask_followup_question" + params: Partial, "question" | "follow_up">> +} + +export interface AttemptCompletionToolUse extends ToolUse { + name: "attempt_completion" + params: Partial, "result" | "command">> +} + +export interface SwitchModeToolUse extends ToolUse { + name: "switch_mode" + params: Partial, "mode_slug" | "reason">> +} + +export interface NewTaskToolUse extends ToolUse { + name: "new_task" + params: Partial, "mode" | "message">> +} + +export type ToolUsage = Record< + ToolName, + { + attempts: number + failures: number + } +> + +// Define tool group configuration +export type ToolGroupConfig = { + tools: readonly string[] + alwaysAvailable?: boolean // Whether this group is always available and shouldn't show in prompts view +} + +export const TOOL_DISPLAY_NAMES: Record = { + execute_command: "run commands", + read_file: "read files", + fetch_instructions: "fetch instructions", + write_to_file: "write files", + append_to_file: "append to files", + apply_diff: "apply changes", + search_files: "search files", + list_files: "list files", + list_code_definition_names: "list definitions", + browser_action: "use a browser", + use_mcp_tool: "use mcp tools", + access_mcp_resource: "access mcp resources", + ask_followup_question: "ask questions", + attempt_completion: "complete tasks", + switch_mode: "switch modes", + new_task: "create new task", + insert_content: "insert content", + search_and_replace: "search and replace", +} as const + +export type { ToolGroup } + +// Define available tool groups. +export const TOOL_GROUPS: Record = { + read: { + tools: ["read_file", "fetch_instructions", "search_files", "list_files", "list_code_definition_names"], + }, + edit: { + tools: ["apply_diff", "write_to_file", "append_to_file", "insert_content", "search_and_replace"], + }, + browser: { + tools: ["browser_action"], + }, + command: { + tools: ["execute_command"], + }, + mcp: { + tools: ["use_mcp_tool", "access_mcp_resource"], + }, + modes: { + tools: ["switch_mode", "new_task"], + alwaysAvailable: true, + }, +} + +// Tools that are always available to all modes. +export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ + "ask_followup_question", + "attempt_completion", + "switch_mode", + "new_task", +] as const diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 95664e266f..b3ea68a3dd 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -9,6 +9,7 @@ import { VSCodeRadioGroup, VSCodeRadio, } from "@vscode/webview-ui-toolkit/react" + import { useExtensionState } from "../../context/ExtensionStateContext" import { Mode, @@ -22,7 +23,7 @@ import { import { modeConfigSchema } from "../../../../src/schemas" import { supportPrompt, SupportPromptType } from "../../../../src/shared/support-prompt" -import { TOOL_GROUPS, ToolGroup } from "../../../../src/shared/tool-groups" +import { TOOL_GROUPS, ToolGroup } from "../../../../src/shared/tools" import { vscode } from "../../utils/vscode" import { Tab, TabContent, TabHeader } from "../common/Tab" import i18next from "i18next"