Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| import { create } from 'zustand'; | |
| import { apiFetch } from '@/utils/api'; | |
| import { useSessionStore } from '@/store/sessionStore'; | |
| export interface UsageBucket { | |
| session_id?: string | null; | |
| total_usd: number; | |
| inference_usd: number; | |
| hf_jobs_estimated_usd: number; | |
| sandbox_estimated_usd: number; | |
| llm_calls: number; | |
| hf_jobs_count: number; | |
| sandbox_count: number; | |
| prompt_tokens: number; | |
| completion_tokens: number; | |
| cache_read_tokens: number; | |
| cache_creation_tokens: number; | |
| total_tokens: number; | |
| hf_jobs_billable_seconds_estimate: number; | |
| sandbox_billable_seconds_estimate: number; | |
| } | |
| export interface HfAccountUsageBucket { | |
| window_start?: string | null; | |
| window_end?: string | null; | |
| timezone?: string | null; | |
| total_usd: number; | |
| inference_providers_usd: number; | |
| hf_jobs_usd: number; | |
| inference_provider_requests: number; | |
| hf_jobs_minutes: number; | |
| } | |
| export interface HfInferenceProvidersCredits { | |
| included_usd: number; | |
| used_usd: number; | |
| remaining_included_usd: number; | |
| limit_usd: number; | |
| remaining_limit_usd: number; | |
| num_requests: number; | |
| period_start?: string | null; | |
| period_end?: string | null; | |
| } | |
| export interface HfAccountUsage { | |
| source: 'hf_billing'; | |
| available: boolean; | |
| error?: string | null; | |
| current_session: HfAccountUsageBucket | null; | |
| month: HfAccountUsageBucket | null; | |
| inference_providers_credits: HfInferenceProvidersCredits | null; | |
| } | |
| export interface SessionAutoApprovalUsage { | |
| enabled: boolean; | |
| cost_cap_usd?: number | null; | |
| estimated_spend_usd?: number; | |
| remaining_usd?: number | null; | |
| } | |
| export interface UsageResponse { | |
| source: 'app_telemetry'; | |
| currency: 'USD'; | |
| generated_at: string; | |
| timezone: string; | |
| session: UsageBucket | null; | |
| hf_account?: HfAccountUsage | null; | |
| auto_approval?: SessionAutoApprovalUsage | null; | |
| links: Record<string, string>; | |
| } | |
| type UsageEventType = 'llm_call' | 'hf_job_complete' | 'sandbox_destroy'; | |
| interface UsageStore { | |
| usage: UsageResponse | null; | |
| isLoading: boolean; | |
| error: string | null; | |
| fetchUsage: (sessionId?: string | null) => Promise<void>; | |
| applyUsageEvent: ( | |
| sessionId: string, | |
| eventType: UsageEventType, | |
| data: Record<string, unknown>, | |
| ) => void; | |
| } | |
| function numberValue(value: unknown): number { | |
| return typeof value === 'number' && Number.isFinite(value) ? value : 0; | |
| } | |
| function intValue(value: unknown): number { | |
| return Math.trunc(numberValue(value)); | |
| } | |
| function roundUsd(value: number): number { | |
| return Math.round(value * 1_000_000) / 1_000_000; | |
| } | |
| function usageUrl(sessionId?: string | null): string { | |
| const params = new URLSearchParams(); | |
| const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; | |
| params.set('tz', timezone); | |
| if (sessionId) params.set('session_id', sessionId); | |
| return `/api/usage?${params.toString()}`; | |
| } | |
| function applyEventToBucket( | |
| bucket: UsageBucket | null, | |
| eventType: UsageEventType, | |
| data: Record<string, unknown>, | |
| ): UsageBucket | null { | |
| if (!bucket) return null; | |
| const next = { ...bucket }; | |
| if (eventType === 'llm_call') { | |
| const prompt = intValue(data.prompt_tokens); | |
| const completion = intValue(data.completion_tokens); | |
| const cacheRead = intValue(data.cache_read_tokens); | |
| const cacheCreation = intValue(data.cache_creation_tokens); | |
| const total = | |
| intValue(data.total_tokens) || prompt + completion + cacheRead + cacheCreation; | |
| next.llm_calls += 1; | |
| next.inference_usd = roundUsd(next.inference_usd + numberValue(data.cost_usd)); | |
| next.prompt_tokens += prompt; | |
| next.completion_tokens += completion; | |
| next.cache_read_tokens += cacheRead; | |
| next.cache_creation_tokens += cacheCreation; | |
| next.total_tokens += total; | |
| } else if (eventType === 'hf_job_complete') { | |
| next.hf_jobs_count += 1; | |
| next.hf_jobs_estimated_usd = roundUsd( | |
| next.hf_jobs_estimated_usd + numberValue(data.estimated_cost_usd), | |
| ); | |
| next.hf_jobs_billable_seconds_estimate += | |
| intValue(data.billable_seconds_estimate) || intValue(data.wall_time_s); | |
| } | |
| next.total_usd = roundUsd( | |
| next.inference_usd + | |
| next.hf_jobs_estimated_usd + | |
| (next.sandbox_estimated_usd ?? 0), | |
| ); | |
| return next; | |
| } | |
| export const useUsageStore = create<UsageStore>()((set, get) => ({ | |
| usage: null, | |
| isLoading: false, | |
| error: null, | |
| fetchUsage: async (sessionId?: string | null) => { | |
| const current = get().usage; | |
| set({ | |
| usage: | |
| sessionId && current?.session?.session_id !== sessionId ? null : current, | |
| isLoading: true, | |
| error: null, | |
| }); | |
| try { | |
| let response = await apiFetch(usageUrl(sessionId)); | |
| if (response.status === 404 && sessionId) { | |
| response = await apiFetch(usageUrl()); | |
| } | |
| if (!response.ok) { | |
| throw new Error(response.statusText || 'Failed to load usage'); | |
| } | |
| const usage = (await response.json()) as UsageResponse; | |
| set({ usage, isLoading: false, error: null }); | |
| if (sessionId && usage.auto_approval) { | |
| useSessionStore.getState().updateSessionYolo(sessionId, usage.auto_approval); | |
| } | |
| } catch (error) { | |
| set({ | |
| isLoading: false, | |
| error: error instanceof Error ? error.message : 'Failed to load usage', | |
| }); | |
| } | |
| }, | |
| applyUsageEvent: (sessionId, eventType, data) => { | |
| if (eventType === 'sandbox_destroy') { | |
| void get().fetchUsage(sessionId); | |
| return; | |
| } | |
| const current = get().usage; | |
| if (!current) return; | |
| set({ | |
| usage: { | |
| ...current, | |
| session: | |
| current.session?.session_id === sessionId | |
| ? applyEventToBucket(current.session, eventType, data) | |
| : current.session, | |
| }, | |
| }); | |
| }, | |
| })); | |