From a6648b323b65fba4316d3f00a77cb3f446c5cbee Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 22 Sep 2025 12:05:47 -0700 Subject: [PATCH 1/4] Bare metal evals fixes --- apps/web-evals/package.json | 2 +- packages/evals/README.md | 2 +- packages/evals/docker-compose.yml | 2 +- packages/evals/scripts/setup.sh | 5 ++--- src/api/providers/roo.ts | 26 ++++++++++++++++++++------ src/extension.ts | 2 +- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/web-evals/package.json b/apps/web-evals/package.json index 869355100f50..020d56fbd2d9 100644 --- a/apps/web-evals/package.json +++ b/apps/web-evals/package.json @@ -5,7 +5,7 @@ "scripts": { "lint": "next lint --max-warnings 0", "check-types": "tsc -b", - "dev": "scripts/check-services.sh && next dev", + "dev": "scripts/check-services.sh && next dev -p 3446", "format": "prettier --write src", "build": "next build", "start": "next start", diff --git a/packages/evals/README.md b/packages/evals/README.md index 750454956f8f..2fea434c61c3 100644 --- a/packages/evals/README.md +++ b/packages/evals/README.md @@ -95,7 +95,7 @@ By default, the evals system uses the following ports: - **PostgreSQL**: 5433 (external) → 5432 (internal) - **Redis**: 6380 (external) → 6379 (internal) -- **Web Service**: 3446 (external) → 3000 (internal) +- **Web Service**: 3446 (external) → 3446 (internal) These ports are configured to avoid conflicts with other services that might be running on the standard PostgreSQL (5432) and Redis (6379) ports. diff --git a/packages/evals/docker-compose.yml b/packages/evals/docker-compose.yml index 74c25cf26095..5928b5311427 100644 --- a/packages/evals/docker-compose.yml +++ b/packages/evals/docker-compose.yml @@ -52,7 +52,7 @@ services: context: ../../ dockerfile: packages/evals/Dockerfile.web ports: - - "${EVALS_WEB_PORT:-3446}:3000" + - "${EVALS_WEB_PORT:-3446}:3446" environment: - HOST_EXECUTION_METHOD=docker volumes: diff --git a/packages/evals/scripts/setup.sh b/packages/evals/scripts/setup.sh index cca6f9ce954e..f4ba30ce7957 100755 --- a/packages/evals/scripts/setup.sh +++ b/packages/evals/scripts/setup.sh @@ -12,7 +12,6 @@ build_extension() { echo "šŸ”Ø Building the Roo Code extension..." pnpm -w vsix -- --out ../bin/roo-code-$(git rev-parse --short HEAD).vsix || exit 1 code --install-extension ../../bin/roo-code-$(git rev-parse --short HEAD).vsix || exit 1 - cd evals } check_docker_services() { @@ -377,7 +376,7 @@ fi echo -e "\nšŸš€ You're ready to rock and roll! \n" -if ! nc -z localhost 3000; then +if ! nc -z localhost 3446; then read -p "🌐 Would you like to start the evals web app? (Y/n): " start_evals if [[ "$start_evals" =~ ^[Yy]|^$ ]]; then @@ -386,5 +385,5 @@ if ! nc -z localhost 3000; then echo "šŸ’” You can start it anytime with 'pnpm --filter @roo-code/web-evals dev'." fi else - echo "šŸ‘Ÿ The evals web app is running at http://localhost:3000 (or http://localhost:3446 if using Docker)" + echo "šŸ‘Ÿ The evals web app is running at http://localhost:3446" fi diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index 44b016086276..0fa357a58015 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -1,22 +1,22 @@ import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" -import { rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types" +import { AuthState, rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types" import { CloudService } from "@roo-code/cloud" import type { ApiHandlerOptions } from "../../shared/api" import { ApiStream } from "../transform/stream" import type { ApiHandlerCreateMessageMetadata } from "../index" +import { DEFAULT_HEADERS } from "./constants" import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" export class RooHandler extends BaseOpenAiCompatibleProvider { constructor(options: ApiHandlerOptions) { - // Get the session token if available, but don't throw if not. - // The server will handle authentication errors and return appropriate status codes. - let sessionToken = "" + let sessionToken: string | undefined = undefined if (CloudService.hasInstance()) { - sessionToken = CloudService.instance.authService?.getSessionToken() || "" + sessionToken = CloudService.instance.authService?.getSessionToken() } // Always construct the handler, even without a valid token. @@ -25,11 +25,25 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { ...options, providerName: "Roo Code Cloud", baseURL: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy/v1", - apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token + apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token. defaultProviderModelId: rooDefaultModelId, providerModels: rooModels, defaultTemperature: 0.7, }) + + if (CloudService.hasInstance()) { + const cloudService = CloudService.instance + + cloudService.on("auth-state-changed", (state: { state: AuthState }) => { + if (state.state === "active-session") { + this.client = new OpenAI({ + baseURL: this.baseURL, + apiKey: this.options.apiKey, + defaultHeaders: DEFAULT_HEADERS, + }) + } + }) + } } override async *createMessage( diff --git a/src/extension.ts b/src/extension.ts index dc96e282c430..5db0996ad657 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -194,7 +194,7 @@ export async function activate(context: vscode.ExtensionContext) { // Add to subscriptions for proper cleanup on deactivate. context.subscriptions.push(cloudService) - // Trigger initial cloud profile sync now that CloudService is ready + // Trigger initial cloud profile sync now that CloudService is ready. try { await provider.initializeCloudProfileSyncWhenReady() } catch (error) { From f4fb8ee2c30db36da88b1c4b98a68d25b9cc7223 Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 22 Sep 2025 12:10:45 -0700 Subject: [PATCH 2/4] Handle logged out --- src/api/providers/roo.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index 0fa357a58015..a16d2b33a7b8 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -38,7 +38,13 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { if (state.state === "active-session") { this.client = new OpenAI({ baseURL: this.baseURL, - apiKey: this.options.apiKey, + apiKey: CloudService.instance.authService?.getSessionToken() ?? "unauthenticated", + defaultHeaders: DEFAULT_HEADERS, + }) + } else if (state.state === "logged-out") { + this.client = new OpenAI({ + baseURL: this.baseURL, + apiKey: "unauthenticated", defaultHeaders: DEFAULT_HEADERS, }) } From 6f3028325ac1839f49ff12fdc2045782adb8b862 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 22 Sep 2025 19:22:31 +0000 Subject: [PATCH 3/4] fix: resolve memory leak in RooHandler event listener - Add private authStateListener property to store listener reference - Store listener function reference for proper cleanup - Implement dispose() method to remove event listener - Fix API key issue by using cloudService reference instead of CloudService.instance - Update tests to include on/off methods in CloudService mock This prevents memory leaks when multiple RooHandler instances are created and ensures proper cleanup of event listeners. --- src/api/providers/__tests__/roo.spec.ts | 8 +++++++- src/api/providers/roo.ts | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 5897156b0a08..2f5448552049 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -82,6 +82,8 @@ vitest.mock("@roo-code/cloud", () => ({ authService: { getSessionToken: () => mockGetSessionTokenFn(), }, + on: vitest.fn(), + off: vitest.fn(), } }, }, @@ -413,7 +415,11 @@ describe("RooHandler", () => { try { Object.defineProperty(CloudService, "instance", { - get: () => ({ authService: undefined }), + get: () => ({ + authService: undefined, + on: vitest.fn(), + off: vitest.fn(), + }), configurable: true, }) diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index a16d2b33a7b8..6f10157a3131 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -12,6 +12,8 @@ import { DEFAULT_HEADERS } from "./constants" import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" export class RooHandler extends BaseOpenAiCompatibleProvider { + private authStateListener?: (state: { state: AuthState }) => void + constructor(options: ApiHandlerOptions) { let sessionToken: string | undefined = undefined @@ -34,11 +36,11 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { if (CloudService.hasInstance()) { const cloudService = CloudService.instance - cloudService.on("auth-state-changed", (state: { state: AuthState }) => { + this.authStateListener = (state: { state: AuthState }) => { if (state.state === "active-session") { this.client = new OpenAI({ baseURL: this.baseURL, - apiKey: CloudService.instance.authService?.getSessionToken() ?? "unauthenticated", + apiKey: cloudService.authService?.getSessionToken() ?? "unauthenticated", defaultHeaders: DEFAULT_HEADERS, }) } else if (state.state === "logged-out") { @@ -48,7 +50,15 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { defaultHeaders: DEFAULT_HEADERS, }) } - }) + } + + cloudService.on("auth-state-changed", this.authStateListener) + } + } + + dispose() { + if (this.authStateListener && CloudService.hasInstance()) { + CloudService.instance.off("auth-state-changed", this.authStateListener) } } From 22645a9e0eaac306593eb200d546e7c03999b3f7 Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 22 Sep 2025 12:27:42 -0700 Subject: [PATCH 4/4] Fix test --- src/api/providers/__tests__/roo.spec.ts | 37 +++++++++---------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 2f5448552049..d4affa2beafa 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -36,26 +36,12 @@ vitest.mock("openai", () => { return { [Symbol.asyncIterator]: async function* () { yield { - choices: [ - { - delta: { content: "Test response" }, - index: 0, - }, - ], + choices: [{ delta: { content: "Test response" }, index: 0 }], usage: null, } yield { - choices: [ - { - delta: {}, - index: 0, - }, - ], - usage: { - prompt_tokens: 10, - completion_tokens: 5, - total_tokens: 15, - }, + choices: [{ delta: {}, index: 0 }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, } }, } @@ -73,6 +59,7 @@ const mockHasInstance = vitest.fn() // Create mock functions that we can control const mockGetSessionTokenFn = vitest.fn() const mockHasInstanceFn = vitest.fn() +const mockOnFn = vitest.fn() vitest.mock("@roo-code/cloud", () => ({ CloudService: { @@ -411,7 +398,10 @@ describe("RooHandler", () => { it("should handle undefined auth service gracefully", () => { mockHasInstanceFn.mockReturnValue(true) // Mock CloudService with undefined authService - const originalGetter = Object.getOwnPropertyDescriptor(CloudService, "instance")?.get + const originalGetSessionToken = mockGetSessionTokenFn.getMockImplementation() + + // Temporarily make authService return undefined + mockGetSessionTokenFn.mockImplementation(() => undefined) try { Object.defineProperty(CloudService, "instance", { @@ -430,12 +420,11 @@ describe("RooHandler", () => { const handler = new RooHandler(mockOptions) expect(handler).toBeInstanceOf(RooHandler) } finally { - // Always restore original getter, even if test fails - if (originalGetter) { - Object.defineProperty(CloudService, "instance", { - get: originalGetter, - configurable: true, - }) + // Restore original mock implementation + if (originalGetSessionToken) { + mockGetSessionTokenFn.mockImplementation(originalGetSessionToken) + } else { + mockGetSessionTokenFn.mockReturnValue("test-session-token") } } })