Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/cloud/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { config } from "@roo-code/config-eslint/base"

/** @type {import("eslint").Linter.Config} */
export default [...config]
25 changes: 25 additions & 0 deletions packages/cloud/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@roo-code/cloud",
"description": "Roo Code Cloud VSCode integration.",
"version": "0.0.0",
"type": "module",
"exports": "./src/index.ts",
"scripts": {
"lint": "eslint src --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"test": "vitest run",
"clean": "rimraf dist .turbo"
},
"dependencies": {
"@roo-code/telemetry": "workspace:^",
"@roo-code/types": "workspace:^",
"zod": "^3.25.61"
},
"devDependencies": {
"@roo-code/config-eslint": "workspace:^",
"@roo-code/config-typescript": "workspace:^",
"@types/node": "20.x",
"@types/vscode": "^1.84.0",
"vitest": "^3.2.3"
}
}
122 changes: 122 additions & 0 deletions packages/cloud/src/CloudAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { type ShareVisibility, type ShareResponse, shareResponseSchema } from "@roo-code/types"

import { getRooCodeApiUrl } from "./config"
import type { AuthService } from "./auth"
import { getUserAgent } from "./utils"
import { AuthenticationError, CloudAPIError, NetworkError, TaskNotFoundError } from "./errors"

interface CloudAPIRequestOptions extends Omit<RequestInit, "headers"> {
timeout?: number
headers?: Record<string, string>
}

export class CloudAPI {
private authService: AuthService
private log: (...args: unknown[]) => void
private baseUrl: string

constructor(authService: AuthService, log?: (...args: unknown[]) => void) {
this.authService = authService
this.log = log || console.log
this.baseUrl = getRooCodeApiUrl()
}

private async request<T>(
endpoint: string,
options: CloudAPIRequestOptions & {
parseResponse?: (data: unknown) => T
} = {},
): Promise<T> {
const { timeout = 10000, parseResponse, headers = {}, ...fetchOptions } = options

const sessionToken = this.authService.getSessionToken()

if (!sessionToken) {
throw new AuthenticationError()
}

const url = `${this.baseUrl}${endpoint}`

const requestHeaders = {
"Content-Type": "application/json",
Authorization: `Bearer ${sessionToken}`,
"User-Agent": getUserAgent(),
...headers,
}

try {
const response = await fetch(url, {
...fetchOptions,
headers: requestHeaders,
signal: AbortSignal.timeout(timeout),
})

if (!response.ok) {
await this.handleErrorResponse(response, endpoint)
}

const data = await response.json()

if (parseResponse) {
return parseResponse(data)
}

return data as T
} catch (error) {
if (error instanceof TypeError && error.message.includes("fetch")) {
throw new NetworkError(`Network error while calling ${endpoint}`)
}

if (error instanceof CloudAPIError) {
throw error
}

if (error instanceof Error && error.name === "AbortError") {
throw new CloudAPIError(`Request to ${endpoint} timed out`, undefined, undefined)
}

throw new CloudAPIError(
`Unexpected error while calling ${endpoint}: ${error instanceof Error ? error.message : String(error)}`,
)
}
}

private async handleErrorResponse(response: Response, endpoint: string): Promise<never> {
let responseBody: unknown

try {
responseBody = await response.json()
} catch {
responseBody = await response.text()
}

switch (response.status) {
case 401:
throw new AuthenticationError()
case 404:
if (endpoint.includes("/share")) {
throw new TaskNotFoundError()
}
throw new CloudAPIError(`Resource not found: ${endpoint}`, 404, responseBody)
default:
throw new CloudAPIError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
responseBody,
)
}
}

async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
this.log(`[CloudAPI] Sharing task ${taskId} with visibility: ${visibility}`)

const response = await this.request("/api/extension/share", {
method: "POST",
body: JSON.stringify({ taskId, visibility }),
parseResponse: (data) => shareResponseSchema.parse(data),
})

this.log("[CloudAPI] Share response:", response)
return response
}
}
Loading
Loading