diff --git a/src/api/index.ts b/src/api/index.ts index 8b09bf4cf9..e224be0525 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -56,6 +56,11 @@ export interface ApiHandler { * @returns A promise resolving to the token count */ countTokens(content: Array): Promise + + /** + * Optional method to dispose of any resources held by the API handler. + */ + dispose?: () => void } export function buildApiHandler(configuration: ProviderSettings): ApiHandler { diff --git a/src/api/providers/base-provider.ts b/src/api/providers/base-provider.ts index 1abbf5f558..6816477719 100644 --- a/src/api/providers/base-provider.ts +++ b/src/api/providers/base-provider.ts @@ -10,6 +10,8 @@ import { countTokens } from "../../utils/countTokens" * Base class for API providers that implements common functionality. */ export abstract class BaseProvider implements ApiHandler { + // constructor remains empty, no new properties added here + abstract createMessage( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], @@ -32,4 +34,25 @@ export abstract class BaseProvider implements ApiHandler { return countTokens(content, { useWorker: true }) } + + /** + * Disposes of any resources held by the provider. + * Attempts common disposal methods on the client if it exists. + */ + public dispose(): void { + // Use reflection to find any property named 'client' on the instance + const clientProperty = (this as any).client + if (clientProperty) { + // Try common disposal methods that SDKs might have + if (typeof clientProperty.close === "function") { + clientProperty.close() + } else if (typeof clientProperty.destroy === "function") { + clientProperty.destroy() + } else if (typeof clientProperty.dispose === "function") { + clientProperty.dispose() + } + // Clear the reference on the instance + ;(this as any).client = undefined + } + } } diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 0e335755ec..f3c62e8402 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -976,4 +976,14 @@ Suggestions: return `Bedrock completion error: ${errorMessage}` } } + + override dispose(): void { + // Clear custom cache specific to this handler + this.previousCachePointPlacements = {} + logger.debug("AwsBedrockHandler: Cleared previousCachePointPlacements.", { ctx: "bedrock" }) + + // Call base class dispose for any generic cleanup (like the SDK client) + // The base dispose method handles client disposal reflectively. + super.dispose() + } } diff --git a/src/api/providers/vscode-lm.ts b/src/api/providers/vscode-lm.ts index 6474371bee..847f15f46b 100644 --- a/src/api/providers/vscode-lm.ts +++ b/src/api/providers/vscode-lm.ts @@ -164,7 +164,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan * converts the messages to VS Code LM format, and streams the response chunks. * Tool calls handling is currently a work in progress. */ - dispose(): void { + override dispose(): void { if (this.disposable) { this.disposable.dispose() } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 5683c2d9b2..515490f90d 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1001,6 +1001,7 @@ export class Task extends EventEmitter { this.browserSession.closeBrowser() this.rooIgnoreController?.dispose() this.fileContextTracker.dispose() + this.api?.dispose?.() // Added this line // If we're not streaming then `abortStream` (which reverts the diff // view changes) won't be called, so we need to revert the changes here. @@ -1012,6 +1013,25 @@ export class Task extends EventEmitter { await this.saveClineMessages() } + // Added new dispose method as per leak report suggestion + public dispose(): void { + console.log(`[subtasks] disposing task ${this.taskId}.${this.instanceId}`) + this.abortTask(true) // Call abortTask to ensure all resources are released + + // Explicitly call dispose on the api handler again, in case abortTask didn't catch it + // or if dispose is called directly without abortTask. + this.api?.dispose?.() + + // Clear any other task-specific resources if they weren't handled by abortTask + if (this.pauseInterval) { + clearInterval(this.pauseInterval) + this.pauseInterval = undefined + } + // Ensure other disposables are handled if not already by abortTask + // this.rooIgnoreController?.dispose(); // Already called in abortTask + // this.fileContextTracker.dispose(); // Already called in abortTask + } + // Used when a sub-task is launched and the parent task is waiting for it to // finish. // TBD: The 1s should be added to the settings, also should add a timeout to