mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
363 lines
9.4 KiB
TypeScript
363 lines
9.4 KiB
TypeScript
import type { Disposable, ExtensionContext } from "vscode"
|
|
import EventEmitter from "events"
|
|
|
|
import type {
|
|
TelemetryEvent,
|
|
ClineMessage,
|
|
CloudServiceEvents,
|
|
AuthService,
|
|
SettingsService,
|
|
CloudUserInfo,
|
|
OrganizationAllowList,
|
|
OrganizationSettings,
|
|
ShareVisibility,
|
|
UserSettingsConfig,
|
|
UserSettingsData,
|
|
UserFeatures,
|
|
} from "@roo-code/types"
|
|
|
|
import { TaskNotFoundError } from "./errors.js"
|
|
import { WebAuthService } from "./WebAuthService.js"
|
|
import { StaticTokenAuthService } from "./StaticTokenAuthService.js"
|
|
import { CloudSettingsService } from "./CloudSettingsService.js"
|
|
import { StaticSettingsService } from "./StaticSettingsService.js"
|
|
import { CloudTelemetryClient as TelemetryClient } from "./TelemetryClient.js"
|
|
import { CloudShareService } from "./CloudShareService.js"
|
|
import { CloudAPI } from "./CloudAPI.js"
|
|
|
|
type AuthStateChangedPayload = CloudServiceEvents["auth-state-changed"][0]
|
|
type AuthUserInfoPayload = CloudServiceEvents["user-info"][0]
|
|
type SettingsPayload = CloudServiceEvents["settings-updated"][0]
|
|
|
|
export class CloudService extends EventEmitter<CloudServiceEvents> implements Disposable {
|
|
private static _instance: CloudService | null = null
|
|
|
|
private context: ExtensionContext
|
|
|
|
private authStateListener: (data: AuthStateChangedPayload) => void
|
|
private authUserInfoListener: (data: AuthUserInfoPayload) => void
|
|
private settingsListener: (data: SettingsPayload) => void
|
|
|
|
private isInitialized = false
|
|
private log: (...args: unknown[]) => void
|
|
|
|
/**
|
|
* Services
|
|
*/
|
|
|
|
private _authService: AuthService | null = null
|
|
|
|
public get authService() {
|
|
return this._authService
|
|
}
|
|
|
|
private _settingsService: SettingsService | null = null
|
|
|
|
public get settingsService() {
|
|
return this._settingsService
|
|
}
|
|
|
|
private _telemetryClient: TelemetryClient | null = null
|
|
|
|
public get telemetryClient() {
|
|
return this._telemetryClient
|
|
}
|
|
|
|
private _shareService: CloudShareService | null = null
|
|
|
|
public get shareService() {
|
|
return this._shareService
|
|
}
|
|
|
|
private _cloudAPI: CloudAPI | null = null
|
|
|
|
public get cloudAPI() {
|
|
return this._cloudAPI
|
|
}
|
|
|
|
private constructor(context: ExtensionContext, log?: (...args: unknown[]) => void) {
|
|
super()
|
|
|
|
this.context = context
|
|
this.log = log || console.log
|
|
|
|
this.authStateListener = (data: AuthStateChangedPayload) => {
|
|
this.emit("auth-state-changed", data)
|
|
}
|
|
|
|
this.authUserInfoListener = (data: AuthUserInfoPayload) => {
|
|
this.emit("user-info", data)
|
|
}
|
|
|
|
this.settingsListener = (data: SettingsPayload) => {
|
|
this.emit("settings-updated", data)
|
|
}
|
|
}
|
|
|
|
public async initialize(): Promise<void> {
|
|
if (this.isInitialized) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
// For testing you can create a token with:
|
|
// `pnpm --filter @roo-code-cloud/roomote-cli development auth job-token --job-id 1 --user-id user_2xmBhejNeDTwanM8CgIOnMgVxzC --org-id org_2wbhchVXZMQl8OS1yt0mrDazCpW`
|
|
// The token will last for 1 hour.
|
|
const cloudToken = process.env.ROO_CODE_CLOUD_TOKEN
|
|
|
|
if (cloudToken && cloudToken.length > 0) {
|
|
this._authService = new StaticTokenAuthService(this.context, cloudToken, this.log)
|
|
} else {
|
|
this._authService = new WebAuthService(this.context, this.log)
|
|
}
|
|
|
|
this._authService.on("auth-state-changed", this.authStateListener)
|
|
this._authService.on("user-info", this.authUserInfoListener)
|
|
await this._authService.initialize()
|
|
|
|
// Check for static settings environment variable.
|
|
const staticOrgSettings = process.env.ROO_CODE_CLOUD_ORG_SETTINGS
|
|
|
|
if (staticOrgSettings && staticOrgSettings.length > 0) {
|
|
this._settingsService = new StaticSettingsService(staticOrgSettings, this.log)
|
|
} else {
|
|
const cloudSettingsService = new CloudSettingsService(this.context, this._authService, this.log)
|
|
|
|
cloudSettingsService.on("settings-updated", this.settingsListener)
|
|
await cloudSettingsService.initialize()
|
|
|
|
this._settingsService = cloudSettingsService
|
|
}
|
|
|
|
this._cloudAPI = new CloudAPI(this._authService, this.log)
|
|
|
|
this._telemetryClient = new TelemetryClient(this._authService, this._settingsService)
|
|
|
|
this._shareService = new CloudShareService(this._cloudAPI, this._settingsService, this.log)
|
|
|
|
this.isInitialized = true
|
|
} catch (error) {
|
|
this.log("[CloudService] Failed to initialize:", error)
|
|
throw new Error(`Failed to initialize CloudService: ${error}`)
|
|
}
|
|
}
|
|
|
|
// AuthService
|
|
|
|
public async login(): Promise<void> {
|
|
this.ensureInitialized()
|
|
return this.authService!.login()
|
|
}
|
|
|
|
public async logout(): Promise<void> {
|
|
this.ensureInitialized()
|
|
return this.authService!.logout()
|
|
}
|
|
|
|
public isAuthenticated(): boolean {
|
|
this.ensureInitialized()
|
|
return this.authService!.isAuthenticated()
|
|
}
|
|
|
|
public hasActiveSession(): boolean {
|
|
this.ensureInitialized()
|
|
return this.authService!.hasActiveSession()
|
|
}
|
|
|
|
public hasOrIsAcquiringActiveSession(): boolean {
|
|
this.ensureInitialized()
|
|
return this.authService!.hasOrIsAcquiringActiveSession()
|
|
}
|
|
|
|
public getUserInfo(): CloudUserInfo | null {
|
|
this.ensureInitialized()
|
|
return this.authService!.getUserInfo()
|
|
}
|
|
|
|
public getOrganizationId(): string | null {
|
|
this.ensureInitialized()
|
|
const userInfo = this.authService!.getUserInfo()
|
|
return userInfo?.organizationId || null
|
|
}
|
|
|
|
public getOrganizationName(): string | null {
|
|
this.ensureInitialized()
|
|
const userInfo = this.authService!.getUserInfo()
|
|
return userInfo?.organizationName || null
|
|
}
|
|
|
|
public getOrganizationRole(): string | null {
|
|
this.ensureInitialized()
|
|
const userInfo = this.authService!.getUserInfo()
|
|
return userInfo?.organizationRole || null
|
|
}
|
|
|
|
public hasStoredOrganizationId(): boolean {
|
|
this.ensureInitialized()
|
|
return this.authService!.getStoredOrganizationId() !== null
|
|
}
|
|
|
|
public getStoredOrganizationId(): string | null {
|
|
this.ensureInitialized()
|
|
return this.authService!.getStoredOrganizationId()
|
|
}
|
|
|
|
public getAuthState(): string {
|
|
this.ensureInitialized()
|
|
return this.authService!.getState()
|
|
}
|
|
|
|
public async handleAuthCallback(
|
|
code: string | null,
|
|
state: string | null,
|
|
organizationId?: string | null,
|
|
): Promise<void> {
|
|
this.ensureInitialized()
|
|
return this.authService!.handleCallback(code, state, organizationId)
|
|
}
|
|
|
|
// SettingsService
|
|
|
|
public getAllowList(): OrganizationAllowList {
|
|
this.ensureInitialized()
|
|
return this.settingsService!.getAllowList()
|
|
}
|
|
|
|
public getOrganizationSettings(): OrganizationSettings | undefined {
|
|
this.ensureInitialized()
|
|
return this.settingsService!.getSettings()
|
|
}
|
|
|
|
public getUserSettings(): UserSettingsData | undefined {
|
|
this.ensureInitialized()
|
|
return this.settingsService!.getUserSettings()
|
|
}
|
|
|
|
public getUserFeatures(): UserFeatures {
|
|
this.ensureInitialized()
|
|
return this.settingsService!.getUserFeatures()
|
|
}
|
|
|
|
public getUserSettingsConfig(): UserSettingsConfig {
|
|
this.ensureInitialized()
|
|
return this.settingsService!.getUserSettingsConfig()
|
|
}
|
|
|
|
public async updateUserSettings(settings: Partial<UserSettingsConfig>): Promise<boolean> {
|
|
this.ensureInitialized()
|
|
return this.settingsService!.updateUserSettings(settings)
|
|
}
|
|
|
|
// TelemetryClient
|
|
|
|
public captureEvent(event: TelemetryEvent): void {
|
|
this.ensureInitialized()
|
|
this.telemetryClient!.capture(event)
|
|
}
|
|
|
|
// ShareService
|
|
|
|
public async shareTask(
|
|
taskId: string,
|
|
visibility: ShareVisibility = "organization",
|
|
clineMessages?: ClineMessage[],
|
|
) {
|
|
this.ensureInitialized()
|
|
|
|
try {
|
|
return await this.shareService!.shareTask(taskId, visibility)
|
|
} catch (error) {
|
|
if (error instanceof TaskNotFoundError && clineMessages) {
|
|
// Backfill messages and retry.
|
|
await this.telemetryClient!.backfillMessages(clineMessages, taskId)
|
|
return await this.shareService!.shareTask(taskId, visibility)
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
public async canShareTask(): Promise<boolean> {
|
|
this.ensureInitialized()
|
|
return this.shareService!.canShareTask()
|
|
}
|
|
|
|
// Lifecycle
|
|
|
|
public dispose(): void {
|
|
if (this.authService) {
|
|
this.authService.off("auth-state-changed", this.authStateListener)
|
|
this.authService.off("user-info", this.authUserInfoListener)
|
|
}
|
|
|
|
if (this.settingsService) {
|
|
if (this.settingsService instanceof CloudSettingsService) {
|
|
this.settingsService.off("settings-updated", this.settingsListener)
|
|
}
|
|
|
|
this.settingsService.dispose()
|
|
}
|
|
|
|
this.isInitialized = false
|
|
}
|
|
|
|
private ensureInitialized(): void {
|
|
if (!this.isInitialized) {
|
|
throw new Error("CloudService not initialized.")
|
|
}
|
|
}
|
|
|
|
static get instance(): CloudService {
|
|
if (!this._instance) {
|
|
throw new Error("CloudService not initialized")
|
|
}
|
|
|
|
return this._instance
|
|
}
|
|
|
|
static async createInstance(
|
|
context: ExtensionContext,
|
|
log?: (...args: unknown[]) => void,
|
|
eventHandlers?: Partial<{
|
|
[K in keyof CloudServiceEvents]: (...args: CloudServiceEvents[K]) => void
|
|
}>,
|
|
): Promise<CloudService> {
|
|
if (this._instance) {
|
|
throw new Error("CloudService instance already created")
|
|
}
|
|
|
|
this._instance = new CloudService(context, log)
|
|
|
|
await this._instance.initialize()
|
|
|
|
if (eventHandlers) {
|
|
for (const [event, handler] of Object.entries(eventHandlers)) {
|
|
if (handler) {
|
|
this._instance.on(
|
|
event as keyof CloudServiceEvents,
|
|
handler as (...args: CloudServiceEvents[keyof CloudServiceEvents]) => void,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
await this._instance.authService?.broadcast()
|
|
|
|
return this._instance
|
|
}
|
|
|
|
static hasInstance(): boolean {
|
|
return this._instance !== null && this._instance.isInitialized
|
|
}
|
|
|
|
static resetInstance(): void {
|
|
if (this._instance) {
|
|
this._instance.dispose()
|
|
this._instance = null
|
|
}
|
|
}
|
|
|
|
static isEnabled(): boolean {
|
|
return !!this._instance?.isAuthenticated()
|
|
}
|
|
}
|