Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ export type ExtensionState = Pick<
mdmCompliant?: boolean
taskSyncEnabled: boolean
openAiCodexIsAuthenticated?: boolean
zooCodeIsAuthenticated?: boolean
zooCodeUserName?: string
zooCodeUserEmail?: string
zooCodeUserImage?: string
zooCodeBaseUrl?: string
debug?: boolean

/**
Expand Down Expand Up @@ -505,6 +510,7 @@ export interface WebviewMessage {
| "rooCloudManualUrl"
| "openAiCodexSignIn"
| "openAiCodexSignOut"
| "zooCodeSignOut"
| "switchOrganization"
| "condenseTaskContextRequest"
| "requestIndexingStatus"
Expand Down
32 changes: 28 additions & 4 deletions src/activate/handleUri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@ import * as vscode from "vscode"

import { getRouterUnavailableSignInMessage } from "../core/config/routerRemoval"
import { ClineProvider } from "../core/webview/ClineProvider"
import { handleAuthCallback as handleZooCodeAuthCallback, setZooCodeUserInfo } from "../services/zoo-code-auth"

export const handleUri = async (uri: vscode.Uri) => {
const path = uri.path
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
const visibleProvider = ClineProvider.getVisibleInstance()

if (!visibleProvider) {
return
}

switch (path) {
case "/openrouter": {
if (!visibleProvider) return
const code = query.get("code")
if (code) {
await visibleProvider.handleOpenRouterCallback(code)
}
break
}
case "/requesty": {
if (!visibleProvider) return
const code = query.get("code")
const baseUrl = query.get("baseUrl")
if (code) {
Expand All @@ -32,6 +31,31 @@ export const handleUri = async (uri: vscode.Uri) => {
vscode.window.showInformationMessage(getRouterUnavailableSignInMessage())
break
}
case "/auth-callback": {
Comment thread
roomote[bot] marked this conversation as resolved.
const token = query.get("token")
if (token) {
// Extract user info from callback URL params
// URLSearchParams.get() already decodes percent-encoded values - no need for decodeURIComponent
const name = query.get("name") ?? undefined
const email = query.get("email") ?? undefined
const image = query.get("image") ?? undefined

const success = await handleZooCodeAuthCallback(token)
if (success && visibleProvider) {
Comment thread
roomote[bot] marked this conversation as resolved.
Outdated
// Store user info only after successful auth validation
if (name || email || image) {
await setZooCodeUserInfo({
name,
email,
image,
})
}
// Update the active API configuration to use zoo-code with the new token
Comment thread
roomote[bot] marked this conversation as resolved.
Outdated
await visibleProvider.handleZooCodeCallback(token)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
break
}
default:
break
}
Expand Down
19 changes: 19 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3155,6 +3155,25 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
cacheReadTokens: tokens.cacheRead,
cost: tokens.total ?? costResult.totalCost,
})

// Zoo Code observability telemetry
import("../../services/zoo-telemetry")
.then(({ sendLlmTelemetry }) =>
sendLlmTelemetry({
taskId: this.taskId,
provider: this.apiConfiguration?.apiProvider ?? "unknown",
model: this.apiConfiguration
? (getModelId(this.apiConfiguration) ?? "unknown")
: "unknown",
mode: this.taskMode,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
inputTokens: costResult.totalInputTokens,
outputTokens: costResult.totalOutputTokens,
cacheReadTokens: tokens.cacheRead ?? 0,
cacheWriteTokens: tokens.cacheWrite ?? 0,
totalCost: tokens.total ?? costResult.totalCost,
}).catch(() => {}),
)
.catch(() => {})
}
}

Expand Down
39 changes: 38 additions & 1 deletion src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,9 @@ export class ClineProvider

// Create named listener functions so we can remove them later.
const onTaskStarted = () => this.emit(RooCodeEventName.TaskStarted, instance.taskId)
const onTaskCompleted = (taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage) =>
const onTaskCompleted = (taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage) => {
this.emit(RooCodeEventName.TaskCompleted, taskId, tokenUsage, toolUsage)
}
const onTaskAborted = async () => {
this.emit(RooCodeEventName.TaskAborted, instance.taskId)

Expand Down Expand Up @@ -1681,6 +1682,15 @@ export class ClineProvider
await this.upsertProviderProfile(currentApiConfigName, newConfiguration)
}

// Zoo Code Auth (for observability telemetry)

async handleZooCodeCallback(_token: string) {
// Auth mutation (token storage, subscription check, success toast) was already
// performed by handleAuthCallback() in handleUri.ts before this method was called.
// This method only needs to refresh the webview state to reflect the new auth status.
await this.postStateToWebview()
}

// Requesty

async handleRequestyCallback(code: string, baseUrl: string | null) {
Expand Down Expand Up @@ -2279,6 +2289,33 @@ export class ClineProvider
return false
}
})(),
zooCodeIsAuthenticated: await (async () => {
try {
const { isZooCodeAuthenticated } = await import("../../services/zoo-code-auth")
return await isZooCodeAuthenticated()
} catch {
return false
}
})(),
...(() => {
try {
const { getCachedZooCodeUserInfo, getZooCodeBaseUrl } = require("../../services/zoo-code-auth")
const userInfo = getCachedZooCodeUserInfo()
return {
zooCodeUserName: userInfo.name,
zooCodeUserEmail: userInfo.email,
zooCodeUserImage: userInfo.image,
zooCodeBaseUrl: getZooCodeBaseUrl(),
}
} catch {
return {
zooCodeUserName: undefined,
zooCodeUserEmail: undefined,
zooCodeUserImage: undefined,
zooCodeBaseUrl: "https://www.zoocode.dev",
}
}
})(),
debug: vscode.workspace.getConfiguration(Package.name).get<boolean>("debug", false),
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,18 @@ export const webviewMessageHandler = async (
await provider.postStateToWebview()
break
}
case "zooCodeSignOut": {
try {
const { clearZooCodeToken } = await import("../../services/zoo-code-auth")
await clearZooCodeToken()
await provider.postStateToWebview()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error) {
provider.log(
`Failed to sign out of Zoo Code: ${error instanceof Error ? error.message : String(error)}`,
)
}
break
}
case "switchOrganization": {
try {
const organizationId = message.organizationId ?? null
Expand Down
4 changes: 4 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
} from "./activate"
import { initializeI18n } from "./i18n"
import { initializeModelCacheRefresh } from "./api/providers/fetchers/modelCache"
import { initZooCodeAuth } from "./services/zoo-code-auth"

/**
* Built using https://github.com/microsoft/vscode-webview-ui-toolkit
Expand Down Expand Up @@ -158,6 +159,9 @@ export async function activate(context: vscode.ExtensionContext) {
// Initialize OpenAI Codex OAuth manager for ChatGPT subscription-based access.
openAiCodexOAuthManager.initialize(context, (message) => outputChannel.appendLine(message))

// Initialize Zoo Code auth service for extension session token management.
await initZooCodeAuth(context)

// Get default commands from configuration.
const defaultCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []

Expand Down
Loading
Loading