diff --git a/apps/web-evals/package.json b/apps/web-evals/package.json index 869355100f5..020d56fbd2d 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 750454956f8..2fea434c61c 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 74c25cf2609..5928b531142 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 cca6f9ce954..f4ba30ce795 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/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 5897156b0a0..d4affa2beaf 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: { @@ -82,6 +69,8 @@ vitest.mock("@roo-code/cloud", () => ({ authService: { getSessionToken: () => mockGetSessionTokenFn(), }, + on: vitest.fn(), + off: vitest.fn(), } }, }, @@ -409,11 +398,18 @@ 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", { - get: () => ({ authService: undefined }), + get: () => ({ + authService: undefined, + on: vitest.fn(), + off: vitest.fn(), + }), configurable: true, }) @@ -424,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") } } }) diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index 44b01608627..6f10157a313 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -1,22 +1,24 @@ 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 { + private authStateListener?: (state: { state: AuthState }) => void + 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 +27,39 @@ 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 + + this.authStateListener = (state: { state: AuthState }) => { + if (state.state === "active-session") { + this.client = new OpenAI({ + baseURL: this.baseURL, + apiKey: cloudService.authService?.getSessionToken() ?? "unauthenticated", + defaultHeaders: DEFAULT_HEADERS, + }) + } else if (state.state === "logged-out") { + this.client = new OpenAI({ + baseURL: this.baseURL, + apiKey: "unauthenticated", + defaultHeaders: DEFAULT_HEADERS, + }) + } + } + + cloudService.on("auth-state-changed", this.authStateListener) + } + } + + dispose() { + if (this.authStateListener && CloudService.hasInstance()) { + CloudService.instance.off("auth-state-changed", this.authStateListener) + } } override async *createMessage( diff --git a/src/extension.ts b/src/extension.ts index dc96e282c43..5db0996ad65 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) {