@hanzo/browser-extension 1.9.31 → 1.9.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/browser-extension/background-firefox.js +1427 -0
  2. package/dist/browser-extension/background.js +1443 -18
  3. package/dist/browser-extension/chrome/background.js +1443 -18
  4. package/dist/browser-extension/chrome/manifest.json +17 -2
  5. package/dist/browser-extension/chrome/newtab.css +277 -0
  6. package/dist/browser-extension/chrome/newtab.html +13 -0
  7. package/dist/browser-extension/chrome/newtab.js +64 -0
  8. package/dist/browser-extension/chrome/popup.html +28 -0
  9. package/dist/browser-extension/chrome/popup.js +67 -1
  10. package/dist/browser-extension/firefox/background.js +1427 -0
  11. package/dist/browser-extension/firefox/manifest.json +10 -2
  12. package/dist/browser-extension/firefox/newtab.css +277 -0
  13. package/dist/browser-extension/firefox/newtab.html +13 -0
  14. package/dist/browser-extension/firefox/newtab.js +64 -0
  15. package/dist/browser-extension/firefox/popup.html +28 -0
  16. package/dist/browser-extension/firefox/popup.js +67 -1
  17. package/dist/browser-extension/manifest.json +17 -2
  18. package/dist/browser-extension/newtab.js +64 -0
  19. package/dist/browser-extension/popup.js +67 -1
  20. package/dist/browser-extension/safari/Info.plist +2 -2
  21. package/dist/browser-extension/safari/background.js +1443 -18
  22. package/dist/browser-extension/safari/newtab.css +277 -0
  23. package/dist/browser-extension/safari/newtab.html +13 -0
  24. package/dist/browser-extension/safari/newtab.js +64 -0
  25. package/dist/browser-extension/safari/popup.html +28 -0
  26. package/dist/browser-extension/safari/popup.js +67 -1
  27. package/package.json +3 -1
  28. package/src/answer/AnswerEngine.tsx +553 -0
  29. package/src/manifest-firefox.json +10 -2
  30. package/src/manifest.json +17 -2
  31. package/src/newtab.css +277 -0
  32. package/src/newtab.html +13 -0
  33. package/src/popup.html +28 -0
@@ -1317,6 +1317,1403 @@
1317
1317
  });
1318
1318
  }
1319
1319
 
