This commit is contained in:
null 2026-05-21 04:16:19 -05:00
parent a6c24673bc
commit ff5aa5f4f1
5 changed files with 91 additions and 5 deletions

View File

@ -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,
)

View File

@ -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)

View File

@ -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()

View File

@ -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<ProviderNativeUsageWindow[], ApiError>({
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}
/>
</div>

View File

@ -140,6 +140,7 @@ export interface ProviderNativeUsageWindow {
confidence: string;
provider: string;
gatewayLabel: string;
note?: string;
}
// ---------------------------------------------------------------------------
@ -456,7 +457,7 @@ export function RuntimeUsageSection({
/>
</div>
<p className="mt-1 text-[11px] text-muted">
Resets in {resetText} · {window.provider} · {window.gatewayLabel}
{window.note ?? `Resets in ${resetText}`} · {window.provider} · {window.gatewayLabel}
</p>
</div>
);