Kaynağa Gözat

Clean up types related to tools (#2719)

Chris Estreich 8 ay önce
ebeveyn
işleme
471caff000
38 değiştirilmiş dosya ile 536 ekleme ve 559 silme
  1. 5 0
      .changeset/wise-spies-type.md
  2. 45 29
      src/core/Cline.ts
  3. 1 1
      src/core/__tests__/mode-validator.test.ts
  4. 2 2
      src/core/__tests__/read-file-maxReadFileLine.test.ts
  5. 3 3
      src/core/__tests__/read-file-xml.test.ts
  6. 1 156
      src/core/assistant-message/index.ts
  7. 5 11
      src/core/assistant-message/parse-assistant-message.ts
  8. 3 2
      src/core/diff/strategies/multi-search-replace.ts
  9. 1 1
      src/core/diff/types.ts
  10. 1 4
      src/core/mode-validator.ts
  11. 1 1
      src/core/prompts/tools/index.ts
  12. 3 4
      src/core/tools/__tests__/appendToFileTool.test.ts
  13. 2 3
      src/core/tools/__tests__/executeCommandTool.test.ts
  14. 1 3
      src/core/tools/accessMcpResourceTool.ts
  15. 3 5
      src/core/tools/appendToFileTool.ts
  16. 6 5
      src/core/tools/applyDiffTool.ts
  17. 1 2
      src/core/tools/askFollowupQuestionTool.ts
  18. 4 4
      src/core/tools/attemptCompletionTool.ts
  19. 1 2
      src/core/tools/browserActionTool.ts
  20. 1 2
      src/core/tools/executeCommandTool.ts
  21. 1 2
      src/core/tools/fetchInstructionsTool.ts
  22. 4 4
      src/core/tools/insertContentTool.ts
  23. 4 5
      src/core/tools/listCodeDefinitionNamesTool.ts
  24. 3 2
      src/core/tools/listFilesTool.ts
  25. 3 4
      src/core/tools/newTaskTool.ts
  26. 3 3
      src/core/tools/readFileTool.ts
  27. 4 4
      src/core/tools/searchAndReplaceTool.ts
  28. 3 3
      src/core/tools/searchFilesTool.ts
  29. 4 5
      src/core/tools/switchModeTool.ts
  30. 0 19
      src/core/tools/types.ts
  31. 1 2
      src/core/tools/useMcpToolTool.ts
  32. 4 6
      src/core/tools/writeToFileTool.ts
  33. 147 0
      src/services/telemetry/PostHogClient.ts
  34. 26 186
      src/services/telemetry/TelemetryService.ts
  35. 1 1
      src/shared/modes.ts
  36. 0 72
      src/shared/tool-groups.ts
  37. 236 0
      src/shared/tools.ts
  38. 2 1
      webview-ui/src/components/prompts/PromptsView.tsx

+ 5 - 0
.changeset/wise-spies-type.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Clean up types related to tools

+ 45 - 29
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 { 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 { 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 { DiffStrategy, getDiffStrategy } from "./diff/DiffStrategy"
 import { telemetryService } from "../services/telemetry/TelemetryService"
-import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validator"
-import { getWorkspacePath } from "../utils/path"
+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, 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<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
+// 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<Anthropic.Messages.ContentBlockParam>
 
 export type ClineEvents = {
@@ -573,7 +589,7 @@ export class Cline extends EventEmitter<ClineEvents> {
 		}
 	}
 
-	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}${

+ 1 - 1
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)

+ 2 - 2
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")

+ 3 - 3
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")

+ 1 - 156
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<Record<ToolParamName, string>>
-	partial: boolean
-}
-
-export interface ExecuteCommandToolUse extends ToolUse {
-	name: "execute_command"
-	// Pick<Record<ToolParamName, string>, "command"> makes "command" required, but Partial<> makes it optional
-	params: Partial<Pick<Record<ToolParamName, string>, "command" | "cwd">>
-}
-
-export interface ReadFileToolUse extends ToolUse {
-	name: "read_file"
-	params: Partial<Pick<Record<ToolParamName, string>, "path" | "start_line" | "end_line">>
-}
-
-export interface FetchInstructionsToolUse extends ToolUse {
-	name: "fetch_instructions"
-	params: Partial<Pick<Record<ToolParamName, string>, "task">>
-}
-
-export interface WriteToFileToolUse extends ToolUse {
-	name: "write_to_file"
-	params: Partial<Pick<Record<ToolParamName, string>, "path" | "content" | "line_count">>
-}
-
-export interface AppendToFileToolUse extends ToolUse {
-	name: "append_to_file"
-	params: Partial<Pick<Record<ToolParamName, string>, "path" | "content">>
-}
-
-export interface InsertCodeBlockToolUse extends ToolUse {
-	name: "insert_content"
-	params: Partial<Pick<Record<ToolParamName, string>, "path" | "operations">>
-}
-
-export interface SearchFilesToolUse extends ToolUse {
-	name: "search_files"
-	params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
-}
-
-export interface ListFilesToolUse extends ToolUse {
-	name: "list_files"
-	params: Partial<Pick<Record<ToolParamName, string>, "path" | "recursive">>
-}
-
-export interface ListCodeDefinitionNamesToolUse extends ToolUse {
-	name: "list_code_definition_names"
-	params: Partial<Pick<Record<ToolParamName, string>, "path">>
-}
-
-export interface BrowserActionToolUse extends ToolUse {
-	name: "browser_action"
-	params: Partial<Pick<Record<ToolParamName, string>, "action" | "url" | "coordinate" | "text" | "size">>
-}
-
-export interface UseMcpToolToolUse extends ToolUse {
-	name: "use_mcp_tool"
-	params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "tool_name" | "arguments">>
-}
-
-export interface AccessMcpResourceToolUse extends ToolUse {
-	name: "access_mcp_resource"
-	params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "uri">>
-}
-
-export interface AskFollowupQuestionToolUse extends ToolUse {
-	name: "ask_followup_question"
-	params: Partial<Pick<Record<ToolParamName, string>, "question" | "follow_up">>
-}
-
-export interface AttemptCompletionToolUse extends ToolUse {
-	name: "attempt_completion"
-	params: Partial<Pick<Record<ToolParamName, string>, "result" | "command">>
-}
-
-export interface SwitchModeToolUse extends ToolUse {
-	name: "switch_mode"
-	params: Partial<Pick<Record<ToolParamName, string>, "mode_slug" | "reason">>
-}
-
-export interface NewTaskToolUse extends ToolUse {
-	name: "new_task"
-	params: Partial<Pick<Record<ToolParamName, string>, "mode" | "message">>
-}
+export { type AssistantMessageContent, parseAssistantMessage } from "./parse-assistant-message"

+ 5 - 11
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,
 				}