1320
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/host.js
1321
+ var expandHome = (host, path) => path.startsWith("~/") ? `${host.homeDir()}/${path.slice(2)}` : path;
1322
+ var readJsonFile = async (host, path) => {
1323
+ const text = await host.readTextFile(expandHome(host, path));
1324
+ if (text === void 0)
1325
+ return void 0;
1326
+ try {
1327
+ return JSON.parse(text);
1328
+ } catch {
1329
+ return void 0;
1330
+ }
1331
+ };
1332
+
1333
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/provider.js
1334
+ var matchesMode = (kind, mode) => {
1335
+ if (mode === "auto")
1336
+ return true;
1337
+ if (mode === "web")
1338
+ return kind === "web";
1339
+ if (mode === "cli")
1340
+ return kind === "cli" || kind === "localProbe";
1341
+ if (mode === "oauth")
1342
+ return kind === "oauth";
1343
+ return kind === "apiToken";
1344
+ };
1345
+ var forMode = (all, mode) => all.filter((s) => matchesMode(s.kind, mode));
1346
+ var runPipeline = async (descriptor, ctx) => {
1347
+ const attempts = [];
1348
+ let lastError;
1349
+ for (const strategy of descriptor.strategies(ctx.sourceMode)) {
1350
+ if (!await strategy.isAvailable(ctx)) {
1351
+ attempts.push({ strategyId: strategy.id, ok: false, skipped: true });
1352
+ continue;
1353
+ }
1354
+ try {
1355
+ const result = await strategy.fetch(ctx);
1356
+ attempts.push({ strategyId: strategy.id, ok: true });
1357
+ return { result, attempts };
1358
+ } catch (error) {
1359
+ attempts.push({
1360
+ strategyId: strategy.id,
1361
+ ok: false,
1362
+ error: error instanceof Error ? error.message : String(error)
1363
+ });
1364
+ lastError = error;
1365
+ const fallback = strategy.shouldFallback?.(error, ctx) ?? true;
1366
+ if (!fallback)
1367
+ break;
1368
+ }
1369
+ }
1370
+ return { error: lastError ?? new Error(`${descriptor.id}: no strategy available`), attempts };
1371
+ };
1372
+
1373
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/codex.js
1374
+ var codexHome = (ctx) => ctx.host.env("CODEX_HOME") ?? expandHome(ctx.host, "~/.codex");
1375
+ var toWindow = (w) => {
1376
+ if (!w || typeof w.used_percent !== "number")
1377
+ return void 0;
1378
+ return {
1379
+ usedPercent: w.used_percent,
1380
+ windowMinutes: w.limit_window_seconds ? Math.round(w.limit_window_seconds / 60) : void 0,
1381
+ resetsAt: w.reset_at ? new Date(w.reset_at * 1e3).toISOString() : void 0
1382
+ };
1383
+ };
1384
+ var oauthStrategy = {
1385
+ id: "codex.oauth",
1386
+ kind: "oauth",
1387
+ async isAvailable(ctx) {
1388
+ const auth = await readJsonFile(ctx.host, `${codexHome(ctx)}/auth.json`);
1389
+ return Boolean(auth?.tokens?.access_token);
1390
+ },
1391
+ async fetch(ctx) {
1392
+ const auth = await readJsonFile(ctx.host, `${codexHome(ctx)}/auth.json`);
1393
+ const token = auth?.tokens?.access_token;
1394
+ if (!token)
1395
+ throw new Error("codex: no access token in auth.json");
1396
+ const headers = {
1397
+ Authorization: `Bearer ${token}`,
1398
+ Accept: "application/json",
1399
+ "User-Agent": "HanzoUsage"
1400
+ };
1401
+ if (auth?.tokens?.account_id)
1402
+ headers["ChatGPT-Account-Id"] = auth.tokens.account_id;
1403
+ const res = await ctx.host.http({
1404
+ url: "https://chatgpt.com/backend-api/wham/usage",
1405
+ headers,
1406
+ timeoutMs: 3e4
1407
+ });
1408
+ if (res.status !== 200)
1409
+ throw new Error(`codex usage HTTP ${res.status}`);
1410
+ const body = JSON.parse(res.text);
1411
+ const now = ctx.host.now().toISOString();
1412
+ const usage = {
1413
+ providerId: "codex",
1414
+ primary: toWindow(body.rate_limit?.primary_window),
1415
+ secondary: toWindow(body.rate_limit?.secondary_window),
1416
+ extraRateWindows: (body.additional_rate_limits ?? []).map((extra) => {
1417
+ const window2 = toWindow(extra.rate_limit?.primary_window);
1418
+ if (!window2 || !extra.limit_name)
1419
+ return void 0;
1420
+ return { id: extra.limit_name, title: extra.limit_name, window: window2, usageKnown: true };
1421
+ }).filter((w) => Boolean(w)),
1422
+ identity: { providerId: "codex", plan: body.plan_type, loginMethod: "oauth" },
1423
+ dataConfidence: "percentOnly",
1424
+ updatedAt: now
1425
+ };
1426
+ let credits;
1427
+ if (body.credits?.has_credits) {
1428
+ credits = {
1429
+ remaining: body.credits.balance ?? 0,
1430
+ unlimited: body.credits.unlimited,
1431
+ updatedAt: now
1432
+ };
1433
+ }
1434
+ return {
1435
+ usage,
1436
+ credits,
1437
+ sourceLabel: "OpenAI OAuth",
1438
+ strategyId: this.id,
1439
+ strategyKind: this.kind
1440
+ };
1441
+ }
1442
+ };
1443
+ var codexProvider = {
1444
+ id: "codex",
1445
+ metadata: {
1446
+ displayName: "Codex",
1447
+ sessionLabel: "5h limit",
1448
+ weeklyLabel: "Weekly limit",
1449
+ supportsCredits: true,
1450
+ defaultEnabled: true,
1451
+ dashboardUrl: "https://chatgpt.com/codex/settings/usage",
1452
+ statusPageUrl: "https://status.openai.com",
1453
+ color: "#10a37f"
1454
+ },
1455
+ strategies: (mode) => forMode([oauthStrategy], mode)
1456
+ };
1457
+
1458
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/claude.js
1459
+ var WINDOW_MINUTES = {
1460
+ five_hour: 300,
1461
+ seven_day: 10080,
1462
+ seven_day_sonnet: 10080,
1463
+ seven_day_opus: 10080
1464
+ };
1465
+ var toWindow2 = (key, w) => {
1466
+ const used = w?.utilization ?? w?.used_percent;
1467
+ if (typeof used !== "number")
1468
+ return void 0;
1469
+ return { usedPercent: used, windowMinutes: WINDOW_MINUTES[key], resetsAt: w?.resets_at };
1470
+ };
1471
+ var mapUsage = (body, now, loginMethod, plan) => {
1472
+ const extras = [];
1473
+ for (const [key, title] of [
1474
+ ["seven_day_sonnet", "Sonnet weekly"],
1475
+ ["seven_day_opus", "Opus weekly"]
1476
+ ]) {
1477
+ const window2 = toWindow2(key, body[key]);
1478
+ if (window2)
1479
+ extras.push({ id: key, title, window: window2, usageKnown: true });
1480
+ }
1481
+ return {
1482
+ providerId: "claude",
1483
+ primary: toWindow2("five_hour", body.five_hour),
1484
+ secondary: toWindow2("seven_day", body.seven_day),
1485
+ extraRateWindows: extras.length ? extras : void 0,
1486
+ identity: {
1487
+ providerId: "claude",
1488
+ plan: plan ?? body.subscriptionType ?? body.rate_limit_tier,
1489
+ loginMethod
1490
+ },
1491
+ providerCost: body.extra_usage && typeof body.extra_usage.used_cents === "number" ? {
1492
+ used: body.extra_usage.used_cents / 100,
1493
+ limit: typeof body.extra_usage.limit_cents === "number" ? body.extra_usage.limit_cents / 100 : void 0,
1494
+ currencyCode: "USD",
1495
+ period: "monthly",
1496
+ updatedAt: now
1497
+ } : void 0,
1498
+ dataConfidence: "percentOnly",
1499
+ updatedAt: now
1500
+ };
1501
+ };
1502
+ var credentialPaths = ["~/.claude/.credentials.json", "~/.config/claude/.credentials.json"];
1503
+ var readCredentials = async (ctx) => {
1504
+ for (const path of credentialPaths) {
1505
+ const file = await readJsonFile(ctx.host, path);
1506
+ if (file?.claudeAiOauth?.accessToken)
1507
+ return file.claudeAiOauth;
1508
+ }
1509
+ return void 0;
1510
+ };
1511
+ var oauthStrategy2 = {
1512
+ id: "claude.oauth",
1513
+ kind: "oauth",
1514
+ async isAvailable(ctx) {
1515
+ return Boolean(await readCredentials(ctx));
1516
+ },
1517
+ async fetch(ctx) {
1518
+ const creds = await readCredentials(ctx);
1519
+ if (!creds?.accessToken)
1520
+ throw new Error("claude: no OAuth credentials");
1521
+ const res = await ctx.host.http({
1522
+ url: "https://api.anthropic.com/api/oauth/usage",
1523
+ headers: {
1524
+ Authorization: `Bearer ${creds.accessToken}`,
1525
+ "anthropic-beta": "oauth-2025-04-20",
1526
+ Accept: "application/json"
1527
+ },
1528
+ timeoutMs: 3e4
1529
+ });
1530
+ if (res.status !== 200)
1531
+ throw new Error(`claude usage HTTP ${res.status}`);
1532
+ const body = JSON.parse(res.text);
1533
+ return {
1534
+ usage: mapUsage(body, ctx.host.now().toISOString(), "oauth", creds.subscriptionType),
1535
+ sourceLabel: "Claude OAuth",
1536
+ strategyId: this.id,
1537
+ strategyKind: this.kind
1538
+ };
1539
+ }
1540
+ };
1541
+ var webStrategy = {
1542
+ id: "claude.web",
1543
+ kind: "web",
1544
+ async isAvailable(ctx) {
1545
+ return typeof ctx.settings?.cookieHeader === "string";
1546
+ },
1547
+ async fetch(ctx) {
1548
+ const cookie = String(ctx.settings?.cookieHeader);
1549
+ const get = async (path) => {
1550
+ const res = await ctx.host.http({
1551
+ url: `https://claude.ai/api${path}`,
1552
+ headers: { Cookie: cookie, Accept: "application/json" },
1553
+ timeoutMs: 3e4
1554
+ });
1555
+ if (res.status !== 200)
1556
+ throw new Error(`claude.ai${path} HTTP ${res.status}`);
1557
+ return JSON.parse(res.text);
1558
+ };
1559
+ const orgs = await get("/organizations");
1560
+ const orgId = orgs[0]?.uuid;
1561
+ if (!orgId)
1562
+ throw new Error("claude: no organization for session");
1563
+ const body = await get(`/organizations/${orgId}/usage`);
1564
+ return {
1565
+ usage: mapUsage(body, ctx.host.now().toISOString(), "web"),
1566
+ sourceLabel: "claude.ai session",
1567
+ strategyId: this.id,
1568
+ strategyKind: this.kind
1569
+ };
1570
+ }
1571
+ };
1572
+ var claudeProvider = {
1573
+ id: "claude",
1574
+ metadata: {
1575
+ displayName: "Claude",
1576
+ sessionLabel: "Session (5h)",
1577
+ weeklyLabel: "Weekly limit",
1578
+ defaultEnabled: true,
1579
+ dashboardUrl: "https://claude.ai/settings/usage",
1580
+ statusPageUrl: "https://status.anthropic.com",
1581
+ color: "#d97757"
1582
+ },
1583
+ strategies: (mode) => forMode([oauthStrategy2, webStrategy], mode)
1584
+ };
1585
+
1586
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/hanzo.js
1587
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
1588
+ var cloudStrategy = {
1589
+ id: "hanzo.cloud",
1590
+ kind: "apiToken",
1591
+ async isAvailable(ctx) {
1592
+ return Boolean(ctx.settings?.getToken || ctx.settings?.apiKey);
1593
+ },
1594
+ async fetch(ctx) {
1595
+ const base = ctx.settings?.baseUrl ?? "https://api.hanzo.ai";
1596
+ const token = await ctx.settings?.getToken?.() ?? ctx.settings?.apiKey;
1597
+ if (!token)
1598
+ throw new Error("hanzo: no token for cloud usage");
1599
+ const res = await ctx.host.http({
1600
+ url: `${base}/v1/billing/usage`,
1601
+ headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
1602
+ timeoutMs: 3e4
1603
+ });
1604
+ if (res.status !== 200)
1605
+ throw new Error(`hanzo billing usage HTTP ${res.status}`);
1606
+ const body = JSON.parse(res.text);
1607
+ const entries = Array.isArray(body.usage) ? body.usage : Array.isArray(body.data) ? body.data : [];
1608
+ let tokens = num(body.total_tokens ?? body.totalTokens);
1609
+ let spendCents = num(body.spend_cents ?? body.spendCents);
1610
+ let requests = num(body.total_requests ?? body.requests);
1611
+ for (const e of entries) {
1612
+ tokens += num(e.total_tokens ?? e.totalTokens ?? e.tokens);
1613
+ spendCents += num(e.spend_cents ?? e.spendCents ?? e.cost_cents);
1614
+ requests += num(e.requests ?? e.request_count ?? 1) - (e.requests === void 0 && e.request_count === void 0 ? 1 : 0);
1615
+ }
1616
+ const now = ctx.host.now().toISOString();
1617
+ const usage = {
1618
+ providerId: "hanzo",
1619
+ identity: { providerId: "hanzo", loginMethod: "iam" },
1620
+ providerCost: {
1621
+ used: spendCents / 100,
1622
+ currencyCode: "USD",
1623
+ period: "monthly",
1624
+ updatedAt: now
1625
+ },
1626
+ totals: { tokens, requests },
1627
+ dataConfidence: "exact",
1628
+ updatedAt: now
1629
+ };
1630
+ return {
1631
+ usage,
1632
+ sourceLabel: "Hanzo Cloud",
1633
+ strategyId: this.id,
1634
+ strategyKind: this.kind
1635
+ };
1636
+ }
1637
+ };
1638
+ var devHome = async (ctx) => {
1639
+ const candidates = [
1640
+ ctx.host.env("HANZO_HOME"),
1641
+ ctx.host.env("CODEX_HOME"),
1642
+ expandHome(ctx.host, "~/.hanzo"),
1643
+ expandHome(ctx.host, "~/.codex")
1644
+ ].filter((c) => Boolean(c));
1645
+ for (const dir of candidates) {
1646
+ if ((await ctx.host.listDir(`${dir}/sessions`)).length > 0)
1647
+ return dir;
1648
+ }
1649
+ return void 0;
1650
+ };
1651
+ var newest = (names) => names.filter((n) => /^\d+$/.test(n)).sort().at(-1);
1652
+ var toRateWindow = (w, now) => {
1653
+ if (!w || typeof w.used_percent !== "number")
1654
+ return void 0;
1655
+ return {
1656
+ usedPercent: w.used_percent,
1657
+ windowMinutes: w.window_minutes,
1658
+ resetsAt: typeof w.resets_in_seconds === "number" ? new Date(now.getTime() + w.resets_in_seconds * 1e3).toISOString() : void 0
1659
+ };
1660
+ };
1661
+ var devStrategy = {
1662
+ id: "hanzo.dev",
1663
+ kind: "localProbe",
1664
+ async isAvailable(ctx) {
1665
+ return Boolean(await devHome(ctx));
1666
+ },
1667
+ async fetch(ctx) {
1668
+ const home = await devHome(ctx);
1669
+ if (!home)
1670
+ throw new Error("hanzo: no dev CLI home with sessions");
1671
+ const sessions = `${home}/sessions`;
1672
+ const year = newest(await ctx.host.listDir(sessions));
1673
+ const month = year && newest(await ctx.host.listDir(`${sessions}/${year}`));
1674
+ const day = month && newest(await ctx.host.listDir(`${sessions}/${year}/${month}`));
1675
+ if (!day)
1676
+ throw new Error("hanzo: no dev sessions found");
1677
+ const dayDir = `${sessions}/${year}/${month}/${day}`;
1678
+ const rollouts = (await ctx.host.listDir(dayDir)).filter((f) => f.endsWith(".jsonl")).sort();
1679
+ const latest = rollouts.at(-1);
1680
+ if (!latest)
1681
+ throw new Error("hanzo: no rollout files today");
1682
+ const text = await ctx.host.readTextFile(`${dayDir}/${latest}`) ?? "";
1683
+ let payload;
1684
+ for (const line of text.split("\n")) {
1685
+ if (!line.includes('"token_count"'))
1686
+ continue;
1687
+ try {
1688
+ const item = JSON.parse(line);
1689
+ if (item.payload?.type === "token_count")
1690
+ payload = item.payload;
1691
+ } catch {
1692
+ }
1693
+ }
1694
+ const now = ctx.host.now();
1695
+ const totals = payload?.info?.total_token_usage;
1696
+ const usage = {
1697
+ providerId: "hanzo",
1698
+ primary: toRateWindow(payload?.rate_limits?.primary, now),
1699
+ secondary: toRateWindow(payload?.rate_limits?.secondary, now),
1700
+ identity: { providerId: "hanzo", loginMethod: "dev-cli" },
1701
+ totals: totals ? {
1702
+ tokens: totals.total_tokens ?? 0,
1703
+ inputTokens: totals.input_tokens ?? 0,
1704
+ outputTokens: totals.output_tokens ?? 0,
1705
+ cachedInputTokens: totals.cached_input_tokens ?? 0
1706
+ } : void 0,
1707
+ dataConfidence: totals ? "exact" : "unknown",
1708
+ updatedAt: now.toISOString()
1709
+ };
1710
+ return {
1711
+ usage,
1712
+ sourceLabel: "Hanzo Dev CLI",
1713
+ strategyId: this.id,
1714
+ strategyKind: this.kind
1715
+ };
1716
+ }
1717
+ };
1718
+ var hanzoProvider = {
1719
+ id: "hanzo",
1720
+ metadata: {
1721
+ displayName: "Hanzo",
1722
+ sessionLabel: "5h limit",
1723
+ weeklyLabel: "Weekly limit",
1724
+ defaultEnabled: true,
1725
+ dashboardUrl: "https://console.hanzo.ai/billing/usage",
1726
+ color: "#ff2d55"
1727
+ },
1728
+ strategies: (mode) => forMode([cloudStrategy, devStrategy], mode)
1729
+ };
1730
+
1731
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/api-token.js
1732
+ var resolveBaseUrl = (ctx, envKeys = []) => {
1733
+ const fromSettings = ctx.settings?.baseUrl;
1734
+ if (typeof fromSettings === "string" && fromSettings.length > 0)
1735
+ return fromSettings;
1736
+ for (const name of envKeys) {
1737
+ const value = ctx.host.env(name);
1738
+ if (value)
1739
+ return value;
1740
+ }
1741
+ return void 0;
1742
+ };
1743
+ var resolveApiKey = (ctx, envKeys = []) => {
1744
+ const fromSettings = ctx.settings?.apiKey;
1745
+ if (typeof fromSettings === "string" && fromSettings.length > 0)
1746
+ return fromSettings;
1747
+ for (const name of envKeys) {
1748
+ const value = ctx.host.env(name);
1749
+ if (value)
1750
+ return value;
1751
+ }
1752
+ return void 0;
1753
+ };
1754
+ var makeApiTokenStrategy = (config) => {
1755
+ const label = config.sourceLabel ?? config.metadata.displayName;
1756
+ return {
1757
+ id: `${config.id}.apiToken`,
1758
+ kind: "apiToken",
1759
+ async isAvailable(ctx) {
1760
+ if (!resolveApiKey(ctx, config.envKeys))
1761
+ return false;
1762
+ if (config.requireBaseUrl && !resolveBaseUrl(ctx, config.baseUrlEnv))
1763
+ return false;
1764
+ return true;
1765
+ },
1766
+ async fetch(ctx) {
1767
+ const apiKey = resolveApiKey(ctx, config.envKeys);
1768
+ if (!apiKey)
1769
+ throw new Error(`${config.id}: no API key`);
1770
+ const baseUrl = resolveBaseUrl(ctx, config.baseUrlEnv);
1771
+ if (config.requireBaseUrl && !baseUrl)
1772
+ throw new Error(`${config.id}: no base URL`);
1773
+ const settings = { ...ctx.settings, apiKey, ...baseUrl ? { baseUrl } : {} };
1774
+ const req = config.request(settings, ctx);
1775
+ const res = await ctx.host.http({ method: "GET", timeoutMs: 3e4, ...req });
1776
+ if (res.status !== 200)
1777
+ throw new Error(`${config.id} HTTP ${res.status}`);
1778
+ let body;
1779
+ try {
1780
+ body = JSON.parse(res.text);
1781
+ } catch {
1782
+ throw new Error(`${config.id}: invalid JSON body`);
1783
+ }
1784
+ const now = ctx.host.now();
1785
+ const iso = now.toISOString();
1786
+ const { usage, credits } = config.map(body, now, settings);
1787
+ return {
1788
+ usage: { ...usage, providerId: config.id, updatedAt: iso },
1789
+ credits: credits ? { ...credits, updatedAt: iso } : void 0,
1790
+ sourceLabel: label,
1791
+ strategyId: `${config.id}.apiToken`,
1792
+ strategyKind: "apiToken"
1793
+ };
1794
+ },
1795
+ // API-key providers have exactly one source; nothing to fall back to.
1796
+ shouldFallback() {
1797
+ return false;
1798
+ }
1799
+ };
1800
+ };
1801
+ var makeApiTokenProvider = (config) => {
1802
+ const strategy = makeApiTokenStrategy(config);
1803
+ return {
1804
+ id: config.id,
1805
+ metadata: config.metadata,
1806
+ strategies: (mode) => forMode([strategy], mode)
1807
+ };
1808
+ };
1809
+ var numberOrUndefined = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
1810
+ var bearer = (key) => ({
1811
+ Authorization: `Bearer ${key}`,
1812
+ Accept: "application/json"
1813
+ });
1814
+
1815
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/groq.js
1816
+ var ENV_KEYS = ["GROQ_API_KEY"];
1817
+ var ENV_URL = "GROQ_API_URL";
1818
+ var DEFAULT_BASE = "https://api.groq.com/v1";
1819
+ var QUERIES = {
1820
+ requests: "sum(model_project_id_status_code:requests:rate5m)",
1821
+ tokensIn: "sum(model_project_id:tokens_in:rate5m)",
1822
+ tokensOut: "sum(model_project_id:tokens_out:rate5m)"
1823
+ };
1824
+ var sumRate = (body) => {
1825
+ if (body.status !== "success")
1826
+ return 0;
1827
+ let total = 0;
1828
+ for (const series of body.data?.result ?? []) {
1829
+ const raw = series.value?.[1];
1830
+ const n = typeof raw === "number" ? raw : Number(raw);
1831
+ if (Number.isFinite(n))
1832
+ total += n;
1833
+ }
1834
+ return total;
1835
+ };
1836
+ var groqStrategy = {
1837
+ id: "groq.apiToken",
1838
+ kind: "apiToken",
1839
+ async isAvailable(ctx) {
1840
+ return Boolean(resolveApiKey(ctx, ENV_KEYS));
1841
+ },
1842
+ async fetch(ctx) {
1843
+ const key = resolveApiKey(ctx, ENV_KEYS);
1844
+ if (!key)
1845
+ throw new Error("groq: no API key");
1846
+ const base = ctx.settings?.baseUrl ?? ctx.host.env(ENV_URL) ?? DEFAULT_BASE;
1847
+ const query = async (promql) => {
1848
+ const res = await ctx.host.http({
1849
+ url: `${base}/metrics/prometheus/api/v1/query?query=${encodeURIComponent(promql)}`,
1850
+ headers: bearer(key),
1851
+ timeoutMs: 3e4
1852
+ });
1853
+ if (res.status !== 200)
1854
+ throw new Error(`groq metrics HTTP ${res.status}`);
1855
+ return JSON.parse(res.text);
1856
+ };
1857
+ const [requests, tokensIn, tokensOut] = await Promise.all([
1858
+ query(QUERIES.requests),
1859
+ query(QUERIES.tokensIn),
1860
+ query(QUERIES.tokensOut)
1861
+ ]);
1862
+ const requestsPerMin = Math.round(sumRate(requests) * 60);
1863
+ const tokensPerMin = Math.round((sumRate(tokensIn) + sumRate(tokensOut)) * 60);
1864
+ const now = ctx.host.now().toISOString();
1865
+ const usage = {
1866
+ providerId: "groq",
1867
+ identity: { providerId: "groq", loginMethod: "api" },
1868
+ totals: { tokens: tokensPerMin, requests: requestsPerMin },
1869
+ dataConfidence: "estimated",
1870
+ updatedAt: now
1871
+ };
1872
+ return { usage, sourceLabel: "Groq metrics", strategyId: this.id, strategyKind: this.kind };
1873
+ },
1874
+ shouldFallback() {
1875
+ return false;
1876
+ }
1877
+ };
1878
+ var groqProvider = {
1879
+ id: "groq",
1880
+ metadata: {
1881
+ displayName: "Groq",
1882
+ sessionLabel: "Throughput (req/min)",
1883
+ weeklyLabel: "Tokens/min",
1884
+ dashboardUrl: "https://console.groq.com/metrics",
1885
+ color: "#f55036"
1886
+ },
1887
+ strategies: (mode) => forMode([groqStrategy], mode)
1888
+ };
1889
+
1890
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/deepgram.js
1891
+ var ENV_KEYS2 = ["DEEPGRAM_API_KEY"];
1892
+ var ENV_URL2 = "DEEPGRAM_API_URL";
1893
+ var ENV_PROJECT = "DEEPGRAM_PROJECT_ID";
1894
+ var DEFAULT_BASE2 = "https://api.deepgram.com/v1";
1895
+ var num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
1896
+ var deepgramStrategy = {
1897
+ id: "deepgram.apiToken",
1898
+ kind: "apiToken",
1899
+ async isAvailable(ctx) {
1900
+ return Boolean(resolveApiKey(ctx, ENV_KEYS2));
1901
+ },
1902
+ async fetch(ctx) {
1903
+ const key = resolveApiKey(ctx, ENV_KEYS2);
1904
+ if (!key)
1905
+ throw new Error("deepgram: no API key");
1906
+ const base = ctx.settings?.baseUrl ?? ctx.host.env(ENV_URL2) ?? DEFAULT_BASE2;
1907
+ const headers = { Authorization: `Token ${key}`, Accept: "application/json" };
1908
+ const get = async (path) => {
1909
+ const res = await ctx.host.http({ url: `${base}${path}`, headers, timeoutMs: 3e4 });
1910
+ if (res.status !== 200)
1911
+ throw new Error(`deepgram ${path} HTTP ${res.status}`);
1912
+ return JSON.parse(res.text);
1913
+ };
1914
+ const configured = (typeof ctx.settings?.projectId === "string" ? ctx.settings.projectId : void 0) ?? ctx.host.env(ENV_PROJECT);
1915
+ let projectIds;
1916
+ if (configured) {
1917
+ projectIds = [configured];
1918
+ } else {
1919
+ const body = await get("/projects");
1920
+ const projects = Array.isArray(body.projects) ? body.projects : [];
1921
+ projectIds = projects.map((p) => p.project_id).filter((id) => Boolean(id));
1922
+ }
1923
+ let hours = 0;
1924
+ let tokens = 0;
1925
+ let requests = 0;
1926
+ for (const id of projectIds) {
1927
+ const body = await get(`/projects/${id}/usage/breakdown`);
1928
+ for (const r of Array.isArray(body.results) ? body.results : []) {
1929
+ hours += num2(r.total_hours);
1930
+ tokens += num2(r.tokens_in) + num2(r.tokens_out);
1931
+ requests += num2(r.requests);
1932
+ }
1933
+ }
1934
+ const now = ctx.host.now().toISOString();
1935
+ const usage = {
1936
+ providerId: "deepgram",
1937
+ identity: {
1938
+ providerId: "deepgram",
1939
+ loginMethod: "api",
1940
+ accountOrganization: projectIds.length === 1 ? projectIds[0] : `${projectIds.length} projects`
1941
+ },
1942
+ totals: { tokens, requests },
1943
+ dataConfidence: tokens > 0 || requests > 0 || hours > 0 ? "exact" : "unknown",
1944
+ updatedAt: now
1945
+ };
1946
+ return { usage, sourceLabel: "Deepgram usage", strategyId: this.id, strategyKind: this.kind };
1947
+ },
1948
+ shouldFallback() {
1949
+ return false;
1950
+ }
1951
+ };
1952
+ var deepgramProvider = {
1953
+ id: "deepgram",
1954
+ metadata: {
1955
+ displayName: "Deepgram",
1956
+ sessionLabel: "Usage",
1957
+ weeklyLabel: "Requests",
1958
+ dashboardUrl: "https://console.deepgram.com/usage",
1959
+ color: "#13ef93"
1960
+ },
1961
+ strategies: (mode) => forMode([deepgramStrategy], mode)
1962
+ };
1963
+
1964
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/byo.js
1965
+ var ENV_KEYS3 = ["BYO_API_KEY"];
1966
+ var ENV_URL3 = ["BYO_BASE_URL"];
1967
+ var num3 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
1968
+ var obj = (v) => v && typeof v === "object" ? v : {};
1969
+ var stripV1 = (base) => base.replace(/\/v1\/?$/, "").replace(/\/$/, "");
1970
+ var keyInfo = async (ctx, base, key, now) => {
1971
+ const res = await ctx.host.http({
1972
+ url: `${stripV1(base)}/key/info`,
1973
+ headers: bearer(key),
1974
+ timeoutMs: 15e3
1975
+ });
1976
+ if (res.status !== 200)
1977
+ return void 0;
1978
+ let info;
1979
+ try {
1980
+ info = obj(obj(JSON.parse(res.text)).info);
1981
+ } catch {
1982
+ return void 0;
1983
+ }
1984
+ if (Object.keys(info).length === 0)
1985
+ return void 0;
1986
+ const spend = num3(info.spend);
1987
+ return {
1988
+ providerId: "byo",
1989
+ identity: {
1990
+ providerId: "byo",
1991
+ loginMethod: "api",
1992
+ accountEmail: typeof info.user_id === "string" ? info.user_id : void 0,
1993
+ accountOrganization: typeof info.team_id === "string" ? info.team_id : void 0,
1994
+ plan: typeof info.key_name === "string" ? info.key_name : void 0
1995
+ },
1996
+ providerCost: { used: spend, currencyCode: "USD", period: "key", updatedAt: now.toISOString() },
1997
+ subscriptionExpiresAt: typeof info.expires === "string" ? info.expires : void 0,
1998
+ dataConfidence: "exact",
1999
+ updatedAt: now.toISOString()
2000
+ };
2001
+ };
2002
+ var modelsLiveness = async (ctx, base, key, now) => {
2003
+ const root = stripV1(base);
2004
+ const res = await ctx.host.http({
2005
+ url: `${root}/v1/models`,
2006
+ headers: bearer(key),
2007
+ timeoutMs: 15e3
2008
+ });
2009
+ if (res.status !== 200)
2010
+ throw new Error(`byo: /v1/models HTTP ${res.status}`);
2011
+ let count;
2012
+ try {
2013
+ const data = obj(JSON.parse(res.text)).data;
2014
+ if (Array.isArray(data))
2015
+ count = data.length;
2016
+ } catch {
2017
+ }
2018
+ return {
2019
+ providerId: "byo",
2020
+ identity: {
2021
+ providerId: "byo",
2022
+ loginMethod: "api",
2023
+ accountOrganization: count !== void 0 ? `${count} models` : void 0
2024
+ },
2025
+ dataConfidence: "unknown",
2026
+ updatedAt: now.toISOString()
2027
+ };
2028
+ };
2029
+ var byoStrategy = {
2030
+ id: "byo.apiToken",
2031
+ kind: "apiToken",
2032
+ async isAvailable(ctx) {
2033
+ return Boolean(resolveApiKey(ctx, ENV_KEYS3) && resolveBaseUrl(ctx, ENV_URL3));
2034
+ },
2035
+ async fetch(ctx) {
2036
+ const key = resolveApiKey(ctx, ENV_KEYS3);
2037
+ const base = resolveBaseUrl(ctx, ENV_URL3);
2038
+ if (!key || !base)
2039
+ throw new Error("byo: baseUrl and apiKey are required");
2040
+ const now = ctx.host.now();
2041
+ const info = await keyInfo(ctx, base, key, now);
2042
+ if (info) {
2043
+ return { usage: info, sourceLabel: "BYO /key/info", strategyId: this.id, strategyKind: this.kind };
2044
+ }
2045
+ const usage = await modelsLiveness(ctx, base, key, now);
2046
+ return { usage, sourceLabel: "BYO liveness", strategyId: this.id, strategyKind: this.kind };
2047
+ },
2048
+ shouldFallback() {
2049
+ return false;
2050
+ }
2051
+ };
2052
+ var byoProvider = {
2053
+ id: "byo",
2054
+ metadata: {
2055
+ displayName: "BYO endpoint",
2056
+ sessionLabel: "Key spend",
2057
+ weeklyLabel: "Liveness",
2058
+ dashboardUrl: "https://docs.hanzo.ai/docs/gateway",
2059
+ color: "#64748b"
2060
+ },
2061
+ strategies: (mode) => forMode([byoStrategy], mode)
2062
+ };
2063
+
2064
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/providers/api-token-providers.js
2065
+ var num4 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
2066
+ var loose = (v) => {
2067
+ if (typeof v === "number" && Number.isFinite(v))
2068
+ return v;
2069
+ if (typeof v === "string") {
2070
+ const n = Number(v);
2071
+ return Number.isFinite(n) ? n : void 0;
2072
+ }
2073
+ return void 0;
2074
+ };
2075
+ var pct = (used, limit) => limit > 0 ? Math.min(100, Math.max(0, used / limit * 100)) : 0;
2076
+ var obj2 = (v) => v && typeof v === "object" ? v : {};
2077
+ var openrouterProvider = makeApiTokenProvider({
2078
+ id: "openrouter",
2079
+ metadata: {
2080
+ displayName: "OpenRouter",
2081
+ sessionLabel: "Credits used",
2082
+ weeklyLabel: "Credit limit",
2083
+ supportsCredits: true,
2084
+ dashboardUrl: "https://openrouter.ai/settings/credits",
2085
+ color: "#6467f2"
2086
+ },
2087
+ envKeys: ["OPENROUTER_API_KEY"],
2088
+ request: (s, ctx) => ({
2089
+ url: `${s.baseUrl ?? ctx.host.env("OPENROUTER_API_URL") ?? "https://openrouter.ai/api/v1"}/credits`,
2090
+ headers: bearer(s.apiKey)
2091
+ }),
2092
+ map: (json, now) => {
2093
+ const d = obj2(obj2(json).data);
2094
+ const total = num4(d.total_credits);
2095
+ const used = num4(d.total_usage);
2096
+ return {
2097
+ usage: {
2098
+ identity: { providerId: "openrouter", loginMethod: "api" },
2099
+ providerCost: { used, limit: total, currencyCode: "USD", period: "total", updatedAt: now.toISOString() },
2100
+ dataConfidence: "exact"
2101
+ },
2102
+ credits: { remaining: Math.max(0, total - used) }
2103
+ };
2104
+ }
2105
+ });
2106
+ var deepseekProvider = makeApiTokenProvider({
2107
+ id: "deepseek",
2108
+ metadata: {
2109
+ displayName: "DeepSeek",
2110
+ sessionLabel: "Balance",
2111
+ weeklyLabel: "Balance",
2112
+ supportsCredits: true,
2113
+ dashboardUrl: "https://platform.deepseek.com/usage",
2114
+ color: "#527df0"
2115
+ },
2116
+ envKeys: ["DEEPSEEK_API_KEY", "DEEPSEEK_KEY"],
2117
+ request: (s) => ({ url: "https://api.deepseek.com/user/balance", headers: bearer(s.apiKey) }),
2118
+ map: (json) => {
2119
+ const infos = Array.isArray(obj2(json).balance_infos) ? obj2(json).balance_infos : [];
2120
+ const bal = (r) => loose(r.total_balance) ?? 0;
2121
+ const row = infos.find((r) => r.currency === "USD" && bal(r) > 0) ?? infos.find((r) => bal(r) > 0) ?? infos.find((r) => r.currency === "USD") ?? infos[0];
2122
+ return {
2123
+ usage: {
2124
+ identity: { providerId: "deepseek", loginMethod: "api", plan: typeof row?.currency === "string" ? row.currency : void 0 },
2125
+ dataConfidence: "exact"
2126
+ },
2127
+ credits: { remaining: row ? bal(row) : 0 }
2128
+ };
2129
+ }
2130
+ });
2131
+ var elevenlabsProvider = makeApiTokenProvider({
2132
+ id: "elevenlabs",
2133
+ metadata: {
2134
+ displayName: "ElevenLabs",
2135
+ sessionLabel: "Characters",
2136
+ weeklyLabel: "Character limit",
2137
+ dashboardUrl: "https://elevenlabs.io/app/subscription",
2138
+ color: "#ebebe6"
2139
+ },
2140
+ envKeys: ["ELEVENLABS_API_KEY", "XI_API_KEY"],
2141
+ request: (s, ctx) => ({
2142
+ url: `${s.baseUrl ?? ctx.host.env("ELEVENLABS_API_URL") ?? "https://api.elevenlabs.io"}/v1/user/subscription`,
2143
+ headers: { "xi-api-key": s.apiKey, Accept: "application/json" }
2144
+ }),
2145
+ map: (json, now) => {
2146
+ const b = obj2(json);
2147
+ const count = num4(b.character_count);
2148
+ const limit = num4(b.character_limit);
2149
+ const resetUnix = numberOrUndefined(b.next_character_count_reset_unix);
2150
+ const resetsAt = resetUnix !== void 0 ? new Date(resetUnix * 1e3).toISOString() : void 0;
2151
+ const overage = obj2(b.current_overage);
2152
+ const overageAmount = loose(overage.amount);
2153
+ return {
2154
+ usage: {
2155
+ primary: { usedPercent: pct(count, limit), resetsAt },
2156
+ identity: { providerId: "elevenlabs", loginMethod: "api", plan: typeof b.tier === "string" ? b.tier : void 0 },
2157
+ subscriptionRenewsAt: resetsAt,
2158
+ providerCost: overageAmount !== void 0 && overageAmount > 0 ? {
2159
+ used: overageAmount,
2160
+ currencyCode: typeof overage.currency === "string" ? overage.currency : "USD",
2161
+ period: "overage",
2162
+ updatedAt: now.toISOString()
2163
+ } : void 0,
2164
+ dataConfidence: "exact"
2165
+ }
2166
+ };
2167
+ }
2168
+ });
2169
+ var poeProvider = makeApiTokenProvider({
2170
+ id: "poe",
2171
+ metadata: {
2172
+ displayName: "Poe",
2173
+ sessionLabel: "Points",
2174
+ weeklyLabel: "Points",
2175
+ supportsCredits: true,
2176
+ dashboardUrl: "https://poe.com/api/keys",
2177
+ color: "#5d5cde"
2178
+ },
2179
+ envKeys: ["POE_API_KEY"],
2180
+ request: (s) => ({ url: "https://api.poe.com/usage/current_balance", headers: bearer(s.apiKey) }),
2181
+ map: (json) => ({
2182
+ usage: { identity: { providerId: "poe", loginMethod: "api" }, dataConfidence: "exact" },
2183
+ credits: { remaining: num4(obj2(json).current_point_balance) }
2184
+ })
2185
+ });
2186
+ var veniceProvider = makeApiTokenProvider({
2187
+ id: "venice",
2188
+ metadata: {
2189
+ displayName: "Venice",
2190
+ sessionLabel: "Balance",
2191
+ weeklyLabel: "DIEM allocation",
2192
+ supportsCredits: true,
2193
+ dashboardUrl: "https://venice.ai/settings/api",
2194
+ color: "#3399ff"
2195
+ },
2196
+ envKeys: ["VENICE_API_KEY", "VENICE_KEY"],
2197
+ request: (s) => ({ url: "https://api.venice.ai/api/v1/billing/balance", headers: bearer(s.apiKey) }),
2198
+ map: (json) => {
2199
+ const b = obj2(json);
2200
+ const balances = obj2(b.balances);
2201
+ const canConsume = b.canConsume !== false;
2202
+ const currency = (typeof b.consumptionCurrency === "string" ? b.consumptionCurrency : "USD").toUpperCase();
2203
+ const usd = loose(balances.usd);
2204
+ const diem = loose(balances.diem);
2205
+ const alloc = loose(b.diemEpochAllocation);
2206
+ const isDiem = currency === "DIEM";
2207
+ const remaining = (isDiem ? diem : usd) ?? 0;
2208
+ let usedPercent = 0;
2209
+ if (!canConsume)
2210
+ usedPercent = 100;
2211
+ else if (isDiem && alloc && alloc > 0)
2212
+ usedPercent = pct(Math.max(0, alloc - remaining), alloc);
2213
+ return {
2214
+ usage: {
2215
+ primary: { usedPercent },
2216
+ identity: { providerId: "venice", loginMethod: "api", plan: currency },
2217
+ dataConfidence: "exact"
2218
+ },
2219
+ credits: { remaining }
2220
+ };
2221
+ }
2222
+ });
2223
+ var chutesWindow = (container, windowMinutes) => {
2224
+ const w = obj2(container);
2225
+ const pick = (keys) => {
2226
+ for (const k of keys) {
2227
+ const v = loose(w[k]);
2228
+ if (v !== void 0)
2229
+ return v;
2230
+ }
2231
+ return void 0;
2232
+ };
2233
+ const explicit = pick(["percent_used", "usage_percent", "used_percent", "utilization", "utilization_percent"]);
2234
+ const remainingPct = pick(["percent_remaining", "remaining_percent"]);
2235
+ const used = pick(["used", "usage", "consumed", "current", "requests", "tokens", "monthly_usage"]);
2236
+ const limit = pick(["limit", "cap", "max", "maximum", "quota", "quota_limit", "monthly_limit", "total"]);
2237
+ const remaining = pick(["remaining", "available", "balance", "left"]);
2238
+ let usedPercent = explicit;
2239
+ if (usedPercent === void 0 && remainingPct !== void 0)
2240
+ usedPercent = 100 - remainingPct;
2241
+ if (usedPercent !== void 0 && usedPercent <= 1 && usedPercent >= 0)
2242
+ usedPercent *= 100;
2243
+ if (usedPercent === void 0 && limit !== void 0) {
2244
+ const u = used ?? (remaining !== void 0 ? limit - remaining : void 0);
2245
+ if (u !== void 0)
2246
+ usedPercent = pct(u, limit);
2247
+ }
2248
+ if (usedPercent === void 0)
2249
+ return void 0;
2250
+ const resetRaw = pick(["reset_at", "resets_at", "next_reset_at", "period_end", "current_period_end", "expires_at", "window_end"]);
2251
+ return {
2252
+ usedPercent: Math.min(100, Math.max(0, usedPercent)),
2253
+ windowMinutes,
2254
+ resetsAt: resetRaw !== void 0 ? new Date(resetRaw * 1e3).toISOString() : void 0
2255
+ };
2256
+ };
2257
+ var chutesProvider = makeApiTokenProvider({
2258
+ id: "chutes",
2259
+ metadata: {
2260
+ displayName: "Chutes",
2261
+ sessionLabel: "4h quota",
2262
+ weeklyLabel: "Monthly quota",
2263
+ dashboardUrl: "https://chutes.ai",
2264
+ color: "#eab308"
2265
+ },
2266
+ envKeys: ["CHUTES_API_KEY"],
2267
+ request: (s, ctx) => ({
2268
+ url: `${s.baseUrl ?? ctx.host.env("CHUTES_API_URL") ?? "https://api.chutes.ai"}/users/me/subscription_usage`,
2269
+ headers: bearer(s.apiKey)
2270
+ }),
2271
+ map: (json) => {
2272
+ const b = obj2(json);
2273
+ const rolling = chutesWindow(b.rolling, 240) ?? chutesWindow(b.rolling_window, 240) ?? chutesWindow(b.four_hour, 240);
2274
+ const monthly = chutesWindow(b.monthly, 43200) ?? chutesWindow(b.subscription, 43200) ?? chutesWindow(b.subscription_usage, 43200);
2275
+ const plan = b.plan_name ?? b.plan ?? b.tier;
2276
+ return {
2277
+ usage: {
2278
+ primary: rolling,
2279
+ secondary: monthly,
2280
+ identity: { providerId: "chutes", loginMethod: "api", plan: typeof plan === "string" ? plan : void 0 },
2281
+ dataConfidence: rolling || monthly ? "percentOnly" : "unknown"
2282
+ }
2283
+ };
2284
+ }
2285
+ });
2286
+ var moonshotBase = (region) => region === "china" ? "https://api.moonshot.cn" : "https://api.moonshot.ai";
2287
+ var moonshotProvider = makeApiTokenProvider({
2288
+ id: "moonshot",
2289
+ metadata: {
2290
+ displayName: "Moonshot",
2291
+ sessionLabel: "Balance",
2292
+ weeklyLabel: "Balance",
2293
+ supportsCredits: true,
2294
+ dashboardUrl: "https://platform.moonshot.ai/console/info",
2295
+ color: "#16a34a"
2296
+ },
2297
+ envKeys: ["MOONSHOT_API_KEY", "MOONSHOT_KEY"],
2298
+ request: (s, ctx) => ({
2299
+ url: `${s.baseUrl ?? moonshotBase(s.region ?? ctx.host.env("MOONSHOT_REGION"))}/v1/users/me/balance`,
2300
+ headers: bearer(s.apiKey)
2301
+ }),
2302
+ map: (json) => {
2303
+ const d = obj2(obj2(json).data);
2304
+ return {
2305
+ usage: { identity: { providerId: "moonshot", loginMethod: "api" }, dataConfidence: "exact" },
2306
+ credits: { remaining: num4(d.available_balance) }
2307
+ };
2308
+ }
2309
+ });
2310
+ var kimiProvider = makeApiTokenProvider({
2311
+ id: "kimi",
2312
+ metadata: {
2313
+ displayName: "Kimi",
2314
+ sessionLabel: "Weekly",
2315
+ weeklyLabel: "Rate (5h)",
2316
+ dashboardUrl: "https://www.kimi.com/code/console",
2317
+ color: "#111111"
2318
+ },
2319
+ envKeys: ["KIMI_CODE_API_KEY"],
2320
+ request: (s, ctx) => ({
2321
+ url: `${s.baseUrl ?? ctx.host.env("KIMI_CODE_BASE_URL") ?? "https://api.kimi.com"}/coding/v1/usages`,
2322
+ headers: bearer(s.apiKey)
2323
+ }),
2324
+ map: (json) => {
2325
+ const b = obj2(json);
2326
+ const detail = obj2(b.usage);
2327
+ const limit = loose(detail.limit) ?? 0;
2328
+ const used = loose(detail.used) ?? (limit && loose(detail.remaining) !== void 0 ? limit - loose(detail.remaining) : 0);
2329
+ const resetTime = detail.resetTime ?? detail.resetAt ?? detail.reset_time;
2330
+ const rate = obj2(obj2(Array.isArray(b.limits) ? b.limits[0] : void 0).detail);
2331
+ const rateLimit = loose(rate.limit);
2332
+ const rateUsed = loose(rate.used);
2333
+ const secondary = rateLimit !== void 0 ? { usedPercent: pct(rateUsed ?? 0, rateLimit), windowMinutes: 300 } : void 0;
2334
+ return {
2335
+ usage: {
2336
+ primary: { usedPercent: pct(used, limit), resetsAt: typeof resetTime === "string" ? resetTime : void 0 },
2337
+ secondary,
2338
+ identity: { providerId: "kimi", loginMethod: "api" },
2339
+ dataConfidence: "percentOnly"
2340
+ }
2341
+ };
2342
+ }
2343
+ });
2344
+ var kimik2Provider = makeApiTokenProvider({
2345
+ id: "kimik2",
2346
+ metadata: {
2347
+ displayName: "Kimi K2",
2348
+ sessionLabel: "Credits",
2349
+ weeklyLabel: "Credits",
2350
+ supportsCredits: true,
2351
+ dashboardUrl: "https://kimi-k2.ai",
2352
+ color: "#111111"
2353
+ },
2354
+ envKeys: ["KIMI_K2_API_KEY", "KIMI_API_KEY", "KIMI_KEY"],
2355
+ request: (s) => ({ url: "https://kimi-k2.ai/api/user/credits", headers: bearer(s.apiKey) }),
2356
+ map: (json) => {
2357
+ const b = obj2(json);
2358
+ const nested = { ...obj2(b.data), ...obj2(b.result), ...obj2(b.usage), ...obj2(b.credits) };
2359
+ const pick = (keys) => {
2360
+ for (const k of keys) {
2361
+ const v = loose(b[k]) ?? loose(nested[k]);
2362
+ if (v !== void 0)
2363
+ return v;
2364
+ }
2365
+ return void 0;
2366
+ };
2367
+ const remaining = pick([
2368
+ "credits_remaining",
2369
+ "creditsRemaining",
2370
+ "remaining_credits",
2371
+ "remainingCredits",
2372
+ "available_credits",
2373
+ "availableCredits",
2374
+ "credits_left",
2375
+ "creditsLeft",
2376
+ "remaining"
2377
+ ]) ?? 0;
2378
+ return {
2379
+ usage: { identity: { providerId: "kimik2", loginMethod: "api" }, dataConfidence: "exact" },
2380
+ credits: { remaining }
2381
+ };
2382
+ }
2383
+ });
2384
+ var zaiBase = (region) => region === "bigmodel-cn" ? "https://open.bigmodel.cn" : "https://api.z.ai";
2385
+ var ZAI_UNIT_MINUTES = { 1: 1440, 3: 60, 5: 1, 6: 10080 };
2386
+ var zaiWindow = (raw) => {
2387
+ const limit = loose(raw.usage) ?? 0;
2388
+ const current = loose(raw.currentValue);
2389
+ const remaining = loose(raw.remaining);
2390
+ const used = current ?? (remaining !== void 0 ? Math.max(0, limit - remaining) : void 0);
2391
+ const explicit = loose(raw.percentage);
2392
+ const usedPercent = used !== void 0 && limit > 0 ? pct(used, limit) : explicit ?? 0;
2393
+ const unit = loose(raw.unit);
2394
+ const number = loose(raw.number);
2395
+ const resetMs = loose(raw.nextResetTime);
2396
+ return {
2397
+ usedPercent,
2398
+ windowMinutes: unit !== void 0 && number !== void 0 ? (ZAI_UNIT_MINUTES[unit] ?? 0) * number : void 0,
2399
+ resetsAt: resetMs !== void 0 ? new Date(resetMs).toISOString() : void 0
2400
+ };
2401
+ };
2402
+ var zaiProvider = makeApiTokenProvider({
2403
+ id: "zai",
2404
+ metadata: {
2405
+ displayName: "z.ai",
2406
+ sessionLabel: "Token quota",
2407
+ weeklyLabel: "Time quota",
2408
+ dashboardUrl: "https://z.ai/manage-apikey/coding-plan/personal/my-plan",
2409
+ color: "#3b82f6"
2410
+ },
2411
+ envKeys: ["Z_AI_API_KEY"],
2412
+ baseUrlEnv: ["Z_AI_API_HOST"],
2413
+ request: (s, ctx) => ({
2414
+ url: `${resolveZaiBase(s, ctx.host.env("Z_AI_API_HOST"))}/api/monitor/usage/quota/limit`,
2415
+ headers: { ...bearer(s.apiKey), accept: "application/json" }
2416
+ }),
2417
+ map: (json) => {
2418
+ const data = obj2(obj2(json).data);
2419
+ const limits = Array.isArray(data.limits) ? data.limits : [];
2420
+ const tokens = limits.filter((l) => l.type === "TOKENS_LIMIT").map(zaiWindow);
2421
+ const time = limits.find((l) => l.type === "TIME_LIMIT");
2422
+ const plan = data.planName ?? data.plan ?? data.planType ?? data.packageName;
2423
+ return {
2424
+ usage: {
2425
+ primary: tokens[0],
2426
+ secondary: time ? zaiWindow(time) : void 0,
2427
+ tertiary: tokens[1],
2428
+ identity: { providerId: "zai", loginMethod: "api", plan: typeof plan === "string" ? plan : void 0 },
2429
+ dataConfidence: tokens.length || time ? "percentOnly" : "unknown"
2430
+ }
2431
+ };
2432
+ }
2433
+ });
2434
+ var resolveZaiBase = (s, hostEnv) => s.baseUrl ?? hostEnv ?? zaiBase(s.region);
2435
+ var openaiProvider = makeApiTokenProvider({
2436
+ id: "openai",
2437
+ metadata: {
2438
+ displayName: "OpenAI",
2439
+ sessionLabel: "Credits used",
2440
+ weeklyLabel: "Credit grant",
2441
+ supportsCredits: true,
2442
+ dashboardUrl: "https://platform.openai.com/usage",
2443
+ statusPageUrl: "https://status.openai.com",
2444
+ color: "#0f826e"
2445
+ },
2446
+ envKeys: ["OPENAI_ADMIN_KEY", "OPENAI_API_KEY"],
2447
+ request: (s) => ({
2448
+ url: "https://api.openai.com/v1/dashboard/billing/credit_grants",
2449
+ headers: bearer(s.apiKey)
2450
+ }),
2451
+ map: (json, now) => {
2452
+ const b = obj2(json);
2453
+ const granted = num4(b.total_granted);
2454
+ const used = num4(b.total_used);
2455
+ const available = num4(b.total_available);
2456
+ return {
2457
+ usage: {
2458
+ primary: { usedPercent: pct(used, granted) },
2459
+ identity: { providerId: "openai", loginMethod: "api" },
2460
+ providerCost: { used, limit: granted, currencyCode: "USD", period: "grant", updatedAt: now.toISOString() },
2461
+ dataConfidence: "exact"
2462
+ },
2463
+ credits: { remaining: available }
2464
+ };
2465
+ }
2466
+ });
2467
+ var stripV12 = (base) => base.replace(/\/v1\/?$/, "");
2468
+ var litellmProvider = makeApiTokenProvider({
2469
+ id: "litellm",
2470
+ metadata: {
2471
+ displayName: "LiteLLM",
2472
+ sessionLabel: "Key spend",
2473
+ weeklyLabel: "Budget",
2474
+ dashboardUrl: "https://docs.litellm.ai/docs/proxy/cost_tracking",
2475
+ color: "#22c55e"
2476
+ },
2477
+ envKeys: ["LITELLM_API_KEY"],
2478
+ baseUrlEnv: ["LITELLM_BASE_URL"],
2479
+ requireBaseUrl: true,
2480
+ request: (s) => ({ url: `${stripV12(s.baseUrl)}/key/info`, headers: bearer(s.apiKey) }),
2481
+ map: (json, now) => {
2482
+ const info = obj2(obj2(json).info);
2483
+ const spend = num4(info.spend);
2484
+ return {
2485
+ usage: {
2486
+ identity: {
2487
+ providerId: "litellm",
2488
+ loginMethod: "api",
2489
+ accountOrganization: typeof info.team_id === "string" ? info.team_id : void 0,
2490
+ accountEmail: typeof info.user_id === "string" ? info.user_id : void 0,
2491
+ plan: typeof info.key_name === "string" ? info.key_name : void 0
2492
+ },
2493
+ providerCost: { used: spend, currencyCode: "USD", period: "key", updatedAt: now.toISOString() },
2494
+ subscriptionExpiresAt: typeof info.expires === "string" ? info.expires : void 0,
2495
+ dataConfidence: "exact"
2496
+ }
2497
+ };
2498
+ }
2499
+ });
2500
+ var llmProxyV1 = (base) => /\/v1\/?$/.test(base) ? base.replace(/\/$/, "") : `${base.replace(/\/$/, "")}/v1`;
2501
+ var llmproxyProvider = makeApiTokenProvider({
2502
+ id: "llmproxy",
2503
+ metadata: {
2504
+ displayName: "LLM Proxy",
2505
+ sessionLabel: "Quota used",
2506
+ weeklyLabel: "Approx. spend",
2507
+ dashboardUrl: "https://github.com/hzruo/LLM-API-Key-Proxy",
2508
+ color: "#8b5cf6"
2509
+ },
2510
+ envKeys: ["LLM_PROXY_API_KEY"],
2511
+ baseUrlEnv: ["LLM_PROXY_BASE_URL"],
2512
+ requireBaseUrl: true,
2513
+ request: (s) => ({ url: `${llmProxyV1(s.baseUrl)}/quota-stats`, headers: bearer(s.apiKey) }),
2514
+ map: (json, now) => {
2515
+ const b = obj2(json);
2516
+ const providers = obj2(b.providers);
2517
+ const summary = obj2(b.summary);
2518
+ let minRemaining = 100;
2519
+ let earliestReset;
2520
+ let sumTokens = 0;
2521
+ let sumRequests = 0;
2522
+ let sumCost = 0;
2523
+ for (const p of Object.values(providers)) {
2524
+ const stats = obj2(p);
2525
+ sumRequests += num4(stats.total_requests);
2526
+ sumCost += num4(stats.approx_cost);
2527
+ const t = obj2(stats.tokens);
2528
+ sumTokens += num4(t.input_cached) + num4(t.input_uncached) + num4(t.output);
2529
+ const groups = Array.isArray(stats.quota_groups) ? stats.quota_groups : Object.values(obj2(stats.quota_groups));
2530
+ for (const g of groups) {
2531
+ const grp = obj2(g);
2532
+ const rem = loose(grp.remaining_percent);
2533
+ if (rem !== void 0 && rem < minRemaining)
2534
+ minRemaining = rem;
2535
+ const reset = typeof grp.reset_time === "string" ? grp.reset_time : void 0;
2536
+ if (reset && (!earliestReset || reset < earliestReset))
2537
+ earliestReset = reset;
2538
+ }
2539
+ }
2540
+ const totalRequests = numberOrUndefined(summary.total_requests) ?? sumRequests;
2541
+ const totalTokens = numberOrUndefined(summary.total_tokens) ?? sumTokens;
2542
+ const approxCost = numberOrUndefined(summary.approx_cost) ?? sumCost;
2543
+ return {
2544
+ usage: {
2545
+ primary: { usedPercent: Math.min(100, Math.max(0, 100 - minRemaining)), resetsAt: earliestReset },
2546
+ identity: { providerId: "llmproxy", loginMethod: "api" },
2547
+ totals: { tokens: totalTokens, requests: totalRequests },
2548
+ providerCost: approxCost > 0 ? { used: approxCost, currencyCode: "USD", period: "approx", updatedAt: now.toISOString() } : void 0,
2549
+ dataConfidence: "exact"
2550
+ }
2551
+ };
2552
+ }
2553
+ });
2554
+ var minimaxApiBase = (region) => region === "cn" || region === "china" ? "https://api.minimaxi.com" : "https://api.minimax.io";
2555
+ var minimaxWindow = (m, prefix, windowMinutes) => {
2556
+ const remainingPct = loose(m[`${prefix}_remaining_percent`]);
2557
+ const total = loose(m[`${prefix}_total_count`]);
2558
+ const usedCount = loose(m[`${prefix}_usage_count`]);
2559
+ let usedPercent;
2560
+ if (remainingPct !== void 0)
2561
+ usedPercent = 100 - remainingPct;
2562
+ else if (total !== void 0 && usedCount !== void 0)
2563
+ usedPercent = pct(usedCount, total);
2564
+ if (usedPercent === void 0)
2565
+ return void 0;
2566
+ return { usedPercent: Math.min(100, Math.max(0, usedPercent)), windowMinutes };
2567
+ };
2568
+ var minimaxProvider = makeApiTokenProvider({
2569
+ id: "minimax",
2570
+ metadata: {
2571
+ displayName: "MiniMax",
2572
+ sessionLabel: "Interval",
2573
+ weeklyLabel: "Weekly",
2574
+ dashboardUrl: "https://platform.minimax.io/user-center/payment/coding-plan",
2575
+ color: "#f43f5e"
2576
+ },
2577
+ envKeys: ["MINIMAX_CODING_API_KEY", "MINIMAX_API_KEY"],
2578
+ request: (s, ctx) => ({
2579
+ url: `${s.baseUrl ?? minimaxApiBase(s.region ?? ctx.host.env("MINIMAX_REGION"))}/v1/api/openplatform/coding_plan/remains`,
2580
+ headers: { ...bearer(s.apiKey), accept: "application/json", "MM-API-Source": "HanzoUsage" }
2581
+ }),
2582
+ map: (json) => {
2583
+ const data = obj2(obj2(json).data);
2584
+ const models = Array.isArray(data.model_remains) ? data.model_remains : [];
2585
+ const m = obj2(models[0]);
2586
+ const plan = data.plan_name ?? data.current_plan_title ?? data.current_subscribe_title;
2587
+ const points = loose(data.points_balance) ?? loose(data.balance);
2588
+ return {
2589
+ usage: {
2590
+ primary: minimaxWindow(m, "current_interval", 0),
2591
+ secondary: minimaxWindow(m, "current_weekly", 10080),
2592
+ identity: { providerId: "minimax", loginMethod: "api", plan: typeof plan === "string" ? plan : void 0 },
2593
+ dataConfidence: models.length ? "percentOnly" : "unknown"
2594
+ },
2595
+ credits: points !== void 0 ? { remaining: points } : void 0
2596
+ };
2597
+ }
2598
+ });
2599
+
2600
+ // ../../node_modules/.pnpm/@hanzo+usage@0.1.6_react@18.3.1/node_modules/@hanzo/usage/dist/index.js
2601
+ var providerRegistry = {
2602
+ hanzo: hanzoProvider,
2603
+ codex: codexProvider,
2604
+ claude: claudeProvider,
2605
+ openai: openaiProvider,
2606
+ openrouter: openrouterProvider,
2607
+ deepseek: deepseekProvider,
2608
+ elevenlabs: elevenlabsProvider,
2609
+ deepgram: deepgramProvider,
2610
+ groq: groqProvider,
2611
+ poe: poeProvider,
2612
+ venice: veniceProvider,
2613
+ chutes: chutesProvider,
2614
+ moonshot: moonshotProvider,
2615
+ kimi: kimiProvider,
2616
+ kimik2: kimik2Provider,
2617
+ zai: zaiProvider,
2618
+ litellm: litellmProvider,
2619
+ llmproxy: llmproxyProvider,
2620
+ minimax: minimaxProvider,
2621
+ byo: byoProvider
2622
+ };
2623
+ var allProviders = Object.values(providerRegistry);
2624
+ var trackedProviderIds = Object.keys(providerRegistry);
2625
+
2626
+ // src/shared/usage.ts
2627
+ var http = async (req) => {
2628
+ const controller = new AbortController();
2629
+ const timeout = req.timeoutMs ? setTimeout(() => controller.abort(), req.timeoutMs) : void 0;
2630
+ try {
2631
+ const res = await fetch(req.url, {
2632
+ method: req.method ?? "GET",
2633
+ headers: req.headers,
2634
+ body: req.body,
2635
+ credentials: "include",
2636
+ signal: controller.signal
2637
+ });
2638
+ const headers = {};
2639
+ res.headers.forEach((v, k) => {
2640
+ headers[k] = v;
2641
+ });
2642
+ return { status: res.status, headers, text: await res.text() };
2643
+ } finally {
2644
+ if (timeout) clearTimeout(timeout);
2645
+ }
2646
+ };
2647
+ var browserHost = {
2648
+ readTextFile: async () => void 0,
2649
+ listDir: async () => [],
2650
+ writeTextFile: async () => {
2651
+ },
2652
+ http,
2653
+ env: () => void 0,
2654
+ homeDir: () => "",
2655
+ now: () => /* @__PURE__ */ new Date()
2656
+ };
2657
+ var isAuthError = (text) => /\b(401|403)\b/.test(text);
2658
+ var outcomeError = (providerHost, outcome) => {
2659
+ const attempt = [...outcome.attempts].reverse().find((a) => a.error);
2660
+ const detail = attempt?.error ?? "no data";
2661
+ return isAuthError(detail) ? `Open ${providerHost} to sign in` : detail;
2662
+ };
2663
+ var fetchClaude = async () => {
2664
+ const { displayName, color, dashboardUrl } = claudeProvider.metadata;
2665
+ const base = { providerId: "claude", displayName, color, dashboardUrl };
2666
+ try {
2667
+ const outcome = await runPipeline(claudeProvider, {
2668
+ host: browserHost,
2669
+ sourceMode: "web",
2670
+ settings: { cookieHeader: "" }
2671
+ });
2672
+ if (outcome.result) return { ...base, snapshot: outcome.result.usage };
2673
+ return { ...base, error: outcomeError("claude.ai", outcome) };
2674
+ } catch (e) {
2675
+ return { ...base, error: e instanceof Error ? e.message : String(e) };
2676
+ }
2677
+ };
2678
+ var toWindow3 = (w) => {
2679
+ if (!w || typeof w.used_percent !== "number") return void 0;
2680
+ return {
2681
+ usedPercent: w.used_percent,
2682
+ windowMinutes: w.limit_window_seconds ? Math.round(w.limit_window_seconds / 60) : void 0,
2683
+ resetsAt: w.reset_at ? new Date(w.reset_at * 1e3).toISOString() : void 0
2684
+ };
2685
+ };
2686
+ var fetchCodex = async () => {
2687
+ const { displayName, color, dashboardUrl } = codexProvider.metadata;
2688
+ const base = { providerId: "codex", displayName, color, dashboardUrl };
2689
+ try {
2690
+ const res = await http({
2691
+ url: "https://chatgpt.com/backend-api/wham/usage",
2692
+ headers: { Accept: "application/json" },
2693
+ timeoutMs: 3e4
2694
+ });
2695
+ if (res.status !== 200) {
2696
+ return {
2697
+ ...base,
2698
+ error: isAuthError(String(res.status)) ? "Open chatgpt.com to sign in" : `HTTP ${res.status}`
2699
+ };
2700
+ }
2701
+ const body = JSON.parse(res.text);
2702
+ const snapshot = {
2703
+ providerId: "codex",
2704
+ primary: toWindow3(body.rate_limit?.primary_window),
2705
+ secondary: toWindow3(body.rate_limit?.secondary_window),
2706
+ identity: { providerId: "codex", plan: body.plan_type, loginMethod: "web" },
2707
+ dataConfidence: "percentOnly",
2708
+ updatedAt: browserHost.now().toISOString()
2709
+ };
2710
+ return { ...base, snapshot };
2711
+ } catch (e) {
2712
+ return { ...base, error: e instanceof Error ? e.message : String(e) };
2713
+ }
2714
+ };
2715
+ var fetchAllUsage = async () => Promise.all([fetchClaude(), fetchCodex()]);
2716
+
1320
2717
  // src/shared/evaluable.ts
