Skip to content
19 changes: 18 additions & 1 deletion packages/cloud/src/CloudAPI.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
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,
} from "@roo-code/types"

import { getRooCodeApiUrl } from "./config.js"
import { getUserAgent } from "./utils.js"
Expand Down Expand Up @@ -134,4 +140,15 @@ export class CloudAPI {
.parse(data),
})
}

async getCloudAgents(): Promise<CloudAgent[]> {
this.log("[CloudAPI] Fetching cloud agents")

const response = await this.request<{ success: boolean; data: CloudAgent[] }>("/api/cloud-agents", {
method: "GET",
})

this.log("[CloudAPI] Cloud agents response:", response)
return response.data || []
}
}
81 changes: 81 additions & 0 deletions packages/cloud/src/__tests__/CloudAPI.test.ts
Original file line number Diff line number Diff line change
@@ -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<AuthService>
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
;(global.fetch as ReturnType<typeof vi.fn>).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<typeof vi.fn>).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 data is missing", async () => {
// Mock response with no data
;(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
ok: true,
json: async () => ({ success: true }),
})

const agents = await cloudAPI.getCloudAgents()

expect(agents).toEqual([])
})
})
})
28 changes: 28 additions & 0 deletions packages/types/src/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,34 @@ 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({
agents: z.array(
z.object({
id: z.string(),
name: z.string(),
type: z.string(),
icon: z.string().optional(),
}),
),
})

export type CloudAgentsResponse = z.infer<typeof cloudAgentsResponseSchema>

/**
* UsageStats
*/
Expand Down
16 changes: 10 additions & 6 deletions packages/types/src/providers/chutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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<string, ModelInfo>
3 changes: 1 addition & 2 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1034,12 +1034,11 @@ export class ClineProvider
window.__vite_plugin_react_preamble_installed__ = true
</script>
`

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}`,
Expand Down
Loading