From ff5aa5f4f175a659e0a3c8c347a543da501791a0 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 21 May 2026 04:16:19 -0500 Subject: [PATCH] fix --- backend/app/api/provider_credentials.py | 1 + backend/app/services/provider_usage.py | 4 +- .../test_provider_credentials_usage_api.py | 14 +++- frontend/src/app/dashboard/page.tsx | 74 ++++++++++++++++++- .../dashboard/RuntimeUsageSection.tsx | 3 +- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/backend/app/api/provider_credentials.py b/backend/app/api/provider_credentials.py index 9129706..c113f39 100644 --- a/backend/app/api/provider_credentials.py +++ b/backend/app/api/provider_credentials.py @@ -142,6 +142,7 @@ async def test_provider_credential( account_key=account_key, api_key=payload.api_key, base_url=payload.base_url, + session_key=payload.session_key, force_refresh=True, ) diff --git a/backend/app/services/provider_usage.py b/backend/app/services/provider_usage.py index 71b055e..38fbba9 100644 --- a/backend/app/services/provider_usage.py +++ b/backend/app/services/provider_usage.py @@ -871,7 +871,7 @@ async def fetch_provider_usage( if sub_windows: result.subscription_windows = sub_windows result.reachable = True - elif not result.reachable: + else: result.error = result.error or "No subscription data returned." elif provider in ("openai", "codex"): @@ -895,6 +895,8 @@ async def fetch_provider_usage( result.subscription_windows = sub_windows result.subscription_plan = plan_label result.reachable = True + else: + result.error = result.error or "No subscription data returned." elif provider == "ollama": result = await _fetch_ollama(base_url, api_key) diff --git a/backend/tests/test_provider_credentials_usage_api.py b/backend/tests/test_provider_credentials_usage_api.py index 5434bbf..864d9ad 100644 --- a/backend/tests/test_provider_credentials_usage_api.py +++ b/backend/tests/test_provider_credentials_usage_api.py @@ -20,7 +20,7 @@ from app.models.organization_members import OrganizationMember from app.models.organizations import Organization from app.models.provider_credentials import ProviderCredential from app.services.organizations import OrganizationContext -from app.services.provider_usage import ProviderUsageLive +from app.services.provider_usage import ProviderUsageLive, SubscriptionWindow async def _make_engine() -> AsyncEngine: @@ -153,6 +153,7 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch ) async def _fake_fetch_provider_usage(**kwargs: object) -> ProviderUsageLive: + assert kwargs["session_key"] == "sk-ant-sid-test" result = ProviderUsageLive( provider=str(kwargs["provider"]), account_key=str(kwargs["account_key"]), @@ -167,6 +168,14 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch result.raw_headers = { "anthropic-ratelimit-tokens-limit": "100000", } + result.subscription_windows = [ + SubscriptionWindow( + key="five_hour", + label="Current session", + pct_used=77.0, + reset_at=utcnow(), + ), + ] return result monkeypatch.setattr( @@ -189,6 +198,7 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch "provider": "anthropic", "account_key": "Claude", "api_key": "sk-ant-test", + "session_key": "sk-ant-sid-test", }, ) @@ -204,6 +214,8 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch assert data["sample_input_tokens"] == 8 assert data["sample_output_tokens"] == 1 assert data["sample_latency_ms"] == 111 + assert data["subscription_windows"][0]["key"] == "five_hour" + assert data["subscription_windows"][0]["pct_used"] == 77.0 assert data["debug_rate_limit_headers"] == ["anthropic-ratelimit-tokens-limit"] finally: await engine.dispose() diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index fa2193c..9835e7c 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -39,6 +39,10 @@ import type { RuntimeUsageResponse, SystemHealthResponse, } from "@/api/generated/model"; +import { + getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet, + listProviderCredentialsApiV1ProviderCredentialsGet, +} from "@/api/generated/provider-credentials/provider-credentials"; import { getGatewayCronApiV1GatewaysGatewayIdCronGet, getGatewayHealthApiV1GatewaysGatewayIdHealthGet, @@ -753,7 +757,69 @@ export default function DashboardPage() { return windows; }, }); - const providerUsageWindows = providerUsageQuery.data ?? []; + const credentialUsageQuery = useQuery({ + queryKey: ["dashboard", "provider-credential-usage"], + enabled: Boolean(isSignedIn), + refetchInterval: 30_000, + refetchOnMount: "always", + queryFn: async () => { + const credentialsRes = await listProviderCredentialsApiV1ProviderCredentialsGet(); + if (credentialsRes.status !== 200) return []; + + const credentials = (credentialsRes.data ?? []).filter( + (cred) => cred.active && cred.has_session_key, + ); + const settled = await Promise.allSettled( + credentials.map((cred) => + getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet( + cred.id, + ).then((res) => ({ cred, res })), + ), + ); + + const windows: ProviderNativeUsageWindow[] = []; + for (const item of settled) { + if (item.status !== "fulfilled") continue; + const { cred, res } = item.value; + if (res.status !== 200) continue; + const usage = res.data; + const accountLabel = cred.display_name || cred.account_key || cred.provider; + const subscriptionWindows = usage.subscription_windows ?? []; + for (const window of subscriptionWindows) { + windows.push({ + key: window.key, + label: window.label, + pctUsed: window.pct_used, + remainingMs: window.reset_in_ms ?? null, + remainingLabel: null, + source: window.source ?? "provider_native", + confidence: window.confidence ?? "high", + provider: usage.provider, + gatewayLabel: accountLabel, + }); + } + if (subscriptionWindows.length === 0) { + windows.push({ + key: `${cred.id}:subscription_unavailable`, + label: "Subscription usage", + pctUsed: null, + remainingMs: null, + remainingLabel: null, + source: "provider_native", + confidence: "low", + provider: usage.provider, + gatewayLabel: accountLabel, + note: usage.error ?? "No subscription usage returned for this session key.", + }); + } + } + return windows; + }, + }); + const providerUsageWindows = [ + ...(credentialUsageQuery.data ?? []), + ...(providerUsageQuery.data ?? []), + ]; // Gateway health — query the first gateway only for the compact dashboard panel const primaryGatewayId = gatewayTargets[0]?.gatewayId ?? null; @@ -1200,7 +1266,11 @@ export default function DashboardPage() { usage={runtimeUsage} providerUsageWindows={providerUsageWindows} perGatewayUsage={perGatewayUsage} - isLoading={runtimeUsageQuery.isLoading || providerUsageQuery.isLoading} + isLoading={ + runtimeUsageQuery.isLoading + || providerUsageQuery.isLoading + || credentialUsageQuery.isLoading + } hasGateways={hasConfiguredGateways} /> diff --git a/frontend/src/components/dashboard/RuntimeUsageSection.tsx b/frontend/src/components/dashboard/RuntimeUsageSection.tsx index 88b4265..283c46c 100644 --- a/frontend/src/components/dashboard/RuntimeUsageSection.tsx +++ b/frontend/src/components/dashboard/RuntimeUsageSection.tsx @@ -140,6 +140,7 @@ export interface ProviderNativeUsageWindow { confidence: string; provider: string; gatewayLabel: string; + note?: string; } // --------------------------------------------------------------------------- @@ -456,7 +457,7 @@ export function RuntimeUsageSection({ />

- Resets in {resetText} · {window.provider} · {window.gatewayLabel} + {window.note ?? `Resets in ${resetText}`} · {window.provider} · {window.gatewayLabel}

);