Skip to content

Commit 44fd643

Browse files
authored
refactor: Remove exceptions from RooHandler constructor (#7302)
1 parent 0bc1183 commit 44fd643

File tree

4 files changed

+48
-21
lines changed

4 files changed

+48
-21
lines changed

src/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
143143
case "io-intelligence":
144144
return new IOIntelligenceHandler(options)
145145
case "roo":
146+
// Never throw exceptions from provider constructors
147+
// The provider-proxy server will handle authentication and return appropriate error codes
146148
return new RooHandler(options)
147149
case "featherless":
148150
return new FeatherlessHandler(options)

src/api/providers/__tests__/roo.spec.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,21 +131,25 @@ describe("RooHandler", () => {
131131
expect(handler.getModel().id).toBe(mockOptions.apiModelId)
132132
})
133133

134-
it("should throw error if CloudService is not available", () => {
134+
it("should not throw error if CloudService is not available", () => {
135135
mockHasInstanceFn.mockReturnValue(false)
136136
expect(() => {
137137
new RooHandler(mockOptions)
138-
}).toThrow("Authentication required for Roo Code Cloud")
139-
expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired")
138+
}).not.toThrow()
139+
// Constructor should succeed even without CloudService
140+
const handler = new RooHandler(mockOptions)
141+
expect(handler).toBeInstanceOf(RooHandler)
140142
})
141143

142-
it("should throw error if session token is not available", () => {
144+
it("should not throw error if session token is not available", () => {
143145
mockHasInstanceFn.mockReturnValue(true)
144146
mockGetSessionTokenFn.mockReturnValue(null)
145147
expect(() => {
146148
new RooHandler(mockOptions)
147-
}).toThrow("Authentication required for Roo Code Cloud")
148-
expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired")
149+
}).not.toThrow()
150+
// Constructor should succeed even without session token
151+
const handler = new RooHandler(mockOptions)
152+
expect(handler).toBeInstanceOf(RooHandler)
149153
})
150154

151155
it("should initialize with default model if no model specified", () => {
@@ -400,7 +404,7 @@ describe("RooHandler", () => {
400404
expect(mockGetSessionTokenFn).toHaveBeenCalled()
401405
})
402406

403-
it("should handle undefined auth service", () => {
407+
it("should handle undefined auth service gracefully", () => {
404408
mockHasInstanceFn.mockReturnValue(true)
405409
// Mock CloudService with undefined authService
406410
const originalGetter = Object.getOwnPropertyDescriptor(CloudService, "instance")?.get
@@ -413,7 +417,10 @@ describe("RooHandler", () => {
413417

414418
expect(() => {
415419
new RooHandler(mockOptions)
416-
}).toThrow("Authentication required for Roo Code Cloud")
420+
}).not.toThrow()
421+
// Constructor should succeed even with undefined auth service
422+
const handler = new RooHandler(mockOptions)
423+
expect(handler).toBeInstanceOf(RooHandler)
417424
} finally {
418425
// Always restore original getter, even if test fails
419426
if (originalGetter) {
@@ -425,12 +432,15 @@ describe("RooHandler", () => {
425432
}
426433
})
427434

428-
it("should handle empty session token", () => {
435+
it("should handle empty session token gracefully", () => {
429436
mockGetSessionTokenFn.mockReturnValue("")
430437

431438
expect(() => {
432439
new RooHandler(mockOptions)
433-
}).toThrow("Authentication required for Roo Code Cloud")
440+
}).not.toThrow()
441+
// Constructor should succeed even with empty session token
442+
const handler = new RooHandler(mockOptions)
443+
expect(handler).toBeInstanceOf(RooHandler)
434444
})
435445
})
436446
})

src/api/providers/roo.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,27 @@ import { CloudService } from "@roo-code/cloud"
44

55
import type { ApiHandlerOptions } from "../../shared/api"
66
import { ApiStream } from "../transform/stream"
7-
import { t } from "../../i18n"
87

98
import type { ApiHandlerCreateMessageMetadata } from "../index"
109
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
1110

1211
export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
1312
constructor(options: ApiHandlerOptions) {
14-
// Check if CloudService is available and get the session token.
15-
if (!CloudService.hasInstance()) {
16-
throw new Error(t("common:errors.roo.authenticationRequired"))
17-
}
18-
19-
const sessionToken = CloudService.instance.authService?.getSessionToken()
13+
// Get the session token if available, but don't throw if not.
14+
// The server will handle authentication errors and return appropriate status codes.
15+
let sessionToken = ""
2016

21-
if (!sessionToken) {
22-
throw new Error(t("common:errors.roo.authenticationRequired"))
17+
if (CloudService.hasInstance()) {
18+
sessionToken = CloudService.instance.authService?.getSessionToken() || ""
2319
}
2420

21+
// Always construct the handler, even without a valid token.
22+
// The provider-proxy server will return 401 if authentication fails.
2523
super({
2624
...options,
2725
providerName: "Roo Code Cloud",
2826
baseURL: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy/v1",
29-
apiKey: sessionToken,
27+
apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token
3028
defaultProviderModelId: rooDefaultModelId,
3129
providerModels: rooModels,
3230
defaultTemperature: 0.7,

src/core/webview/webviewMessageHandler.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,24 @@ export const webviewMessageHandler = async (
288288
// Initializing new instance of Cline will make sure that any
289289
// agentically running promises in old instance don't affect our new
290290
// task. This essentially creates a fresh slate for the new task.
291-
await provider.createTask(message.text, message.images)
291+
try {
292+
await provider.createTask(message.text, message.images)
293+
// Task created successfully - notify the UI to reset
294+
await provider.postMessageToWebview({
295+
type: "invoke",
296+
invoke: "newChat",
297+
})
298+
} catch (error) {
299+
// For all errors, reset the UI and show error
300+
await provider.postMessageToWebview({
301+
type: "invoke",
302+
invoke: "newChat",
303+
})
304+
// Show error to user
305+
vscode.window.showErrorMessage(
306+
`Failed to create task: ${error instanceof Error ? error.message : String(error)}`,
307+
)
308+
}
292309
break
293310
case "customInstructions":
294311
await provider.updateCustomInstructions(message.text)

0 commit comments

Comments
 (0)