fix
This commit is contained in:
parent
a6c24673bc
commit
ff5aa5f4f1
|
|
@ -142,6 +142,7 @@ async def test_provider_credential(
|
||||||
account_key=account_key,
|
account_key=account_key,
|
||||||
api_key=payload.api_key,
|
api_key=payload.api_key,
|
||||||
base_url=payload.base_url,
|
base_url=payload.base_url,
|
||||||
|
session_key=payload.session_key,
|
||||||
force_refresh=True,
|
force_refresh=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -871,7 +871,7 @@ async def fetch_provider_usage(
|
||||||
if sub_windows:
|
if sub_windows:
|
||||||
result.subscription_windows = sub_windows
|
result.subscription_windows = sub_windows
|
||||||
result.reachable = True
|
result.reachable = True
|
||||||
elif not result.reachable:
|
else:
|
||||||
result.error = result.error or "No subscription data returned."
|
result.error = result.error or "No subscription data returned."
|
||||||
|
|
||||||
elif provider in ("openai", "codex"):
|
elif provider in ("openai", "codex"):
|
||||||
|
|
@ -895,6 +895,8 @@ async def fetch_provider_usage(
|
||||||
result.subscription_windows = sub_windows
|
result.subscription_windows = sub_windows
|
||||||
result.subscription_plan = plan_label
|
result.subscription_plan = plan_label
|
||||||
result.reachable = True
|
result.reachable = True
|
||||||
|
else:
|
||||||
|
result.error = result.error or "No subscription data returned."
|
||||||
|
|
||||||
elif provider == "ollama":
|
elif provider == "ollama":
|
||||||
result = await _fetch_ollama(base_url, api_key)
|
result = await _fetch_ollama(base_url, api_key)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ from app.models.organization_members import OrganizationMember
|
||||||
from app.models.organizations import Organization
|
from app.models.organizations import Organization
|
||||||
from app.models.provider_credentials import ProviderCredential
|
from app.models.provider_credentials import ProviderCredential
|
||||||
from app.services.organizations import OrganizationContext
|
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:
|
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:
|
async def _fake_fetch_provider_usage(**kwargs: object) -> ProviderUsageLive:
|
||||||
|
assert kwargs["session_key"] == "sk-ant-sid-test"
|
||||||
result = ProviderUsageLive(
|
result = ProviderUsageLive(
|
||||||
provider=str(kwargs["provider"]),
|
provider=str(kwargs["provider"]),
|
||||||
account_key=str(kwargs["account_key"]),
|
account_key=str(kwargs["account_key"]),
|
||||||
|
|
@ -167,6 +168,14 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch
|
||||||
result.raw_headers = {
|
result.raw_headers = {
|
||||||
"anthropic-ratelimit-tokens-limit": "100000",
|
"anthropic-ratelimit-tokens-limit": "100000",
|
||||||
}
|
}
|
||||||
|
result.subscription_windows = [
|
||||||
|
SubscriptionWindow(
|
||||||
|
key="five_hour",
|
||||||
|
label="Current session",
|
||||||
|
pct_used=77.0,
|
||||||
|
reset_at=utcnow(),
|
||||||
|
),
|
||||||
|
]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
|
|
@ -189,6 +198,7 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch
|
||||||
"provider": "anthropic",
|
"provider": "anthropic",
|
||||||
"account_key": "Claude",
|
"account_key": "Claude",
|
||||||
"api_key": "sk-ant-test",
|
"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_input_tokens"] == 8
|
||||||
assert data["sample_output_tokens"] == 1
|
assert data["sample_output_tokens"] == 1
|
||||||
assert data["sample_latency_ms"] == 111
|
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"]
|
assert data["debug_rate_limit_headers"] == ["anthropic-ratelimit-tokens-limit"]
|
||||||
finally:
|
finally:
|
||||||
await engine.dispose()
|
await engine.dispose()
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ import type {
|
||||||
RuntimeUsageResponse,
|
RuntimeUsageResponse,
|
||||||
SystemHealthResponse,
|
SystemHealthResponse,
|
||||||
} from "@/api/generated/model";
|
} from "@/api/generated/model";
|
||||||
|
import {
|
||||||
|
getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet,
|
||||||
|
listProviderCredentialsApiV1ProviderCredentialsGet,
|
||||||
|
} from "@/api/generated/provider-credentials/provider-credentials";
|
||||||
import {
|
import {
|
||||||
getGatewayCronApiV1GatewaysGatewayIdCronGet,
|
getGatewayCronApiV1GatewaysGatewayIdCronGet,
|
||||||
getGatewayHealthApiV1GatewaysGatewayIdHealthGet,
|
getGatewayHealthApiV1GatewaysGatewayIdHealthGet,
|
||||||
|
|
@ -753,7 +757,69 @@ export default function DashboardPage() {
|
||||||
return windows;
|
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
|
// Gateway health — query the first gateway only for the compact dashboard panel
|
||||||
const primaryGatewayId = gatewayTargets[0]?.gatewayId ?? null;
|
const primaryGatewayId = gatewayTargets[0]?.gatewayId ?? null;
|
||||||
|
|
@ -1200,7 +1266,11 @@ export default function DashboardPage() {
|
||||||
usage={runtimeUsage}
|
usage={runtimeUsage}
|
||||||
providerUsageWindows={providerUsageWindows}
|
providerUsageWindows={providerUsageWindows}
|
||||||
perGatewayUsage={perGatewayUsage}
|
perGatewayUsage={perGatewayUsage}
|
||||||
isLoading={runtimeUsageQuery.isLoading || providerUsageQuery.isLoading}
|
isLoading={
|
||||||
|
runtimeUsageQuery.isLoading
|
||||||
|
|| providerUsageQuery.isLoading
|
||||||
|
|| credentialUsageQuery.isLoading
|
||||||
|
}
|
||||||
hasGateways={hasConfiguredGateways}
|
hasGateways={hasConfiguredGateways}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,7 @@ export interface ProviderNativeUsageWindow {
|
||||||
confidence: string;
|
confidence: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
gatewayLabel: string;
|
gatewayLabel: string;
|
||||||
|
note?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -456,7 +457,7 @@ export function RuntimeUsageSection({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-[11px] text-muted">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue