mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
* feat: live provider quota panel (Claude + Codex) Adds Claude and Codex plan usage to the context window percentage circle tooltip, querying their respective APIs directly using existing CLI auth tokens. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review feedback - restore quota trigger, guard NaN date - Re-add triggerFetch() on agent idle/error in agent status subscription - Guard formatResetsAtLabel against NaN from malformed API date strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve composer conflict and stub subscribe in websocket tests * feat(quota): add cursor, copilot, zai, grok, and kimi quota UI and improve type safety * feat(quota): make Claude and Codex pluggable, and map additional quota/credits metrics * security(quota): fix shell injection vulnerability by migrating exec to execFile * revert: restore original scripts/dev.ps1 and packages/desktop/scripts/dev.ps1 * Fix i18n resource test expectation * Fix quota tooltip empty-provider state * Preserve zero values in Grok quota * Fix empty quota fetch state * Ignore quota frames in relay reconnect tests * Persist refreshed Codex tokens to source auth file * fix(quota): read Claude OAuth credentials from the macOS login Keychain On macOS, Claude Code stores its OAuth credential in the login Keychain (generic-password item, service "Claude Code-credentials"), not in ~/.claude/.credentials.json — that file usually does not exist there, so the Claude provider's existsSync check failed and macOS users silently got no Claude quota. Read it via the macOS `security` CLI (its ACL only trusts /usr/bin/security to decrypt; a native Keychain read would prompt), and stay read-only there since Claude Code owns the refresh/persist. Linux and Windows keep using ~/.claude/.credentials.json unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix quota keychain timeout and local host reconciliation * Fix terminal notification test quota stub * Ignore quota frames in terminal notification tests * fix(quota): bypass Claude token refresh on macOS Keychain-backed logins * fix(dev): update local dev daemon port to 6768 on Windows scripts/dev.ps1 * fix(dev): bypass Unix dev-daemon.sh on Windows in dev.ps1 * fix(client): handle and dispatch provider_quota event in DaemonClient * fix(desktop): fix PowerShell quote stripping in desktop dev.ps1 config seeding * fix(desktop): execute config update via temp file to avoid PowerShell quoting bugs * fix(desktop): fix concurrently command invocation on Windows in dev.ps1 * fix(desktop): resolve path escaping and environment setting syntax errors in dev.ps1 for Windows * Add on-demand provider usage views * Fix Cursor usage billing dates * Move provider usage renderer coverage to e2e * Use provider manifest for quota fetchers * Add provider usage fetch timeouts * Reshape provider usage fetchers --------- Co-authored-by: ABorakati <ABorakati@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> Co-authored-by: lumingjun <lumingjun@bytedance.com>
153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
import { expect, test } from "./fixtures";
|
|
import { gotoAppShell, openSettings } from "./helpers/app";
|
|
import { installProviderUsageFixture } from "./helpers/provider-usage";
|
|
import { getServerId } from "./helpers/server-id";
|
|
import { openSettingsHostSection } from "./helpers/settings";
|
|
|
|
test.describe("provider usage settings", () => {
|
|
test("renders every provider returned by the daemon usage RPC", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const serverId = getServerId();
|
|
const usageFixture = await installProviderUsageFixture(page, [
|
|
{
|
|
fetchedAt: "2026-06-19T00:00:00.000Z",
|
|
providers: [
|
|
{
|
|
providerId: "claude",
|
|
displayName: "Claude",
|
|
status: "available",
|
|
planLabel: "Max 20x",
|
|
windows: [{ id: "session", label: "Session", usedPct: 7 }],
|
|
},
|
|
{
|
|
providerId: "codex",
|
|
displayName: "Codex",
|
|
status: "available",
|
|
planLabel: "Pro 20x",
|
|
windows: [{ id: "weekly", label: "Weekly", usedPct: 29 }],
|
|
},
|
|
{
|
|
providerId: "glm",
|
|
displayName: "GLM coding plan",
|
|
status: "available",
|
|
planLabel: "GLM coding plan",
|
|
sourceLabel: "OpenUsage 0.6.27",
|
|
windows: [
|
|
{ id: "biweekly", label: "Biweekly", usedPct: 23 },
|
|
{ id: "daily", label: "Daily", remainingPct: 30 },
|
|
],
|
|
balances: [
|
|
{ id: "credits", label: "Credits", remaining: 1234, unit: "credits" },
|
|
{ id: "extra", label: "Extra usage", used: 5, limit: 20, unit: "usd" },
|
|
],
|
|
details: [{ id: "valid", label: "Valid until", value: "2026-12-31" }],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
expect(usageFixture.requestCount()).toBe(0);
|
|
await openSettingsHostSection(page, serverId, "usage");
|
|
await usageFixture.waitForRequestCount(1);
|
|
|
|
const card = page.getByTestId("provider-usage-card");
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
await expect(card.getByText("Claude", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Codex", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("GLM coding plan", { exact: true }).first()).toBeVisible();
|
|
await expect(card.getByText("Biweekly", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Daily", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("70%")).toBeVisible();
|
|
await expect(card.getByText("Credits", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("1,234 left", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Extra usage", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("$5.00 / $20.00", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Valid until", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("2026-12-31", { exact: true })).toBeVisible();
|
|
await expect(card.getByText(/OpenUsage 0\.6\.27/)).toBeVisible();
|
|
});
|
|
|
|
test("refresh invalidates and refetches usage", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const serverId = getServerId();
|
|
const usageFixture = await installProviderUsageFixture(page, [
|
|
{
|
|
fetchedAt: "2026-06-19T00:00:00.000Z",
|
|
providers: [
|
|
{
|
|
providerId: "glm",
|
|
displayName: "GLM coding plan",
|
|
status: "available",
|
|
planLabel: "GLM coding plan",
|
|
windows: [{ id: "biweekly", label: "Biweekly", usedPct: 23 }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
fetchedAt: "2026-06-19T00:01:00.000Z",
|
|
providers: [
|
|
{
|
|
providerId: "glm",
|
|
displayName: "GLM coding plan",
|
|
status: "available",
|
|
planLabel: "GLM coding plan",
|
|
windows: [{ id: "biweekly", label: "Biweekly", usedPct: 64 }],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
await openSettingsHostSection(page, serverId, "usage");
|
|
await usageFixture.waitForRequestCount(1);
|
|
await expect(page.getByText("23%")).toBeVisible({ timeout: 10_000 });
|
|
|
|
await page.getByRole("button", { name: "Refresh", exact: true }).click();
|
|
await usageFixture.waitForRequestCount(2);
|
|
|
|
expect(usageFixture.requestCount()).toBe(2);
|
|
await expect(page.getByText("64%")).toBeVisible();
|
|
});
|
|
|
|
test("one provider error does not collapse the usage list", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const serverId = getServerId();
|
|
await installProviderUsageFixture(page, [
|
|
{
|
|
fetchedAt: "2026-06-19T00:00:00.000Z",
|
|
providers: [
|
|
{
|
|
providerId: "claude",
|
|
displayName: "Claude",
|
|
status: "error",
|
|
planLabel: null,
|
|
windows: [],
|
|
error: "Claude auth expired",
|
|
},
|
|
{
|
|
providerId: "codex",
|
|
displayName: "Codex",
|
|
status: "available",
|
|
planLabel: "Pro 20x",
|
|
windows: [{ id: "weekly", label: "Weekly", usedPct: 71 }],
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
await openSettingsHostSection(page, serverId, "usage");
|
|
|
|
const card = page.getByTestId("provider-usage-card");
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
await expect(card.getByText("Error", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Claude auth expired", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Codex", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("71%")).toBeVisible();
|
|
});
|
|
});
|