Compare commits

...

2 Commits

Author SHA1 Message Date
abeatrix 347cdcb5a7 clean up 2025-11-03 16:20:37 -08:00
abeatrix 2315e49c82 feat(api): add provider ID to ApiHandler for telemetry and configuration
- Added `id: string` to `ApiHandler` interface and implemented as `public readonly id` in all provider classes (Anthropic, AskSage, Baseten, etc.)
- Updated `AnthropicHandler` constructor to accept optional `id` parameter, defaulting to "anthropic"
- Added `getModelInfo()` method to all providers, returning `{ providerId, model }`
- Refactored `ListFilesToolHandler` to use `config.api.id` and `config.api.getModel().id` for telemetry, removing manual provider extraction

This enables consistent provider identification across the API layer, improving telemetry accuracy and simplifying provider-specific logic.
2025-11-03 16:18:58 -08:00
51 changed files with 107 additions and 200 deletions
+3 -2
View File
@@ -47,6 +47,7 @@ export type CommonApiHandlerOptions = {
}
export interface ApiHandler {
id: string
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream
getModel(): ApiHandlerModel
getApiStreamUsage?(): Promise<ApiStreamUsageChunk | undefined>
@@ -75,7 +76,7 @@ function createHandlerForProvider(
): ApiHandler {
switch (apiProvider) {
case "anthropic":
return new AnthropicHandler({
return new AnthropicHandler("anthropic", {
onRetryAttempt: options.onRetryAttempt,
apiKey: options.apiKey,
anthropicBaseUrl: options.anthropicBaseUrl,
@@ -406,7 +407,7 @@ function createHandlerForProvider(
hicapModelId: mode === "plan" ? options.planModeHicapModelId : options.actModeHicapModelId,
})
default:
return new AnthropicHandler({
return new AnthropicHandler(apiProvider, {
onRetryAttempt: options.onRetryAttempt,
apiKey: options.apiKey,
anthropicBaseUrl: options.anthropicBaseUrl,
+4 -1
View File
@@ -18,7 +18,10 @@ export class AnthropicHandler implements ApiHandler {
private options: AnthropicHandlerOptions
private client: Anthropic | undefined
constructor(options: AnthropicHandlerOptions) {
constructor(
public readonly id = "anthropic",
options: AnthropicHandlerOptions,
) {
this.options = options
}
+1
View File
@@ -30,6 +30,7 @@ type AskSageResponse = {
}
export class AskSageHandler implements ApiHandler {
public readonly id = "asksage"
private options: AskSageHandlerOptions
private apiUrl: string
private apiKey: string
+1
View File
@@ -15,6 +15,7 @@ interface BasetenHandlerOptions extends CommonApiHandlerOptions {
}
export class BasetenHandler implements ApiHandler {
public readonly id = "baseten"
private options: BasetenHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -114,6 +114,7 @@ const JP_SUPPORTED_CRIS_MODELS = ["anthropic.claude-sonnet-4-5-20250929-v1:0", "
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
export class AwsBedrockHandler implements ApiHandler {
public readonly id = "bedrock"
private options: AwsBedrockHandlerOptions
constructor(options: AwsBedrockHandlerOptions) {
+1
View File
@@ -11,6 +11,7 @@ interface CerebrasHandlerOptions extends CommonApiHandlerOptions {
}
export class CerebrasHandler implements ApiHandler {
public readonly id = "cerebras"
private options: CerebrasHandlerOptions
private client: Cerebras | undefined
+1
View File
@@ -13,6 +13,7 @@ interface ClaudeCodeHandlerOptions extends CommonApiHandlerOptions {
}
export class ClaudeCodeHandler implements ApiHandler {
public readonly id = "claude-code"
private options: ClaudeCodeHandlerOptions
constructor(options: ClaudeCodeHandlerOptions) {
+1
View File
@@ -28,6 +28,7 @@ interface ClineHandlerOptions extends CommonApiHandlerOptions {
}
export class ClineHandler implements ApiHandler {
public readonly id = "cline"
private options: ClineHandlerOptions
private clineAccountService = ClineAccountService.getInstance()
private _authService: AuthService
+1
View File
@@ -16,6 +16,7 @@ interface DeepSeekHandlerOptions extends CommonApiHandlerOptions {
}
export class DeepSeekHandler implements ApiHandler {
public readonly id = "deepseek"
private options: DeepSeekHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -71,6 +71,7 @@ interface DifyConversationResponse {
}
export class DifyHandler implements ApiHandler {
public readonly id = "dify"
private options: DifyHandlerOptions
private baseUrl: string
private apiKey: string
+1
View File
@@ -12,6 +12,7 @@ interface DoubaoHandlerOptions extends CommonApiHandlerOptions {
}
export class DoubaoHandler implements ApiHandler {
public readonly id = "doubao"
private options: DoubaoHandlerOptions
private client: OpenAI | undefined
constructor(options: DoubaoHandlerOptions) {
+1
View File
@@ -14,6 +14,7 @@ interface FireworksHandlerOptions extends CommonApiHandlerOptions {
}
export class FireworksHandler implements ApiHandler {
public readonly id = "fireworks"
private options: FireworksHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -53,6 +53,7 @@ interface GeminiHandlerOptions extends CommonApiHandlerOptions {
* 4. Separating immediate costs from ongoing costs to avoid double-counting
*/
export class GeminiHandler implements ApiHandler {
public readonly id = "gemini"
private options: GeminiHandlerOptions
private client: GoogleGenAI | undefined
+1
View File
@@ -86,6 +86,7 @@ const MODEL_FAMILIES: Record<string, GroqModelFamily> = {
}
export class GroqHandler implements ApiHandler {
public readonly id = "groq"
private options: GroqHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -13,6 +13,7 @@ interface OpenAiHandlerOptions extends CommonApiHandlerOptions {
}
export class HicapHandler implements ApiHandler {
public readonly id = "hicap"
private options: OpenAiHandlerOptions
private client: OpenAI | undefined
@@ -15,6 +15,7 @@ interface HuaweiCloudMaaSHandlerOptions extends CommonApiHandlerOptions {
}
export class HuaweiCloudMaaSHandler implements ApiHandler {
public readonly id = "huawei-cloud-maas"
private options: HuaweiCloudMaaSHandlerOptions
private client: OpenAI | undefined
constructor(options: HuaweiCloudMaaSHandlerOptions) {
+1
View File
@@ -16,6 +16,7 @@ interface HuggingFaceHandlerOptions extends CommonApiHandlerOptions {
}
export class HuggingFaceHandler implements ApiHandler {
public readonly id = "huggingface"
private options: HuggingFaceHandlerOptions
private client: OpenAI | undefined
private cachedModel: { id: HuggingFaceModelId; info: ModelInfo } | undefined
+1
View File
@@ -36,6 +36,7 @@ export interface LiteLlmModelInfoResponse {
}
export class LiteLlmHandler implements ApiHandler {
public readonly id = "litellm"
private options: LiteLlmHandlerOptions
private client: OpenAI | undefined
private modelInfoCache: LiteLlmModelInfoResponse | undefined
+1
View File
@@ -15,6 +15,7 @@ interface LmStudioHandlerOptions extends CommonApiHandlerOptions {
}
export class LmStudioHandler implements ApiHandler {
public readonly id = "lmstudio"
private options: LmStudioHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -15,6 +15,7 @@ interface MinimaxHandlerOptions extends CommonApiHandlerOptions {
}
export class MinimaxHandler implements ApiHandler {
public readonly id = "minimax"
private options: MinimaxHandlerOptions
private client: Anthropic | undefined
+1
View File
@@ -14,6 +14,7 @@ interface MistralHandlerOptions extends CommonApiHandlerOptions {
}
export class MistralHandler implements ApiHandler {
public readonly id = "mistral"
private options: MistralHandlerOptions
private client: Mistral | undefined
+1
View File
@@ -15,6 +15,7 @@ interface MoonshotHandlerOptions extends CommonApiHandlerOptions {
}
export class MoonshotHandler implements ApiHandler {
public readonly id = "moonshot"
private client: OpenAI | undefined
constructor(private readonly options: MoonshotHandlerOptions) {}
+1
View File
@@ -15,6 +15,7 @@ interface NebiusHandlerOptions extends CommonApiHandlerOptions {
}
export class NebiusHandler implements ApiHandler {
public readonly id = "nebius"
private client: OpenAI | undefined
constructor(private readonly options: NebiusHandlerOptions) {}
+1
View File
@@ -28,6 +28,7 @@ export interface OcaHandlerOptions extends CommonApiHandlerOptions {
}
export class OcaHandler implements ApiHandler {
public readonly id = "oca"
protected options: OcaHandlerOptions
protected client: OpenAI | undefined
+1 -1
View File
@@ -15,8 +15,8 @@ interface OllamaHandlerOptions extends CommonApiHandlerOptions {
}
const DEFAULT_CONTEXT_WINDOW = 32768
export class OllamaHandler implements ApiHandler {
public readonly id = "ollama"
private options: OllamaHandlerOptions
private client: Ollama | undefined
+1
View File
@@ -16,6 +16,7 @@ interface OpenAiNativeHandlerOptions extends CommonApiHandlerOptions {
}
export class OpenAiNativeHandler implements ApiHandler {
public readonly id = "openai-native"
private options: OpenAiNativeHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -20,6 +20,7 @@ interface OpenAiHandlerOptions extends CommonApiHandlerOptions {
}
export class OpenAiHandler implements ApiHandler {
public readonly id = "openai"
private options: OpenAiHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -22,6 +22,7 @@ interface OpenRouterHandlerOptions extends CommonApiHandlerOptions {
}
export class OpenRouterHandler implements ApiHandler {
public readonly id = "openrouter"
private options: OpenRouterHandlerOptions
private client: OpenAI | undefined
lastGenerationId?: string
+1
View File
@@ -49,6 +49,7 @@ function objectToUrlEncoded(data: Record<string, string>): string {
}
export class QwenCodeHandler implements ApiHandler {
public readonly id = "qwen-code"
private options: QwenCodeHandlerOptions
private credentials: QwenOAuthCredentials | null = null
private client: OpenAI | undefined
+1
View File
@@ -26,6 +26,7 @@ interface QwenHandlerOptions extends CommonApiHandlerOptions {
}
export class QwenHandler implements ApiHandler {
public readonly id = "qwen"
private options: QwenHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -28,6 +28,7 @@ interface RequestyUsage extends OpenAI.CompletionUsage {
}
export class RequestyHandler implements ApiHandler {
public readonly id = "requesty"
private options: RequestyHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -15,6 +15,7 @@ interface SambanovaHandlerOptions extends CommonApiHandlerOptions {
}
export class SambanovaHandler implements ApiHandler {
public readonly id = "sambanova"
private options: SambanovaHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -352,6 +352,7 @@ namespace Gemini {
}
export class SapAiCoreHandler implements ApiHandler {
public readonly id = "sapaicore"
private options: SapAiCoreHandlerOptions
private token?: Token
private deployments?: Deployment[]
+1
View File
@@ -15,6 +15,7 @@ interface TogetherHandlerOptions extends CommonApiHandlerOptions {
}
export class TogetherHandler implements ApiHandler {
public readonly id = "together"
private options: TogetherHandlerOptions
private client: OpenAI | undefined
@@ -15,6 +15,7 @@ interface VercelAIGatewayHandlerOptions extends CommonApiHandlerOptions {
}
export class VercelAIGatewayHandler implements ApiHandler {
public readonly id = "vercel-ai-gateway"
private options: VercelAIGatewayHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -20,6 +20,7 @@ interface VertexHandlerOptions extends CommonApiHandlerOptions {
}
export class VertexHandler implements ApiHandler {
public readonly id = "vertex"
private geminiHandler: GeminiHandler | undefined
private clientAnthropic: AnthropicVertex | undefined
private options: VertexHandlerOptions
+1
View File
@@ -128,6 +128,7 @@ declare module "vscode" {
* ```
*/
export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
public readonly id = "vscode-lm"
private options: VsCodeLmHandlerOptions
private client: vscode.LanguageModelChat | null
private disposable: vscode.Disposable | null
+1
View File
@@ -17,6 +17,7 @@ interface XAIHandlerOptions extends CommonApiHandlerOptions {
}
export class XAIHandler implements ApiHandler {
public readonly id = "xai"
private options: XAIHandlerOptions
private client: OpenAI | undefined
+1
View File
@@ -24,6 +24,7 @@ interface ZAiHandlerOptions extends CommonApiHandlerOptions {
}
export class ZAiHandler implements ApiHandler {
public readonly id = "zai"
private options: ZAiHandlerOptions
private client: OpenAI | undefined
constructor(options: ZAiHandlerOptions) {
+1 -3
View File
@@ -1889,9 +1889,7 @@ export class Task {
private getCurrentProviderInfo(): ApiProviderInfo {
const model = this.api.getModel()
const apiConfig = this.stateManager.getApiConfiguration()
const mode = this.stateManager.getGlobalSettingsKey("mode")
const providerId = (mode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
const providerId = this.api.id
const customPrompt = this.stateManager.getGlobalSettingsKey("customPrompt")
return { model, providerId, customPrompt }
}
@@ -45,10 +45,8 @@ export class AccessMcpResourceHandler implements IFullyManagedTool {
const server_name: string | undefined = block.params.server_name
const uri: string | undefined = block.params.uri
// Extract provider using the proven pattern from ReportBugHandler
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
const modelId = config.api.getModel().id
const providerId = config.api.id
// Validate required parameters
if (!server_name) {
@@ -80,7 +78,7 @@ export class AccessMcpResourceHandler implements IFullyManagedTool {
await config.callbacks.say("use_mcp_server", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, true, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true)
} else {
// Manual approval flow
const notificationMessage = `Cline wants to access ${uri || "unknown resource"} on ${server_name || "unknown server"}`
@@ -92,10 +90,10 @@ export class AccessMcpResourceHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("use_mcp_server", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, false)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true)
}
}
@@ -942,16 +942,15 @@ export class ApplyPatchHandler implements IFullyManagedTool {
primaryFile: string,
): Promise<boolean> {
// Extract provider using the proven pattern from ReportBugHandler
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
const modelId = config.api.getModel().id
const providerId = config.api.id
const messageStr = JSON.stringify(message)
const shouldAutoApprove = await config.callbacks.shouldAutoApproveToolWithPath(block.name, primaryFile)
if (shouldAutoApprove) {
await config.callbacks.say("tool", messageStr, undefined, undefined, false)
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, true, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true)
return true
}
@@ -971,7 +970,7 @@ export class ApplyPatchHandler implements IFullyManagedTool {
const approved = response === "yesButtonClicked"
config.taskState.didRejectTool = !approved
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, approved)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, approved)
return approved
}
@@ -53,10 +53,8 @@ export class ExecuteCommandToolHandler implements IFullyManagedTool {
const timeoutParam: string | undefined = block.params.timeout
let timeoutSeconds: number | undefined
// Extract provider using the proven pattern from ReportBugHandler
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
const modelId = config.api.getModel().id
const providerId = config.api.id
// Validate required parameters
if (!command) {
@@ -78,7 +76,7 @@ export class ExecuteCommandToolHandler implements IFullyManagedTool {
}
// Pre-process command for certain models
if (config.api.getModel().id.includes("gemini")) {
if (modelId.includes("gemini")) {
command = fixModelHtmlEscaping(command)
}
@@ -158,15 +156,7 @@ export class ExecuteCommandToolHandler implements IFullyManagedTool {
await config.callbacks.removeLastPartialMessageIfExistsWithType("ask", "command")
await config.callbacks.say("command", actualCommand, undefined, undefined, false)
didAutoApprove = true
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
true,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true, workspaceContext)
} else {
// Manual approval flow
showNotificationForApproval(
@@ -180,26 +170,10 @@ export class ExecuteCommandToolHandler implements IFullyManagedTool {
config,
)
if (!didApprove) {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
false,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false, workspaceContext)
return formatResponse.toolDenied()
}
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true, workspaceContext)
}
// Setup timeout notification for long-running auto-approved commands
@@ -50,11 +50,6 @@ export class ListCodeDefinitionNamesToolHandler implements IFullyManagedTool {
async execute(config: TaskConfig, block: ToolUse): Promise<ToolResponse> {
const relDirPath: string | undefined = block.params.path
// Extract provider using the proven pattern from ReportBugHandler
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameters
const pathValidation = this.validator.assertRequiredParams(block, "path")
if (!pathValidation.ok) {
@@ -80,6 +75,9 @@ export class ListCodeDefinitionNamesToolHandler implements IFullyManagedTool {
operationIsLocatedInWorkspace: await isLocatedInWorkspace(relDirPath!),
}
const modelId = config.api.getModel().id
const providerId = config.api.id
const completeMessage = JSON.stringify(sharedMessageProps)
if (await config.callbacks.shouldAutoApproveToolWithPath(block.name, relDirPath)) {
@@ -88,7 +86,7 @@ export class ListCodeDefinitionNamesToolHandler implements IFullyManagedTool {
await config.callbacks.say("tool", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, true, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true)
} else {
// Manual approval flow
const notificationMessage = `Cline wants to analyze code definitions in ${getWorkspaceBasename(absolutePath, "ListCodeDefinitionNamesToolHandler.notification")}`
@@ -100,10 +98,10 @@ export class ListCodeDefinitionNamesToolHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("tool", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, false)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true)
}
}
@@ -56,11 +56,6 @@ export class ListFilesToolHandler implements IFullyManagedTool {
const recursiveRaw: string | undefined = block.params.recursive
const recursive = recursiveRaw?.toLowerCase() === "true"
// Extract provider using the proven pattern from ReportBugHandler
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameters
const pathValidation = this.validator.assertRequiredParams(block, "path")
if (!pathValidation.ok) {
@@ -97,6 +92,9 @@ export class ListFilesToolHandler implements IFullyManagedTool {
operationIsLocatedInWorkspace: await isLocatedInWorkspace(relDirPath!),
}
const modelId = config.api.getModel().id
const providerId = config.api.id
const completeMessage = JSON.stringify(sharedMessageProps)
if (await config.callbacks.shouldAutoApproveToolWithPath(block.name, relDirPath)) {
@@ -105,15 +103,7 @@ export class ListFilesToolHandler implements IFullyManagedTool {
await config.callbacks.say("tool", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
true,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true, workspaceContext)
} else {
// Manual approval flow
const notificationMessage = `Cline wants to view directory ${getWorkspaceBasename(absolutePath, "ListFilesToolHandler.notification")}/`
@@ -125,26 +115,10 @@ export class ListFilesToolHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("tool", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
false,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false, workspaceContext)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true, workspaceContext)
}
}
@@ -52,11 +52,6 @@ export class ReadFileToolHandler implements IFullyManagedTool {
async execute(config: TaskConfig, block: ToolUse): Promise<ToolResponse> {
const relPath: string | undefined = block.params.path
// Extract provider information for telemetry
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameters
const pathValidation = this.validator.assertRequiredParams(block, "path")
if (!pathValidation.ok) {
@@ -95,6 +90,9 @@ export class ReadFileToolHandler implements IFullyManagedTool {
operationIsLocatedInWorkspace: await isLocatedInWorkspace(relPath!),
} satisfies ClineSayTool
const modelId = config.api.getModel().id
const providerId = config.api.id
const completeMessage = JSON.stringify(sharedMessageProps)
if (await config.callbacks.shouldAutoApproveToolWithPath(block.name, relPath)) {
@@ -103,15 +101,7 @@ export class ReadFileToolHandler implements IFullyManagedTool {
await config.callbacks.say("tool", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
true,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true, workspaceContext)
} else {
// Manual approval flow
const notificationMessage = `Cline wants to read ${getWorkspaceBasename(absolutePath, "ReadFileToolHandler.notification")}`
@@ -123,26 +113,10 @@ export class ReadFileToolHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("tool", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
false,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false, workspaceContext)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true, workspaceContext)
}
}
@@ -74,13 +74,13 @@ export class ReportBugHandler implements IToolHandler, IPartialBlockHandler {
// Derive system information values algorithmically
const operatingSystem = os.platform() + " " + os.release()
const currentMode = config.mode
const clineVersion = ExtensionRegistryInfo.version
const host = await HostProvider.env.getHostVersion({})
const systemInfo = `${host.platform}: ${host.version}, Node.js: ${process.version}, Architecture: ${os.arch()}`
const apiConfig = config.services.stateManager.getApiConfiguration()
const apiProvider = currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider
const providerAndModel = `${apiProvider} / ${config.api.getModel().id}`
const modelId = config.api.getModel().id
const providerId = config.api.id
const providerAndModel = `${providerId} / ${modelId}`
// Ask user for confirmation
const bugReportData = JSON.stringify({
@@ -207,11 +207,6 @@ export class SearchFilesToolHandler implements IFullyManagedTool {
const regex: string | undefined = block.params.regex
const filePattern: string | undefined = block.params.file_pattern
// Extract provider information for telemetry
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameters
const pathValidation = this.validator.assertRequiredParams(block, "path")
if (!pathValidation.ok) {
@@ -303,6 +298,9 @@ export class SearchFilesToolHandler implements IFullyManagedTool {
operationIsLocatedInWorkspace: await isLocatedInWorkspace(parsedPath),
} satisfies ClineSayTool
const modelId = config.api.getModel().id
const providerId = config.api.id
const completeMessage = JSON.stringify(sharedMessageProps)
if (await config.callbacks.shouldAutoApproveToolWithPath(block.name, relDirPath)) {
@@ -311,15 +309,7 @@ export class SearchFilesToolHandler implements IFullyManagedTool {
await config.callbacks.say("tool", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
true,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true, workspaceContext)
} else {
// Manual approval flow
const notificationMessage = `Cline wants to search files for ${regex}`
@@ -331,26 +321,10 @@ export class SearchFilesToolHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("tool", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
false,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false, workspaceContext)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true, workspaceContext)
}
}
@@ -47,11 +47,6 @@ export class UseMcpToolHandler implements IFullyManagedTool {
const tool_name: string | undefined = block.params.tool_name
const mcp_arguments: string | undefined = block.params.arguments
// Extract provider information for telemetry
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameters
if (!server_name) {
config.taskState.consecutiveMistakeCount++
@@ -85,6 +80,9 @@ export class UseMcpToolHandler implements IFullyManagedTool {
arguments: mcp_arguments,
} satisfies ClineAskUseMcpServer)
const modelId = config.api.getModel().id
const providerId = config.api.id
const isToolAutoApproved = config.services.mcpHub.connections
?.find((conn: any) => conn.server.name === server_name)
?.server.tools?.find((tool: any) => tool.name === tool_name)?.autoApprove
@@ -95,7 +93,7 @@ export class UseMcpToolHandler implements IFullyManagedTool {
await config.callbacks.say("use_mcp_server", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, true, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true)
} else {
// Manual approval flow
const notificationMessage = `Cline wants to use ${tool_name || "unknown tool"} on ${server_name || "unknown server"}`
@@ -107,10 +105,10 @@ export class UseMcpToolHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("use_mcp_server", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, false)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true)
}
}
@@ -39,11 +39,6 @@ export class WebFetchToolHandler implements IFullyManagedTool {
try {
const url: string | undefined = block.params.url
// Extract provider information for telemetry
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameter
if (!url) {
config.taskState.consecutiveMistakeCount++
@@ -60,11 +55,14 @@ export class WebFetchToolHandler implements IFullyManagedTool {
}
const completeMessage = JSON.stringify(sharedMessageProps)
const modelId = config.api.getModel().id
const providerId = config.api.id
if (config.callbacks.shouldAutoApproveTool(this.name)) {
// Auto-approve flow
await config.callbacks.removeLastPartialMessageIfExistsWithType("ask", "tool")
await config.callbacks.say("tool", completeMessage, undefined, undefined, false)
telemetryService.captureToolUsage(config.ulid, "web_fetch", config.api.getModel().id, provider, true, true)
telemetryService.captureToolUsage(config.ulid, "web_fetch", modelId, providerId, true, true)
} else {
// Manual approval flow
showNotificationForApproval(
@@ -75,10 +73,10 @@ export class WebFetchToolHandler implements IFullyManagedTool {
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback("tool", completeMessage, config)
if (!didApprove) {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, false)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, false)
return formatResponse.toolDenied()
} else {
telemetryService.captureToolUsage(config.ulid, block.name, config.api.getModel().id, provider, false, true)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true)
}
}
@@ -92,11 +92,6 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
const rawContent = block.params.content // for write_to_file
const rawDiff = block.params.diff // for replace_in_file
// Extract provider information for telemetry
const apiConfig = config.services.stateManager.getApiConfiguration()
const currentMode = config.services.stateManager.getGlobalSettingsKey("mode")
const provider = (currentMode === "plan" ? apiConfig.planModeApiProvider : apiConfig.actModeApiProvider) as string
// Validate required parameters based on tool type
if (!rawRelPath) {
config.taskState.consecutiveMistakeCount++
@@ -156,6 +151,9 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
await config.services.diffViewProvider.scrollToFirstDiff()
// showOmissionWarning(this.diffViewProvider.originalContent || "", newContent)
const modelId = config.api.getModel().id
const providerId = config.api.id
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: diff || content,
@@ -174,15 +172,7 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
await config.callbacks.say("tool", completeMessage, undefined, undefined, false)
// Capture telemetry
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
true,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, true, true, workspaceContext)
// we need an artificial delay to let the diagnostics catch up to the changes
await setTimeoutPromise(3_500)
@@ -230,8 +220,8 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
modelId,
providerId,
false,
false,
workspaceContext,
@@ -257,15 +247,7 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
await config.callbacks.say("user_feedback", text, images, files)
}
telemetryService.captureToolUsage(
config.ulid,
block.name,
config.api.getModel().id,
provider,
false,
true,
workspaceContext,
)
telemetryService.captureToolUsage(config.ulid, block.name, modelId, providerId, false, true, workspaceContext)
}
}