From 0978ba39963e1fcfdacba9c65ae6956b517a53c9 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Fri, 17 Oct 2025 05:18:16 +0100 Subject: [PATCH] feat: Cloud agents in extension (#8470) Co-authored-by: Matt Rubens --- packages/cloud/src/CloudAPI.ts | 21 +- packages/cloud/src/__tests__/CloudAPI.test.ts | 81 ++++++ packages/types/src/cloud.ts | 29 ++ packages/types/src/providers/chutes.ts | 16 +- src/core/webview/ClineProvider.ts | 5 +- .../webviewMessageHandler.cloudAgents.test.ts | 267 ++++++++++++++++++ src/core/webview/webviewMessageHandler.ts | 49 ++++ src/shared/ExtensionMessage.ts | 3 + src/shared/WebviewMessage.ts | 1 + .../src/components/chat/ChatTextArea.tsx | 4 +- webview-ui/src/components/chat/ChatView.tsx | 87 ++---- webview-ui/src/components/chat/TaskHeader.tsx | 8 +- .../chat/__tests__/ChatView.spec.tsx | 107 +++---- .../src/components/cloud/CloudAgents.tsx | 133 +++++++++ .../cloud/__tests__/CloudAgents.spec.tsx | 214 ++++++++++++++ .../src/components/history/HistoryPreview.tsx | 17 +- .../src/components/history/TaskItem.tsx | 4 +- .../src/components/history/TaskItemFooter.tsx | 4 +- .../history/__tests__/HistoryPreview.spec.tsx | 21 -- .../history/__tests__/TaskItem.spec.tsx | 15 - webview-ui/src/components/welcome/RooHero.tsx | 6 +- webview-ui/src/components/welcome/RooTips.tsx | 49 ++-- .../src/context/ExtensionStateContext.tsx | 2 + webview-ui/src/i18n/locales/ca/chat.json | 29 +- webview-ui/src/i18n/locales/de/chat.json | 29 +- webview-ui/src/i18n/locales/en/chat.json | 31 +- webview-ui/src/i18n/locales/en/cloud.json | 2 +- webview-ui/src/i18n/locales/en/history.json | 2 +- webview-ui/src/i18n/locales/es/chat.json | 29 +- webview-ui/src/i18n/locales/fr/chat.json | 29 +- webview-ui/src/i18n/locales/hi/chat.json | 29 +- webview-ui/src/i18n/locales/id/chat.json | 31 +- webview-ui/src/i18n/locales/it/chat.json | 29 +- webview-ui/src/i18n/locales/ja/chat.json | 29 +- webview-ui/src/i18n/locales/ko/chat.json | 29 +- webview-ui/src/i18n/locales/nl/chat.json | 29 +- webview-ui/src/i18n/locales/pl/chat.json | 29 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 29 +- webview-ui/src/i18n/locales/ru/chat.json | 28 +- webview-ui/src/i18n/locales/tr/chat.json | 31 +- webview-ui/src/i18n/locales/vi/chat.json | 31 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 31 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 29 +- 43 files changed, 1190 insertions(+), 488 deletions(-) create mode 100644 packages/cloud/src/__tests__/CloudAPI.test.ts create mode 100644 src/core/webview/__tests__/webviewMessageHandler.cloudAgents.test.ts create mode 100644 webview-ui/src/components/cloud/CloudAgents.tsx create mode 100644 webview-ui/src/components/cloud/__tests__/CloudAgents.spec.tsx diff --git a/packages/cloud/src/CloudAPI.ts b/packages/cloud/src/CloudAPI.ts index d1c3f89c2b..9fb51b553f 100644 --- a/packages/cloud/src/CloudAPI.ts +++ b/packages/cloud/src/CloudAPI.ts @@ -1,6 +1,13 @@ import { z } from "zod" -import { type AuthService, type ShareVisibility, type ShareResponse, shareResponseSchema } from "@roo-code/types" +import { + type AuthService, + type ShareVisibility, + type ShareResponse, + shareResponseSchema, + type CloudAgent, + cloudAgentsResponseSchema, +} from "@roo-code/types" import { getRooCodeApiUrl } from "./config.js" import { getUserAgent } from "./utils.js" @@ -134,4 +141,16 @@ export class CloudAPI { .parse(data), }) } + + async getCloudAgents(): Promise { + this.log("[CloudAPI] Fetching cloud agents") + + const agents = await this.request("/api/cloud-agents", { + method: "GET", + parseResponse: (data) => cloudAgentsResponseSchema.parse(data).data, + }) + + this.log("[CloudAPI] Cloud agents response:", agents) + return agents + } } diff --git a/packages/cloud/src/__tests__/CloudAPI.test.ts b/packages/cloud/src/__tests__/CloudAPI.test.ts new file mode 100644 index 0000000000..2390878dac --- /dev/null +++ b/packages/cloud/src/__tests__/CloudAPI.test.ts @@ -0,0 +1,81 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { CloudAPI } from "../CloudAPI.js" +import { AuthenticationError } from "../errors.js" +import type { AuthService } from "@roo-code/types" + +// Mock fetch globally +global.fetch = vi.fn() + +describe("CloudAPI", () => { + let mockAuthService: Partial + let cloudAPI: CloudAPI + + beforeEach(() => { + // Mock only the methods we need for testing + mockAuthService = { + getSessionToken: vi.fn().mockReturnValue("test-token"), + } + + cloudAPI = new CloudAPI(mockAuthService as AuthService) + vi.clearAllMocks() + }) + + describe("getCloudAgents", () => { + it("should return cloud agents on success", async () => { + const mockAgents = [ + { id: "1", name: "Agent 1", type: "code", icon: "code" }, + { id: "2", name: "Agent 2", type: "chat", icon: "chat" }, + ] + + // Mock successful response with schema-compliant format + ;(global.fetch as ReturnType).mockResolvedValueOnce({ + ok: true, + json: async () => ({ success: true, data: mockAgents }), + }) + + const agents = await cloudAPI.getCloudAgents() + + expect(agents).toEqual(mockAgents) + expect(global.fetch).toHaveBeenCalledWith( + expect.stringContaining("/api/cloud-agents"), + expect.objectContaining({ + method: "GET", + headers: expect.objectContaining({ + Authorization: "Bearer test-token", + }), + }), + ) + }) + + it("should throw AuthenticationError on 401 response", async () => { + // Mock 401 response + ;(global.fetch as ReturnType).mockResolvedValueOnce({ + ok: false, + status: 401, + statusText: "Unauthorized", + json: async () => ({ error: "Authentication required" }), + }) + + await expect(cloudAPI.getCloudAgents()).rejects.toThrow(AuthenticationError) + }) + + it("should throw AuthenticationError when no session token", async () => { + // Mock no session token + mockAuthService.getSessionToken = vi.fn().mockReturnValue(null) + + await expect(cloudAPI.getCloudAgents()).rejects.toThrow(AuthenticationError) + }) + + it("should return empty array when agents array is empty", async () => { + // Mock response with empty agents array + ;(global.fetch as ReturnType).mockResolvedValueOnce({ + ok: true, + json: async () => ({ success: true, data: [] }), + }) + + const agents = await cloudAPI.getCloudAgents() + + expect(agents).toEqual([]) + }) + }) +}) diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts index b412eb1889..b636a8c444 100644 --- a/packages/types/src/cloud.ts +++ b/packages/types/src/cloud.ts @@ -723,6 +723,35 @@ export type LeaveResponse = { timestamp?: string } +/** + * CloudAgent + */ + +export interface CloudAgent { + id: string + name: string + type: string // e.g., "PR Reviewer", "Documentation Writer" + icon?: string // e.g., "pr-reviewer", "documentation-writer" +} + +/** + * CloudAgents API Response + */ + +export const cloudAgentsResponseSchema = z.object({ + success: z.boolean(), + data: z.array( + z.object({ + id: z.string(), + name: z.string(), + type: z.string(), + icon: z.string().optional(), + }), + ), +}) + +export type CloudAgentsResponse = z.infer + /** * UsageStats */ diff --git a/packages/types/src/providers/chutes.ts b/packages/types/src/providers/chutes.ts index c90e044570..53168187e5 100644 --- a/packages/types/src/providers/chutes.ts +++ b/packages/types/src/providers/chutes.ts @@ -87,7 +87,8 @@ export const chutesModels = { supportsPromptCache: false, inputPrice: 0.23, outputPrice: 0.9, - description: "DeepSeek‑V3.1‑Terminus is an update to V3.1 that improves language consistency by reducing CN/EN mix‑ups and eliminating random characters, while strengthening agent capabilities with notably better Code Agent and Search Agent performance.", + description: + "DeepSeek‑V3.1‑Terminus is an update to V3.1 that improves language consistency by reducing CN/EN mix‑ups and eliminating random characters, while strengthening agent capabilities with notably better Code Agent and Search Agent performance.", }, "deepseek-ai/DeepSeek-V3.1-turbo": { maxTokens: 32768, @@ -96,7 +97,8 @@ export const chutesModels = { supportsPromptCache: false, inputPrice: 1.0, outputPrice: 3.0, - description: "DeepSeek-V3.1-turbo is an FP8, speculative-decoding turbo variant optimized for ultra-fast single-shot queries (~200 TPS), with outputs close to the originals and solid function calling/reasoning/structured output, priced at $1/M input and $3/M output tokens, using 2× quota per request and not intended for bulk workloads.", + description: + "DeepSeek-V3.1-turbo is an FP8, speculative-decoding turbo variant optimized for ultra-fast single-shot queries (~200 TPS), with outputs close to the originals and solid function calling/reasoning/structured output, priced at $1/M input and $3/M output tokens, using 2× quota per request and not intended for bulk workloads.", }, "deepseek-ai/DeepSeek-V3.2-Exp": { maxTokens: 163840, @@ -105,7 +107,8 @@ export const chutesModels = { supportsPromptCache: false, inputPrice: 0.25, outputPrice: 0.35, - description: "DeepSeek-V3.2-Exp is an experimental LLM that introduces DeepSeek Sparse Attention to improve long‑context training and inference efficiency while maintaining performance comparable to V3.1‑Terminus.", + description: + "DeepSeek-V3.2-Exp is an experimental LLM that introduces DeepSeek Sparse Attention to improve long‑context training and inference efficiency while maintaining performance comparable to V3.1‑Terminus.", }, "unsloth/Llama-3.3-70B-Instruct": { maxTokens: 32768, // From Groq @@ -387,8 +390,9 @@ export const chutesModels = { contextWindow: 262144, supportsImages: true, supportsPromptCache: false, - inputPrice: 0.1600, - outputPrice: 0.6500, - description: "Qwen3‑VL‑235B‑A22B‑Thinking is an open‑weight MoE vision‑language model (235B total, ~22B activated) optimized for deliberate multi‑step reasoning with strong text‑image‑video understanding and long‑context capabilities.", + inputPrice: 0.16, + outputPrice: 0.65, + description: + "Qwen3‑VL‑235B‑A22B‑Thinking is an open‑weight MoE vision‑language model (235B total, ~22B activated) optimized for deliberate multi‑step reasoning with strong text‑image‑video understanding and long‑context capabilities.", }, } as const satisfies Record diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 91b8687966..0f888e975a 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1034,12 +1034,11 @@ export class ClineProvider window.__vite_plugin_react_preamble_installed__ = true ` - const csp = [ "default-src 'none'", `font-src ${webview.cspSource} data:`, `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`, - `img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:`, + `img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com ${getRooCodeApiUrl()} data:`, `media-src ${webview.cspSource}`, `script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, `connect-src ${webview.cspSource} https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`, @@ -1124,7 +1123,7 @@ export class ClineProvider - +