1321
2718
  function pickEvaluable(params) {
1322
2719
  const p = params ?? {};
@@ -4513,6 +5910,16 @@
4513
5910
  case "listTabFS":
4514
5911
  sendResponse({ success: false, error: "Tab filesystem requires CDP bridge." });
4515
5912
  break;
5913
+ // AI provider usage (Claude + Codex, via @hanzo/usage)
5914
+ case "usage.fetch": {
5915
+ try {
5916
+ const providers = await fetchAllUsage();
5917
+ sendResponse({ success: true, providers });
5918
+ } catch (e) {
5919
+ sendResponse({ success: false, error: e.message });
5920
+ }
5921
+ break;
5922
+ }
4516
5923
  default:
4517
5924
  sendResponse({ success: false, error: `Unknown action: ${request.action}` });
4518
5925
  break;
@@ -4552,5 +5959,25 @@
4552
5959
  void hanzoExtension.stopControlSession();
4553
5960
  }
4554
5961
  });
5962
+ try {
5963
+ const omnibox = browser.omnibox;
5964
+ if (omnibox) {
5965
+ omnibox.setDefaultSuggestion({
5966
+ description: "Search Hanzo AI \u2014 AI answers with sources and realtime news"
5967
+ });
5968
+ omnibox.onInputEntered.addListener((text, disposition) => {
5969
+ const url = browser.runtime.getURL("newtab.html") + "?q=" + encodeURIComponent(text);
5970
+ if (disposition === "newForegroundTab") {
5971
+ void browser.tabs.create({ url });
5972
+ } else if (disposition === "newBackgroundTab") {
5973
+ void browser.tabs.create({ url, active: false });
5974
+ } else {
5975
+ void browser.tabs.update({ url });
5976
+ }
5977
+ });
5978
+ }
5979
+ } catch (e) {
5980
+ console.warn("[Hanzo] omnibox unavailable:", e);
5981
+ }
4555
5982
  console.log("[Hanzo] Firefox background script loaded");
4556
5983
  })();