+ 3 - 2
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

+ 1 - 1
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[] }

+ 1 - 4
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,

+ 1 - 1
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

+ 3 - 4
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"

+ 2 - 3
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")

+ 1 - 3
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"
 

+ 3 - 5
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(

+ 6 - 5
src/core/tools/applyDiffTool.ts

@@ -1,17 +1,18 @@
+import path from "path"
+import fs from "fs/promises"
+
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { getReadablePath } from "../../utils/path"
-import { ToolUse } from "../assistant-message"
 import { Cline } from "../Cline"
-import { RemoveClosingTag } from "./types"
+import { ToolUse, RemoveClosingTag } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
-import { AskApproval, HandleError, PushToolResult } from "./types"
+import { AskApproval, HandleError, PushToolResult } from "../../shared/tools"
 import { fileExistsAtPath } from "../../utils/fs"
 import { addLineNumbers } from "../../integrations/misc/extract-text"
-import path from "path"
-import fs from "fs/promises"
 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,

+ 1 - 2
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"
 

+ 4 - 4
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,

+ 1 - 2
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,

+ 1 - 2
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"
 

+ 1 - 2
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,

+ 4 - 4
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,

+ 4 - 5
src/core/tools/listCodeDefinitionNamesTool.ts

@@ -1,11 +1,10 @@
-import { ToolUse } from "../assistant-message"
-import { HandleError, PushToolResult, RemoveClosingTag } from "./types"
+import path from "path"
+import fs from "fs/promises"
+
+import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 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 { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
 import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
 

+ 3 - 2
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.
  *

+ 3 - 4
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,

+ 3 - 3
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,

+ 4 - 4
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(

+ 3 - 3
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(

+ 4 - 5
src/core/tools/switchModeTool.ts

@@ -1,10 +1,9 @@
+import delay from "delay"
+
 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 { defaultModeSlug } from "../../shared/modes"
-import { getModeBySlug } from "../../shared/modes"
-import delay from "delay"
+import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
 
 export async function switchModeTool(
 	cline: Cline,

+ 0 - 19
src/core/tools/types.ts

@@ -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<boolean>
-
-export type HandleError = (action: string, error: Error) => Promise<void>
-
-export type PushToolResult = (content: ToolResponse) => void
-
-export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string
-
-export type AskFinishSubTaskApproval = () => Promise<boolean>
-
-export type ToolDescription = () => string

+ 1 - 2
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"
 

+ 4 - 6
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"
 

+ 147 - 0
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<Record<string, any>>
+}
+
+/**
+ * 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<ClineProviderInterface> | 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<string>("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<void> {
+		// Only send events if telemetry is enabled
+		if (this.telemetryEnabled) {
+			// Get global properties from ClineProvider if available
+			let globalProperties: Record<string, any> = {}
+			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<void> {
+		await this.client.shutdown()
+	}
+}

+ 26 - 186
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<Record<string, any>>
-}
-
-/**
- * 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<ClineProviderInterface> | 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<string>("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<void> {
-		// Only send events if telemetry is enabled
-		if (this.telemetryEnabled) {
-			// Get global properties from ClineProvider if available
-			let globalProperties: Record<string, any> = {}
-			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<void> {
-		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<ClineProviderInterface> | 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<void> {
-		if (!this.isReady()) return
+		if (!this.isReady) {
+			return
+		}
+
 		await this.client!.shutdown()
 	}
 }

+ 1 - 1
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

+ 0 - 72
src/shared/tool-groups.ts

@@ -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<ToolGroup, ToolGroupConfig> = {
-	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]
-}

+ 236 - 0
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<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
+
+export type AskApproval = (
+	type: ClineAsk,
+	partialMessage?: string,
+	progressStatus?: ToolProgressStatus,
+) => Promise<boolean>
+
+export type HandleError = (action: string, error: Error) => Promise<void>
+
+export type PushToolResult = (content: ToolResponse) => void
+
+export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string
+
+export type AskFinishSubTaskApproval = () => Promise<boolean>
+
+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<Record<ToolParamName, string>>
+	partial: boolean
+}
+
+export interface ExecuteCommandToolUse extends ToolUse {
+	name: "execute_command"
+	// Pick<Record<ToolParamName, string>, "command"> makes "command" required, but Partial<> makes it optional
+	params: Partial<Pick<Record<ToolParamName, string>, "command" | "cwd">>
+}
+
+export interface ReadFileToolUse extends ToolUse {
+	name: "read_file"
+	params: Partial<Pick<Record<ToolParamName, string>, "path" | "start_line" | "end_line">>
+}
+
+export interface FetchInstructionsToolUse extends ToolUse {
+	name: "fetch_instructions"
+	params: Partial<Pick<Record<ToolParamName, string>, "task">>
+}
+
+export interface WriteToFileToolUse extends ToolUse {
+	name: "write_to_file"
+	params: Partial<Pick<Record<ToolParamName, string>, "path" | "content" | "line_count">>
+}
+
+export interface InsertCodeBlockToolUse extends ToolUse {
+	name: "insert_content"
+	params: Partial<Pick<Record<ToolParamName, string>, "path" | "operations">>
+}
+
+export interface SearchFilesToolUse extends ToolUse {
+	name: "search_files"
+	params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
+}
+
+export interface ListFilesToolUse extends ToolUse {
+	name: "list_files"
+	params: Partial<Pick<Record<ToolParamName, string>, "path" | "recursive">>
+}
+
+export interface ListCodeDefinitionNamesToolUse extends ToolUse {
+	name: "list_code_definition_names"
+	params: Partial<Pick<Record<ToolParamName, string>, "path">>
+}
+
+export interface BrowserActionToolUse extends ToolUse {
+	name: "browser_action"
+	params: Partial<Pick<Record<ToolParamName, string>, "action" | "url" | "coordinate" | "text" | "size">>
+}
+
+export interface UseMcpToolToolUse extends ToolUse {
+	name: "use_mcp_tool"
+	params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "tool_name" | "arguments">>
+}
+
+export interface AccessMcpResourceToolUse extends ToolUse {
+	name: "access_mcp_resource"
+	params: Partial<Pick<Record<ToolParamName, string>, "server_name" | "uri">>
+}
+
+export interface AskFollowupQuestionToolUse extends ToolUse {
+	name: "ask_followup_question"
+	params: Partial<Pick<Record<ToolParamName, string>, "question" | "follow_up">>
+}
+
+export interface AttemptCompletionToolUse extends ToolUse {
+	name: "attempt_completion"
+	params: Partial<Pick<Record<ToolParamName, string>, "result" | "command">>
+}
+
+export interface SwitchModeToolUse extends ToolUse {
+	name: "switch_mode"
+	params: Partial<Pick<Record<ToolParamName, string>, "mode_slug" | "reason">>
+}
+
+export interface NewTaskToolUse extends ToolUse {
+	name: "new_task"
+	params: Partial<Pick<Record<ToolParamName, string>, "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<ToolName, string> = {
+	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<ToolGroup, ToolGroupConfig> = {
+	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

+ 2 - 1
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"