slopus--happy
98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
131 行
3.6 KiB
TypeScript
131 行
3.6 KiB
TypeScript
import { AuthCredentials } from '@/auth/tokenStorage';
|
|
import { backoff } from '@/utils/time';
|
|
import { getServerUrl } from './serverConfig';
|
|
import { getHappyClientId } from './apiSocket';
|
|
|
|
export interface UsageDataPoint {
|
|
timestamp: number;
|
|
tokens: Record<string, number>;
|
|
cost: Record<string, number>;
|
|
reportCount: number;
|
|
}
|
|
|
|
export interface UsageQueryParams {
|
|
sessionId?: string;
|
|
startTime?: number; // Unix timestamp in seconds
|
|
endTime?: number; // Unix timestamp in seconds
|
|
groupBy?: 'hour' | 'day';
|
|
}
|
|
|
|
export interface UsageResponse {
|
|
usage: UsageDataPoint[];
|
|
}
|
|
|
|
/**
|
|
* Query usage data from the server
|
|
*/
|
|
export async function queryUsage(
|
|
credentials: AuthCredentials,
|
|
params: UsageQueryParams = {}
|
|
): Promise<UsageResponse> {
|
|
const API_ENDPOINT = getServerUrl();
|
|
|
|
return await backoff(async () => {
|
|
const response = await fetch(`${API_ENDPOINT}/v1/usage/query`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${credentials.token}`,
|
|
'Content-Type': 'application/json',
|
|
'X-Happy-Client': getHappyClientId(),
|
|
},
|
|
body: JSON.stringify(params)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404 && params.sessionId) {
|
|
throw new Error('Session not found');
|
|
}
|
|
throw new Error(`Failed to query usage: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json() as UsageResponse;
|
|
return data;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper function to get usage for a specific time period
|
|
*/
|
|
export async function getUsageForPeriod(
|
|
credentials: AuthCredentials,
|
|
period: 'today' | '7days' | '30days',
|
|
sessionId?: string
|
|
): Promise<UsageResponse> {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const oneDaySeconds = 24 * 60 * 60;
|
|
|
|
let startTime: number;
|
|
let groupBy: 'hour' | 'day';
|
|
|
|
switch (period) {
|
|
case 'today':
|
|
// Start of today (local timezone)
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
startTime = Math.floor(today.getTime() / 1000);
|
|
groupBy = 'hour';
|
|
break;
|
|
case '7days':
|
|
startTime = now - (7 * oneDaySeconds);
|
|
groupBy = 'day';
|
|
break;
|
|
case '30days':
|
|
startTime = now - (30 * oneDaySeconds);
|
|
groupBy = 'day';
|
|
break;
|
|
}
|
|
|
|
return queryUsage(credentials, {
|
|
sessionId,
|
|
startTime,
|
|
endTime: now,
|
|
groupBy
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Calculate total tokens and cost from usage data
|
|
*/
|
|
export function calculateTotals(usage: UsageDataPoint[]): {
|
|
totalTokens: number;
|
|
totalCost: number;
|
|
tokensByModel: Record<string, number>;
|
|
costByModel: Record<string, number>;
|
|
} {
|
|
const result = {
|
|
totalTokens: 0,
|
|
totalCost: 0,
|
|
tokensByModel: {} as Record<string, number>,
|
|
costByModel: {} as Record<string, number>
|
|
};
|
|
|
|
for (const dataPoint of usage) {
|
|
// Sum tokens
|
|
for (const [model, tokens] of Object.entries(dataPoint.tokens)) {
|
|
if (typeof tokens === 'number') {
|
|
result.totalTokens += tokens;
|
|
result.tokensByModel[model] = (result.tokensByModel[model] || 0) + tokens;
|
|
}
|
|
}
|
|
|
|
// Sum costs
|
|
for (const [model, cost] of Object.entries(dataPoint.cost)) {
|
|
if (typeof cost === 'number') {
|
|
result.totalCost += cost;
|
|
result.costByModel[model] = (result.costByModel[model] || 0) + cost;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} |