Compare commits

...

9 Commits

Author SHA1 Message Date
celestial-vault 64b557bdac syntax error 2025-06-20 20:10:41 -07:00
celestial-vault 03158ac198 merge conflicts 2025-06-20 20:10:10 -07:00
celestial-vault c821079626 merge conflicts 2025-06-11 12:45:20 -07:00
celestial-vault e0a08ddc53 change proto fields to snake case 2025-06-11 12:42:26 -07:00
celestial-vault 21033eb502 linter error 2025-06-09 13:31:04 -07:00
celestial-vault 85875d5c4d merge conflicts 2025-06-09 13:30:45 -07:00
celestial-vault ea0d3c14d2 remove parseInt 2025-06-09 13:24:19 -07:00
Elephant Lumps 139ad91004 changeset 2025-05-24 10:33:13 -07:00
Elephant Lumps 3972686972 migrate fetchUserCreditsData 2025-05-24 10:32:44 -07:00
10 changed files with 126 additions and 93 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Migrate fetchUserCreditsData to protobus
+35
View File
@@ -23,6 +23,9 @@ service AccountService {
// Handles authentication state changes from the Firebase context.
// Updates the user info in global state and returns the updated value.
rpc authStateChanged(AuthStateChangedRequest) returns (AuthStateChanged);
// Fetches all user credits data (balance, usage transactions, payment transactions)
rpc fetchUserCreditsData(EmptyRequest) returns (UserCreditsData);
}
message AuthStateChangedRequest {
@@ -39,3 +42,35 @@ message UserInfo {
optional string email = 2;
optional string photo_url = 3;
}
// Response containing all user credits data
message UserCreditsData {
UserCreditsBalance balance = 1;
repeated UsageTransaction usage_transactions = 2;
repeated PaymentTransaction payment_transactions = 3;
}
// User's current credit balance
message UserCreditsBalance {
double current_balance = 1;
}
// Usage transaction record
message UsageTransaction {
string spent_at = 1;
string creator_id = 2;
double credits = 3;
string model_provider = 4;
string model = 5;
int32 prompt_tokens = 6;
int32 completion_tokens = 7;
int32 total_tokens = 8;
}
// Payment transaction record
message PaymentTransaction {
string paid_at = 1;
string creator_id = 2;
int32 amount_cents = 3;
double credits = 4;
}
@@ -0,0 +1,34 @@
import type { Controller } from "../index"
import type { EmptyRequest } from "@shared/proto/common"
import { UserCreditsData } from "@shared/proto/account"
/**
* Handles fetching all user credits data (balance, usage, payments)
* @param controller The controller instance
* @param request Empty request
* @returns User credits data response
*/
export async function fetchUserCreditsData(controller: Controller, request: EmptyRequest): Promise<UserCreditsData> {
try {
if (!controller.accountService) {
throw new Error("Account service not available")
}
// Call the individual RPC variants in parallel
const [balance, usageTransactions, paymentTransactions] = await Promise.all([
controller.accountService.fetchBalanceRPC(),
controller.accountService.fetchUsageTransactionsRPC(),
controller.accountService.fetchPaymentTransactionsRPC(),
])
// Since generated types match exactly, no conversion needed!
return UserCreditsData.create({
balance: balance ? { currentBalance: balance.currentBalance } : { currentBalance: 0 },
usageTransactions: usageTransactions || [],
paymentTransactions: paymentTransactions || [],
})
} catch (error) {
console.error(`Failed to fetch user credits data: ${error}`)
throw error
}
}
-18
View File
@@ -205,10 +205,6 @@ export class Controller {
*/
async handleWebviewMessage(message: WebviewMessage) {
switch (message.type) {
case "fetchUserCreditsData": {
await this.fetchUserCreditsData()
break
}
case "fetchMcpMarketplace": {
await this.fetchMcpMarketplace(message.bool)
break
@@ -492,20 +488,6 @@ export class Controller {
}
}
// Account
async fetchUserCreditsData() {
try {
await Promise.all([
this.accountService?.fetchBalance(),
this.accountService?.fetchUsageTransactions(),
this.accountService?.fetchPaymentTransactions(),
])
} catch (error) {
console.error("Failed to fetch user credits data:", error)
}
}
// Auth
public async validateAuthState(state: string | null): Promise<boolean> {
+16 -34
View File
@@ -49,61 +49,43 @@ export class ClineAccountService {
}
/**
* Fetches the user's current credit balance
* RPC variant that fetches the user's current credit balance without posting to webview
* @returns Balance data or undefined if failed
*/
async fetchBalance(): Promise<BalanceResponse | undefined> {
async fetchBalanceRPC(): Promise<BalanceResponse | undefined> {
try {
const data = await this.authenticatedRequest<BalanceResponse>("/user/credits/balance")
// Post to webview
await this.postMessageToWebview({
type: "userCreditsBalance",
userCreditsBalance: data,
})
return data
} catch (error) {
console.error("Failed to fetch balance:", error)
console.error("Failed to fetch balance (RPC):", error)
return undefined
}
}
/**
* Fetches the user's usage transactions
* RPC variant that fetches the user's usage transactions without posting to webview
* @returns Usage transactions or undefined if failed
*/
async fetchUsageTransactions(): Promise<UsageTransaction[] | undefined> {
async fetchUsageTransactionsRPC(): Promise<UsageTransaction[] | undefined> {
try {
const data = await this.authenticatedRequest<UsageTransaction[]>("/user/credits/usage")
// Post to webview
await this.postMessageToWebview({
type: "userCreditsUsage",
userCreditsUsage: data,
})
return data
const data = await this.authenticatedRequest<{ usageTransactions: UsageTransaction[] }>("/user/credits/usage")
return data.usageTransactions
} catch (error) {
console.error("Failed to fetch usage transactions:", error)
console.error("Failed to fetch usage transactions (RPC):", error)
return undefined
}
}
/**
* Fetches the user's payment transactions
* RPC variant that fetches the user's payment transactions without posting to webview
* @returns Payment transactions or undefined if failed
*/
async fetchPaymentTransactions(): Promise<PaymentTransaction[] | undefined> {
async fetchPaymentTransactionsRPC(): Promise<PaymentTransaction[] | undefined> {
try {
const data = await this.authenticatedRequest<PaymentTransaction[]>("/user/credits/payments")
// Post to webview
await this.postMessageToWebview({
type: "userCreditsPayments",
userCreditsPayments: data,
})
return data
const data = await this.authenticatedRequest<{ paymentTransactions: PaymentTransaction[] }>("/user/credits/payments")
return data.paymentTransactions
} catch (error) {
console.error("Failed to fetch payment transactions:", error)
console.error("Failed to fetch payment transactions (RPC):", error)
return undefined
}
}
+8 -5
View File
@@ -4,15 +4,18 @@ export interface BalanceResponse {
export interface UsageTransaction {
spentAt: string
credits: string
creatorId: string
credits: number
modelProvider: string
model: string
promptTokens: string
completionTokens: string
promptTokens: number
completionTokens: number
totalTokens: number
}
export interface PaymentTransaction {
paidAt: string
amountCents: string
credits: string
creatorId: string
amountCents: number
credits: number
}
+1 -12
View File
@@ -14,15 +14,7 @@ import { UserInfo } from "./UserInfo"
// webview will hold state
export interface ExtensionMessage {
type:
| "action"
| "state"
| "selectedImages"
| "mcpDownloadDetails"
| "userCreditsBalance"
| "userCreditsUsage"
| "userCreditsPayments"
| "grpc_response" // New type for gRPC responses
type: "action" | "state" | "selectedImages" | "mcpDownloadDetails" | "grpc_response" // New type for gRPC responses
text?: string
action?: "accountLogoutClicked"
state?: ExtensionState
@@ -40,9 +32,6 @@ export interface ExtensionMessage {
commits?: GitCommit[]
url?: string
isImage?: boolean
userCreditsBalance?: BalanceResponse
userCreditsUsage?: UsageTransaction[]
userCreditsPayments?: PaymentTransaction[]
success?: boolean
endpoint?: string
isBundled?: boolean
-1
View File
@@ -13,7 +13,6 @@ export interface WebviewMessage {
| "searchCommits"
| "telemetrySetting"
| "clearAllTaskHistory"
| "fetchUserCreditsData"
| "grpc_request"
| "grpc_request_cancel"
@@ -42,30 +42,21 @@ export const ClineAccountView = () => {
const [usageData, setUsageData] = useState<UsageTransaction[]>([])
const [paymentsData, setPaymentsData] = useState<PaymentTransaction[]>([])
// Listen for balance and transaction data updates from the extension
// Fetch all account data when component mounts using gRPC
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const message = event.data
if (message.type === "userCreditsBalance" && message.userCreditsBalance) {
setBalance(message.userCreditsBalance.currentBalance)
} else if (message.type === "userCreditsUsage" && message.userCreditsUsage) {
setUsageData(message.userCreditsUsage.usageTransactions)
} else if (message.type === "userCreditsPayments" && message.userCreditsPayments) {
setPaymentsData(message.userCreditsPayments.paymentTransactions)
}
setIsLoading(false)
}
window.addEventListener("message", handleMessage)
// Fetch all account data when component mounts
if (user) {
setIsLoading(true)
vscode.postMessage({ type: "fetchUserCreditsData" })
}
return () => {
window.removeEventListener("message", handleMessage)
AccountServiceClient.fetchUserCreditsData(EmptyRequest.create())
.then((response) => {
setBalance(response.balance?.currentBalance || 0)
setUsageData(response.usageTransactions)
setPaymentsData(response.paymentTransactions)
setIsLoading(false)
})
.catch((error) => {
console.error("Failed to fetch user credits data:", error)
setIsLoading(false)
})
}
}, [user])
@@ -135,7 +126,20 @@ export const ClineAccountView = () => {
<VSCodeButton
appearance="icon"
className="mt-1"
onClick={() => vscode.postMessage({ type: "fetchUserCreditsData" })}>
onClick={() => {
setIsLoading(true)
AccountServiceClient.fetchUserCreditsData(EmptyRequest.create())
.then((response) => {
setBalance(response.balance?.currentBalance || 0)
setUsageData(response.usageTransactions)
setPaymentsData(response.paymentTransactions)
setIsLoading(false)
})
.catch((error) => {
console.error("Failed to refresh user credits data:", error)
setIsLoading(false)
})
}}>
<span className="codicon codicon-refresh"></span>
</VSCodeButton>
</>
@@ -92,7 +92,7 @@ const CreditsHistoryTable = ({ isLoading, usageData, paymentsData }: CreditsHist
<VSCodeDataGridCell grid-column="1">
{formatTimestamp(row.paidAt)}
</VSCodeDataGridCell>
<VSCodeDataGridCell grid-column="2">{`$${formatDollars(parseInt(row.amountCents))}`}</VSCodeDataGridCell>
<VSCodeDataGridCell grid-column="2">{`$${formatDollars(row.amountCents)}`}</VSCodeDataGridCell>
<VSCodeDataGridCell grid-column="3">{`${row.credits}`}</VSCodeDataGridCell>
</VSCodeDataGridRow>
))}