diff --git a/packages/cloud/src/CloudAPI.ts b/packages/cloud/src/CloudAPI.ts index d1c3f89c2b89..9fb51b553fe4 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 000000000000..2390878dacfe --- /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 b412eb18891f..b636a8c44490 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 c90e0445705c..53168187e5bc 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 91b868796689..0f888e975a92 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 - +