refactor: Remove exceptions from RooHandler constructor (#7302)

This commit is contained in:
Daniel 2025-08-21 16:56:47 -05:00 committed by GitHub
parent 0bc1183c71
commit 44fd6432dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 49 additions and 22 deletions

View file

@ -143,6 +143,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
case "io-intelligence":
return new IOIntelligenceHandler(options)
case "roo":
// Never throw exceptions from provider constructors
// The provider-proxy server will handle authentication and return appropriate error codes
return new RooHandler(options)
case "featherless":
return new FeatherlessHandler(options)

View file

@ -131,21 +131,25 @@ describe("RooHandler", () => {
expect(handler.getModel().id).toBe(mockOptions.apiModelId)
})
it("should throw error if CloudService is not available", () => {
it("should not throw error if CloudService is not available", () => {
mockHasInstanceFn.mockReturnValue(false)
expect(() => {
new RooHandler(mockOptions)
}).toThrow("Authentication required for Roo Code Cloud")
expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired")
}).not.toThrow()
// Constructor should succeed even without CloudService
const handler = new RooHandler(mockOptions)
expect(handler).toBeInstanceOf(RooHandler)
})
it("should throw error if session token is not available", () => {
it("should not throw error if session token is not available", () => {
mockHasInstanceFn.mockReturnValue(true)
mockGetSessionTokenFn.mockReturnValue(null)
expect(() => {
new RooHandler(mockOptions)
}).toThrow("Authentication required for Roo Code Cloud")
expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired")
}).not.toThrow()
// Constructor should succeed even without session token
const handler = new RooHandler(mockOptions)
expect(handler).toBeInstanceOf(RooHandler)
})
it("should initialize with default model if no model specified", () => {
@ -400,7 +404,7 @@ describe("RooHandler", () => {
expect(mockGetSessionTokenFn).toHaveBeenCalled()
})
it("should handle undefined auth service", () => {
it("should handle undefined auth service gracefully", () => {
mockHasInstanceFn.mockReturnValue(true)
// Mock CloudService with undefined authService
const originalGetter = Object.getOwnPropertyDescriptor(CloudService, "instance")?.get
@ -413,7 +417,10 @@ describe("RooHandler", () => {
expect(() => {
new RooHandler(mockOptions)
}).toThrow("Authentication required for Roo Code Cloud")
}).not.toThrow()
// Constructor should succeed even with undefined auth service
const handler = new RooHandler(mockOptions)
expect(handler).toBeInstanceOf(RooHandler)
} finally {
// Always restore original getter, even if test fails
if (originalGetter) {
@ -425,12 +432,15 @@ describe("RooHandler", () => {
}
})
it("should handle empty session token", () => {
it("should handle empty session token gracefully", () => {
mockGetSessionTokenFn.mockReturnValue("")
expect(() => {
new RooHandler(mockOptions)
}).toThrow("Authentication required for Roo Code Cloud")
}).not.toThrow()
// Constructor should succeed even with empty session token
const handler = new RooHandler(mockOptions)
expect(handler).toBeInstanceOf(RooHandler)
})
})
})

View file

@ -4,29 +4,27 @@ import { CloudService } from "@roo-code/cloud"
import type { ApiHandlerOptions } from "../../shared/api"
import { ApiStream } from "../transform/stream"
import { t } from "../../i18n"
import type { ApiHandlerCreateMessageMetadata } from "../index"
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
constructor(options: ApiHandlerOptions) {
// Check if CloudService is available and get the session token.
if (!CloudService.hasInstance()) {
throw new Error(t("common:errors.roo.authenticationRequired"))
}
const sessionToken = CloudService.instance.authService?.getSessionToken()
if (!sessionToken) {
throw new Error(t("common:errors.roo.authenticationRequired"))
// 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 = ""
if (CloudService.hasInstance()) {
sessionToken = CloudService.instance.authService?.getSessionToken() || ""
}
// Always construct the handler, even without a valid token.
// The provider-proxy server will return 401 if authentication fails.
super({
...options,
providerName: "Roo Code Cloud",
baseURL: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy/v1",
apiKey: sessionToken,
apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token
defaultProviderModelId: rooDefaultModelId,
providerModels: rooModels,
defaultTemperature: 0.7,

View file

@ -288,7 +288,24 @@ export const webviewMessageHandler = async (
// Initializing new instance of Cline will make sure that any
// agentically running promises in old instance don't affect our new
// task. This essentially creates a fresh slate for the new task.
await provider.createTask(message.text, message.images)
try {
await provider.createTask(message.text, message.images)
// Task created successfully - notify the UI to reset
await provider.postMessageToWebview({
type: "invoke",
invoke: "newChat",
})
} catch (error) {
// For all errors, reset the UI and show error
await provider.postMessageToWebview({
type: "invoke",
invoke: "newChat",
})
// Show error to user
vscode.window.showErrorMessage(
`Failed to create task: ${error instanceof Error ? error.message : String(error)}`,
)
}
break
case "customInstructions":
await provider.updateCustomInstructions(message.text)