Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7b43022
Working chart in CloudView
brunobergher Sep 30, 2025
af0a90e
Lint fixes
brunobergher Sep 30, 2025
2c31616
Actually makes data flow in
brunobergher Sep 30, 2025
d84e031
i18n
brunobergher Sep 30, 2025
eda8cae
Adds utm params
brunobergher Sep 30, 2025
c33cc29
Adds links to CloudView from mentions of cost elsewhere if the user i…
brunobergher Sep 30, 2025
70a2478
Removes requirement of user being authenticated for links to cloud
brunobergher Sep 30, 2025
d1e7192
Adds missing translations
brunobergher Sep 30, 2025
f70062d
fix(cloud): usage preview fixes — clear timeout on response; correct …
roomote Sep 30, 2025
7ab010a
feat: add GLM-4.6 model support for z.ai provider (#8408)
roomote[bot] Sep 30, 2025
b66c5ec
chore: add changeset for v3.28.14 (#8413)
mrubens Sep 30, 2025
e46eb8a
Changeset version bump (#8414)
github-actions[bot] Sep 30, 2025
22113cd
A couple more sonnet 4.5 fixes (#8421)
mrubens Sep 30, 2025
1e287f9
chore: Remove unsupported Gemini 2.5 Flash Image Preview free model (…
SannidhyaSah Sep 30, 2025
bca20e5
Include reasoning messages in cloud tasks (#8401)
mrubens Sep 30, 2025
3ec844a
fix: show send button when only images are selected in chat textarea …
roomote[bot] Sep 30, 2025
3f85776
Add structured data to the homepage (#8427)
mrubens Oct 1, 2025
82e093b
Consolidate formatting functions
brunobergher Oct 1, 2025
09b2fa2
Fixes in TaskHeader
brunobergher Oct 1, 2025
ac7d139
Chinese fix
brunobergher Oct 1, 2025
a989db7
Removes .review directory, no idea what happened here
brunobergher Oct 1, 2025
0d1cd24
Removes tmp directories
brunobergher Oct 1, 2025
6f37b02
Moves types to shared library
brunobergher Oct 1, 2025
61a133d
Test fixes
brunobergher Oct 1, 2025
c13bf8b
Merge remote-tracking branch 'origin/main' into bb/experiment-stats
mrubens Oct 1, 2025
0393a40
Merge remote-tracking branch 'origin/main' into bb/experiment-stats
brunobergher Oct 1, 2025
5b83dd1
Merge branch 'bb/experiment-stats' of github.com:RooCodeInc/Roo-Code …
brunobergher Oct 1, 2025
ab77ff2
Merge remote-tracking branch 'origin/main' into bb/experiment-stats
jr Oct 1, 2025
35154ef
a11y improvements
brunobergher Oct 2, 2025
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
1 change: 1 addition & 0 deletions .review/pr-8274
Submodule pr-8274 added at e46929
46 changes: 40 additions & 6 deletions packages/cloud/src/CloudAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ import { getRooCodeApiUrl } from "./config.js"
import { getUserAgent } from "./utils.js"
import { AuthenticationError, CloudAPIError, NetworkError, TaskNotFoundError } from "./errors.js"

// Usage stats schemas
const usageStatsSchema = z.object({
Copy link
Collaborator

@mrubens mrubens Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should go in packages/types/src/cloud.ts? I think we could then share it with the cloud side.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usually ends up being easiest (arguably...) to sequence as

  1. Add the types to packages/types.
  2. Update @roo-code/types
  3. Update @roo-code/types in cloud + ship the cloud functionality.
  4. Finally actually use the cloud functionality back here.

success: z.boolean(),
data: z.object({
dates: z.array(z.string()), // Array of date strings
tasks: z.array(z.number()), // Array of task counts
tokens: z.array(z.number()), // Array of token counts
costs: z.array(z.number()), // Array of costs in USD
totals: z.object({
tasks: z.number(),
tokens: z.number(),
cost: z.number(), // Total cost in USD
}),
}),
period: z.number(), // Period in days (e.g., 30)
})

export type UsageStats = z.infer<typeof usageStatsSchema>

interface CloudAPIRequestOptions extends Omit<RequestInit, "headers"> {
timeout?: number
headers?: Record<string, string>
Expand Down Expand Up @@ -53,9 +72,11 @@ export class CloudAPI {
})

if (!response.ok) {
this.log(`[CloudAPI] Request to ${endpoint} failed with status ${response.status}`)
await this.handleErrorResponse(response, endpoint)
}

// Log before attempting to read the body
const data = await response.json()

if (parseResponse) {
Expand Down Expand Up @@ -86,9 +107,15 @@ export class CloudAPI {
let responseBody: unknown

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

try {
responseBody = JSON.parse(bodyText)
} catch {
responseBody = bodyText
}
} catch (_error) {
responseBody = "Failed to read error response"
}

switch (response.status) {
Expand All @@ -109,15 +136,12 @@ export class CloudAPI {
}

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
}

Expand All @@ -134,4 +158,14 @@ export class CloudAPI {
.parse(data),
})
}

async getUsagePreview(): Promise<UsageStats> {
const response = await this.request("/api/analytics/usage/daily?period=7", {
method: "GET",
parseResponse: (data) => {
return usageStatsSchema.parse(data)
},
})
return response
}
}
63 changes: 63 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3110,5 +3110,68 @@ export const webviewMessageHandler = async (
})
break
}
case "getUsagePreview": {
try {
// Get the CloudAPI instance and fetch usage preview
const cloudApi = CloudService.instance.cloudAPI
if (!cloudApi) {
// User is not authenticated
provider.log("[webviewMessageHandler] User not authenticated for usage preview")
await provider.postMessageToWebview({
type: "usagePreviewData",
error: "Authentication required",
data: null,
})
break
}

// Fetch usage preview data
const rawUsageData = await cloudApi.getUsagePreview()

// Transform the data to match UI expectations
// The API returns data with separate arrays, but UI expects an array of day objects
const dates = rawUsageData.data?.dates ?? []
const tasks = rawUsageData.data?.tasks ?? []
const tokens = rawUsageData.data?.tokens ?? []
const costs = rawUsageData.data?.costs ?? []
const len = Math.min(dates.length, tasks.length, tokens.length, costs.length)

const transformedData = {
days: Array.from({ length: len }).map((_, index) => ({
date: dates[index] ?? "",
taskCount: tasks[index] ?? 0,
tokenCount: tokens[index] ?? 0,
cost: costs[index] ?? 0,
})),
totals: rawUsageData.data?.totals || {
tasks: 0,
tokens: 0,
cost: 0,
},
}

// Send the transformed data back to the webview
await provider.postMessageToWebview({
type: "usagePreviewData",
data: transformedData,
error: undefined,
})
} catch (error) {
provider.log(
`[webviewMessageHandler] Failed to fetch usage preview: ${error instanceof Error ? error.message : String(error)}`,
)
provider.log(
`[webviewMessageHandler] Error stack trace: ${error instanceof Error ? error.stack : "No stack trace"}`,
)

// Send error back to webview
await provider.postMessageToWebview({
type: "usagePreviewData",
error: error instanceof Error ? error.message : "Failed to load usage data",
data: null,
})
}
break
}
}
}
2 changes: 2 additions & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export interface ExtensionMessage {
| "insertTextIntoTextarea"
| "dismissedUpsells"
| "organizationSwitchResult"
| "usagePreviewData"
text?: string
payload?: any // Add a generic payload for now, can refine later
action?:
Expand Down Expand Up @@ -205,6 +206,7 @@ export interface ExtensionMessage {
queuedMessages?: QueuedMessage[]
list?: string[] // For dismissedUpsells
organizationId?: string | null // For organizationSwitchResult
data?: any // For usagePreviewData
}

export type ExtensionState = Pick<
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export interface WebviewMessage {
| "editQueuedMessage"
| "dismissUpsell"
| "getDismissedUpsells"
| "getUsagePreview"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
Expand Down
1 change: 1 addition & 0 deletions tmp/pr-8287-Roo-Code
Submodule pr-8287-Roo-Code added at 88a473
1 change: 1 addition & 0 deletions tmp/pr-8412
Submodule pr-8412 added at 1339c7
16 changes: 13 additions & 3 deletions webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export const ChatRowContent = ({
}: ChatRowContentProps) => {
const { t } = useTranslation()

const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState()
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration, cloudIsAuthenticated } =
useExtensionState()
const { info: model } = useSelectedModel(apiConfiguration)
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState("")
Expand Down Expand Up @@ -1074,8 +1075,17 @@ export const ChatRowContent = ({
{title}
</div>
<div
className="text-xs text-vscode-dropdown-foreground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
style={{ opacity: cost !== null && cost !== undefined && cost > 0 ? 1 : 0 }}>
className={cn(
"text-xs text-vscode-dropdown-foreground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg",
cloudIsAuthenticated &&
"cursor-pointer hover:bg-vscode-dropdown-background hover:border-vscode-dropdown-border transition-colors",
)}
style={{ opacity: cost !== null && cost !== undefined && cost > 0 ? 1 : 0 }}
onClick={(e) => {
e.stopPropagation() // Prevent parent onClick from firing
vscode.postMessage({ type: "switchTab", tab: "cloud" })
}}
title={cloudIsAuthenticated ? t("chat:apiRequest.viewTokenUsage") : undefined}>
${Number(cost || 0)?.toFixed(4)}
</div>
</div>
Expand Down
16 changes: 15 additions & 1 deletion webview-ui/src/components/chat/TaskHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react"
import { FoldVertical, ChevronUp, ChevronDown, ChartColumn } from "lucide-react"
import prettyBytes from "pretty-bytes"

import type { ClineMessage } from "@roo-code/types"
Expand Down Expand Up @@ -302,6 +302,20 @@ const TaskHeader = ({
</th>
<td className="align-top">
<span>${totalCost?.toFixed(2)}</span>
<StandardTooltip content={t("chat:apiRequest.viewTokenUsage")}>
<ChartColumn
className="inline size-3.5 -mt-0.5 ml-2 text-vscode-textLink-foreground cursor-pointer hover:text-vscode-textLink-activeForeground transition-colors"
onClick={(e) => {
e.stopPropagation()
import("@src/utils/vscode").then(({ vscode }) => {
vscode.postMessage({
type: "switchTab",
tab: "cloud",
})
})
}}
/>
</StandardTooltip>
</td>
</tr>
)}
Expand Down
Loading
Loading