@kairyou/agent-tools 0.6.0 → 0.7.1
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.
- package/README.md +11 -11
- package/README.zh-CN.md +9 -10
- package/dist/usage/cli.mjs +931 -1
- package/dist/usage/core.mjs +54 -125
- package/integrations/usage/cli.mjs +5 -2
- package/integrations/usage/lib/config.mjs +24 -25
- package/integrations/usage/lib/format.mjs +3 -40
- package/integrations/usage/lib/http.mjs +2 -2
- package/integrations/usage/lib/routes.mjs +37 -96
- package/package.json +1 -1
package/dist/usage/core.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
5
5
|
|
|
6
6
|
// integrations/usage/lib/config.mjs
|
|
7
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
7
|
+
import { readFile, writeFile, mkdir, open, stat } from "node:fs/promises";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
@@ -905,10 +905,28 @@ async function agentConfig() {
|
|
|
905
905
|
}
|
|
906
906
|
return agentConfigCache;
|
|
907
907
|
}
|
|
908
|
+
var DEBUG_LOG_MAX_BYTES = 256 * 1024;
|
|
909
|
+
var DEBUG_LOG_KEEP_BYTES = 128 * 1024;
|
|
910
|
+
async function rotateDebugLogIfNeeded() {
|
|
911
|
+
try {
|
|
912
|
+
const { size } = await stat(DEBUG_PATH);
|
|
913
|
+
if (size <= DEBUG_LOG_MAX_BYTES) return;
|
|
914
|
+
const handle = await open(DEBUG_PATH, "r");
|
|
915
|
+
try {
|
|
916
|
+
const buffer = Buffer.alloc(DEBUG_LOG_KEEP_BYTES);
|
|
917
|
+
await handle.read(buffer, 0, DEBUG_LOG_KEEP_BYTES, size - DEBUG_LOG_KEEP_BYTES);
|
|
918
|
+
await writeFile(DEBUG_PATH, buffer.toString("utf8").replace(/^[^\n]*\n?/, ""));
|
|
919
|
+
} finally {
|
|
920
|
+
await handle.close();
|
|
921
|
+
}
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
}
|
|
908
925
|
async function debugLog(event) {
|
|
909
926
|
const config = await agentConfig();
|
|
910
927
|
if (process.env.PROVIDER_USAGE_DEBUG !== "1" && config.debug !== true) return;
|
|
911
928
|
await mkdir(dirname(DEBUG_PATH), { recursive: true });
|
|
929
|
+
await rotateDebugLogIfNeeded();
|
|
912
930
|
const line = JSON.stringify({
|
|
913
931
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
914
932
|
...event
|
|
@@ -919,33 +937,13 @@ async function debugLog(event) {
|
|
|
919
937
|
async function providerUsageDays() {
|
|
920
938
|
const config = await agentConfig();
|
|
921
939
|
const value = Number(process.env.PROVIDER_USAGE_DAYS || config.days || DEFAULT_USAGE_DAYS);
|
|
922
|
-
if (!Number.isInteger(value) || value <= 0
|
|
923
|
-
return value;
|
|
940
|
+
if (!Number.isInteger(value) || value <= 0) return DEFAULT_USAGE_DAYS;
|
|
941
|
+
return Math.min(value, MAX_USAGE_DAYS);
|
|
924
942
|
}
|
|
925
943
|
async function usagePreset() {
|
|
926
944
|
const config = await agentConfig();
|
|
927
945
|
return String(process.env.PROVIDER_USAGE_PRESET || config.preset || "auto").toLowerCase();
|
|
928
946
|
}
|
|
929
|
-
async function panelUserId() {
|
|
930
|
-
const config = await agentConfig();
|
|
931
|
-
const raw = process.env.PROVIDER_USAGE_USER_ID || config.userId || "";
|
|
932
|
-
const parsed = Number.parseInt(String(raw), 10);
|
|
933
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
934
|
-
}
|
|
935
|
-
async function panelUserHeaders() {
|
|
936
|
-
const userId = await panelUserId();
|
|
937
|
-
if (!userId) return {};
|
|
938
|
-
const value = String(userId);
|
|
939
|
-
return {
|
|
940
|
-
"New-API-User": value,
|
|
941
|
-
"Veloera-User": value,
|
|
942
|
-
"voapi-user": value,
|
|
943
|
-
"User-id": value,
|
|
944
|
-
"X-User-Id": value,
|
|
945
|
-
"Rix-Api-User": value,
|
|
946
|
-
"neo-api-user": value
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
947
|
function snapshotTtlMs() {
|
|
950
948
|
const raw = Number(process.env.AGENT_TOOLS_USAGE_SNAPSHOT_TTL_MS);
|
|
951
949
|
return Number.isFinite(raw) && raw >= 0 ? raw : 6e4;
|
|
@@ -1205,11 +1203,11 @@ function mergeSetCookiePairs(cookieHeader, setCookieHeaders) {
|
|
|
1205
1203
|
return merged;
|
|
1206
1204
|
}
|
|
1207
1205
|
async function requestJson(url, options = {}) {
|
|
1208
|
-
const { key = "", headers = {}, name = "usage" } = options;
|
|
1206
|
+
const { key = "", headers = {}, name = "usage", timeoutMs = REQUEST_TIMEOUT_MS } = options;
|
|
1209
1207
|
let cookieHeader = "";
|
|
1210
1208
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1211
1209
|
const controller = new AbortController();
|
|
1212
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
1210
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1213
1211
|
const response = await fetch(url, {
|
|
1214
1212
|
headers: {
|
|
1215
1213
|
accept: "application/json",
|
|
@@ -1388,36 +1386,8 @@ function formatOpenRouterLine(label, data) {
|
|
|
1388
1386
|
if (parts.length === 1) throw new Error("OpenRouter payload has no usage fields");
|
|
1389
1387
|
return parts.join(" | ");
|
|
1390
1388
|
}
|
|
1391
|
-
function
|
|
1392
|
-
return
|
|
1393
|
-
}
|
|
1394
|
-
function panelQuotaLooksRemaining(kind) {
|
|
1395
|
-
return ["new-api", "anyrouter", "agentrouter", "done-hub", "donehub"].includes(kind);
|
|
1396
|
-
}
|
|
1397
|
-
async function formatPanelUserSelfLine(label, data, kind) {
|
|
1398
|
-
const root = usageRoot(data);
|
|
1399
|
-
const scale = panelQuotaScale(kind);
|
|
1400
|
-
const quota = pickNumber(root, ["quota"]);
|
|
1401
|
-
const used = pickNumber(root, ["used_quota", "usedQuota"]);
|
|
1402
|
-
const todayIncome = pickNumber(root, ["today_income", "todayIncome"]);
|
|
1403
|
-
const todayUsed = pickNumber(root, ["today_quota_consumption", "todayQuotaConsumption"]);
|
|
1404
|
-
if (quota === void 0 && used === void 0) {
|
|
1405
|
-
throw new Error("panel /api/user/self payload has no quota fields");
|
|
1406
|
-
}
|
|
1407
|
-
const quotaUsd = quota === void 0 ? void 0 : quota / scale;
|
|
1408
|
-
const usedUsd = used === void 0 ? void 0 : used / scale;
|
|
1409
|
-
const remainingUsd = panelQuotaLooksRemaining(kind) ? quotaUsd : quotaUsd === void 0 || usedUsd === void 0 ? quotaUsd : Math.max(0, quotaUsd - usedUsd);
|
|
1410
|
-
const totalUsd = panelQuotaLooksRemaining(kind) ? quotaUsd === void 0 || usedUsd === void 0 ? quotaUsd : quotaUsd + usedUsd : quotaUsd;
|
|
1411
|
-
const parts = usageParts();
|
|
1412
|
-
if (remainingUsd !== void 0) parts.push(`balance ${formatMoney(remainingUsd)}`);
|
|
1413
|
-
if (usedUsd !== void 0 && totalUsd !== void 0) {
|
|
1414
|
-
parts.push(`used ${formatMoney(usedUsd)}/${formatMoney(totalUsd)}`);
|
|
1415
|
-
} else if (usedUsd !== void 0) {
|
|
1416
|
-
parts.push(`used ${formatMoney(usedUsd)}`);
|
|
1417
|
-
}
|
|
1418
|
-
if (todayUsed !== void 0) parts.push(`today ${formatMoney(todayUsed / scale)}`);
|
|
1419
|
-
if (todayIncome !== void 0) parts.push(`income ${formatMoney(todayIncome / scale)}`);
|
|
1420
|
-
return parts.join(" | ");
|
|
1389
|
+
function formatOneApiBillingLine(limit, used) {
|
|
1390
|
+
return `API | balance ${formatMoney(Math.max(0, limit - used))} | used ${formatMoney(used)}/${formatMoney(limit)}`;
|
|
1421
1391
|
}
|
|
1422
1392
|
function formatQuotaLimitedLine(label, root) {
|
|
1423
1393
|
const quota = root?.quota || {};
|
|
@@ -1497,7 +1467,7 @@ async function fetchV1Usage(context) {
|
|
|
1497
1467
|
async function fetchNewApiTokenUsage(context) {
|
|
1498
1468
|
const json = await requestJson(joinUrl(serviceRoot(context.baseUrl), "/api/usage/token/"), {
|
|
1499
1469
|
key: context.key,
|
|
1500
|
-
name: "
|
|
1470
|
+
name: "New API token usage"
|
|
1501
1471
|
});
|
|
1502
1472
|
const root = usageRoot(json);
|
|
1503
1473
|
const quota = pickNumber(root, ["quota", "limit", "total_quota", "totalQuota"]);
|
|
@@ -1523,66 +1493,37 @@ async function fetchNewApiTokenUsage(context) {
|
|
|
1523
1493
|
};
|
|
1524
1494
|
return usageResult(context, "newapi-token", await formatNewApiTokenLine(context.label, json), normalized);
|
|
1525
1495
|
}
|
|
1526
|
-
async function
|
|
1527
|
-
const
|
|
1528
|
-
const
|
|
1529
|
-
const json = await requestJson(joinUrl(serviceRoot(context.baseUrl), "/api/user/self"), {
|
|
1496
|
+
async function fetchOneApiBillingUsage(context) {
|
|
1497
|
+
const base = serviceRoot(context.baseUrl);
|
|
1498
|
+
const subscription = await requestJson(joinUrl(base, "/v1/dashboard/billing/subscription"), {
|
|
1530
1499
|
key: context.key,
|
|
1531
|
-
name: "
|
|
1532
|
-
headers: await panelUserHeaders()
|
|
1500
|
+
name: "One API billing subscription"
|
|
1533
1501
|
});
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1502
|
+
const usage = await requestJson(joinUrl(base, "/v1/dashboard/billing/usage"), {
|
|
1503
|
+
key: context.key,
|
|
1504
|
+
name: "One API billing usage"
|
|
1505
|
+
});
|
|
1506
|
+
const limit = pickNumber(subscription, ["hard_limit_usd", "hardLimitUsd"]);
|
|
1507
|
+
const usageCents = pickNumber(usage, ["total_usage", "totalUsage"]);
|
|
1508
|
+
if (limit === void 0 || usageCents === void 0) {
|
|
1509
|
+
throw new Error("One API billing payload has no quota fields");
|
|
1542
1510
|
}
|
|
1543
|
-
const
|
|
1544
|
-
const quota = pickNumber(root, ["quota"]);
|
|
1545
|
-
const used = pickNumber(root, ["used_quota", "usedQuota"]);
|
|
1546
|
-
const remaining = panelQuotaLooksRemaining(kind) ? quota : quota === void 0 || used === void 0 ? quota : Math.max(0, quota - used);
|
|
1547
|
-
const total = panelQuotaLooksRemaining(kind) ? quota === void 0 || used === void 0 ? quota : quota + used : quota;
|
|
1511
|
+
const used = usageCents / 100;
|
|
1548
1512
|
const normalized = {
|
|
1549
1513
|
mode: "quota_limited",
|
|
1550
1514
|
quota: {
|
|
1551
|
-
limit
|
|
1552
|
-
used
|
|
1553
|
-
remaining:
|
|
1515
|
+
limit,
|
|
1516
|
+
used,
|
|
1517
|
+
remaining: Math.max(0, limit - used)
|
|
1554
1518
|
},
|
|
1555
1519
|
unit: "USD",
|
|
1556
|
-
source: "
|
|
1557
|
-
raw:
|
|
1558
|
-
};
|
|
1559
|
-
return usageResult(
|
|
1560
|
-
context,
|
|
1561
|
-
"panel-user-self",
|
|
1562
|
-
await formatPanelUserSelfLine(context.label, json, kind),
|
|
1563
|
-
normalized
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
async function fetchSub2ApiAuthMeUsage(context) {
|
|
1567
|
-
const json = await requestJson(joinUrl(serviceRoot(context.baseUrl), "/api/v1/auth/me"), {
|
|
1568
|
-
key: context.key,
|
|
1569
|
-
name: "Sub2API auth/me"
|
|
1570
|
-
});
|
|
1571
|
-
const root = usageRoot(json);
|
|
1572
|
-
const balance = pickNumber(root, ["balance"]);
|
|
1573
|
-
if (balance === void 0) throw new Error("Sub2API auth/me payload has no balance field");
|
|
1574
|
-
const normalized = {
|
|
1575
|
-
mode: "unrestricted",
|
|
1576
|
-
planName: root?.username || root?.email || context.label || "Sub2API",
|
|
1577
|
-
balance,
|
|
1578
|
-
unit: "USD",
|
|
1579
|
-
source: "sub2api-auth-me",
|
|
1580
|
-
raw: json
|
|
1520
|
+
source: "oneapi-billing",
|
|
1521
|
+
raw: { subscription, usage }
|
|
1581
1522
|
};
|
|
1582
1523
|
return usageResult(
|
|
1583
1524
|
context,
|
|
1584
|
-
"
|
|
1585
|
-
|
|
1525
|
+
"oneapi-billing",
|
|
1526
|
+
formatOneApiBillingLine(limit, used),
|
|
1586
1527
|
normalized
|
|
1587
1528
|
);
|
|
1588
1529
|
}
|
|
@@ -1610,20 +1551,15 @@ var USAGE_ROUTES = {
|
|
|
1610
1551
|
path: "/v1/usage",
|
|
1611
1552
|
run: fetchV1Usage
|
|
1612
1553
|
},
|
|
1613
|
-
"sub2api-auth-me": {
|
|
1614
|
-
id: "sub2api-auth-me",
|
|
1615
|
-
path: "/api/v1/auth/me",
|
|
1616
|
-
run: fetchSub2ApiAuthMeUsage
|
|
1617
|
-
},
|
|
1618
1554
|
"newapi-token": {
|
|
1619
1555
|
id: "newapi-token",
|
|
1620
1556
|
path: "/api/usage/token/",
|
|
1621
1557
|
run: fetchNewApiTokenUsage
|
|
1622
1558
|
},
|
|
1623
|
-
"
|
|
1624
|
-
id: "
|
|
1625
|
-
path: "/
|
|
1626
|
-
run:
|
|
1559
|
+
"oneapi-billing": {
|
|
1560
|
+
id: "oneapi-billing",
|
|
1561
|
+
path: "/v1/dashboard/billing/subscription",
|
|
1562
|
+
run: fetchOneApiBillingUsage
|
|
1627
1563
|
},
|
|
1628
1564
|
"openrouter": {
|
|
1629
1565
|
id: "openrouter",
|
|
@@ -1697,23 +1633,16 @@ async function routeRegistry() {
|
|
|
1697
1633
|
async function usageRouteIds(context) {
|
|
1698
1634
|
const preset = await usagePreset();
|
|
1699
1635
|
const routes = {
|
|
1700
|
-
"sub2api": ["v1-usage"
|
|
1636
|
+
"sub2api": ["v1-usage"],
|
|
1701
1637
|
"openai-compatible": ["v1-usage"],
|
|
1702
|
-
"new-api": ["newapi-token"
|
|
1703
|
-
"one-api": ["
|
|
1704
|
-
"onehub": ["newapi-token", "panel-user-self"],
|
|
1705
|
-
"one-hub": ["newapi-token", "panel-user-self"],
|
|
1706
|
-
"donehub": ["newapi-token", "panel-user-self"],
|
|
1707
|
-
"done-hub": ["newapi-token", "panel-user-self"],
|
|
1708
|
-
"veloera": ["panel-user-self", "newapi-token"],
|
|
1709
|
-
"anyrouter": ["newapi-token", "panel-user-self", "v1-usage"],
|
|
1710
|
-
"agentrouter": ["newapi-token", "panel-user-self", "v1-usage"],
|
|
1638
|
+
"new-api": ["newapi-token"],
|
|
1639
|
+
"one-api": ["oneapi-billing"],
|
|
1711
1640
|
"openrouter": ["openrouter"]
|
|
1712
1641
|
};
|
|
1713
1642
|
if (routes[preset]) return routes[preset];
|
|
1714
1643
|
if (preset !== "auto") return (await routeRegistry())[preset] ? [preset] : [];
|
|
1715
1644
|
const customIds = (await customRoutes()).map((route) => route.id);
|
|
1716
|
-
const builtinIds = hostIncludes(context.baseUrl, "openrouter.ai") ? ["openrouter"] : ["v1-usage", "
|
|
1645
|
+
const builtinIds = hostIncludes(context.baseUrl, "openrouter.ai") ? ["openrouter"] : ["v1-usage", "newapi-token", "oneapi-billing"];
|
|
1717
1646
|
return [.../* @__PURE__ */ new Set([...customIds, ...builtinIds])];
|
|
1718
1647
|
}
|
|
1719
1648
|
async function cachedUsageRoute(context, registry) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Local CLI used by the managed at-usage skills.
|
|
3
3
|
|
|
4
4
|
import { queryAgentProviderUsage } from "./core.mjs";
|
|
5
|
+
import { debugLog } from "./lib/config.mjs";
|
|
5
6
|
|
|
6
7
|
function parseAgent(argv) {
|
|
7
8
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -20,8 +21,10 @@ if (agent !== "claude" && agent !== "codex") {
|
|
|
20
21
|
try {
|
|
21
22
|
const result = await queryAgentProviderUsage(agent);
|
|
22
23
|
if (result?.text) process.stdout.write(`${result.text}\n`);
|
|
23
|
-
} catch {
|
|
24
|
+
} catch (error) {
|
|
24
25
|
// Usage is informational. Leave stdout empty so the skill can report the
|
|
25
|
-
// provider as unavailable without exposing endpoint or credential details
|
|
26
|
+
// provider as unavailable without exposing endpoint or credential details;
|
|
27
|
+
// with providerUsage.debug the error lands in logs/usage-debug.log.
|
|
28
|
+
await debugLog({ source: "cli", agent, error: error?.message || String(error) }).catch(() => {});
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Paths, constants, config.jsonc access, and debug logging shared by the
|
|
2
2
|
// usage runtime modules.
|
|
3
3
|
|
|
4
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
4
|
+
import { readFile, writeFile, mkdir, open, stat } from "node:fs/promises";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -47,10 +47,31 @@ export async function agentConfig() {
|
|
|
47
47
|
return agentConfigCache;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const DEBUG_LOG_MAX_BYTES = 256 * 1024;
|
|
51
|
+
const DEBUG_LOG_KEEP_BYTES = 128 * 1024;
|
|
52
|
+
|
|
53
|
+
async function rotateDebugLogIfNeeded() {
|
|
54
|
+
try {
|
|
55
|
+
const { size } = await stat(DEBUG_PATH);
|
|
56
|
+
if (size <= DEBUG_LOG_MAX_BYTES) return;
|
|
57
|
+
const handle = await open(DEBUG_PATH, "r");
|
|
58
|
+
try {
|
|
59
|
+
const buffer = Buffer.alloc(DEBUG_LOG_KEEP_BYTES);
|
|
60
|
+
await handle.read(buffer, 0, DEBUG_LOG_KEEP_BYTES, size - DEBUG_LOG_KEEP_BYTES);
|
|
61
|
+
await writeFile(DEBUG_PATH, buffer.toString("utf8").replace(/^[^\n]*\n?/, ""));
|
|
62
|
+
} finally {
|
|
63
|
+
await handle.close();
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Rotation is best-effort; appending must not fail because of it.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
export async function debugLog(event) {
|
|
51
71
|
const config = await agentConfig();
|
|
52
72
|
if (process.env.PROVIDER_USAGE_DEBUG !== "1" && config.debug !== true) return;
|
|
53
73
|
await mkdir(dirname(DEBUG_PATH), { recursive: true });
|
|
74
|
+
await rotateDebugLogIfNeeded();
|
|
54
75
|
const line = JSON.stringify({
|
|
55
76
|
at: new Date().toISOString(),
|
|
56
77
|
...event,
|
|
@@ -61,8 +82,8 @@ export async function debugLog(event) {
|
|
|
61
82
|
export async function providerUsageDays() {
|
|
62
83
|
const config = await agentConfig();
|
|
63
84
|
const value = Number(process.env.PROVIDER_USAGE_DAYS || config.days || DEFAULT_USAGE_DAYS);
|
|
64
|
-
if (!Number.isInteger(value) || value <= 0
|
|
65
|
-
return value;
|
|
85
|
+
if (!Number.isInteger(value) || value <= 0) return DEFAULT_USAGE_DAYS;
|
|
86
|
+
return Math.min(value, MAX_USAGE_DAYS);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
export async function usagePreset() {
|
|
@@ -70,28 +91,6 @@ export async function usagePreset() {
|
|
|
70
91
|
return String(process.env.PROVIDER_USAGE_PRESET || config.preset || "auto").toLowerCase();
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
export async function panelUserId() {
|
|
74
|
-
const config = await agentConfig();
|
|
75
|
-
const raw = process.env.PROVIDER_USAGE_USER_ID || config.userId || "";
|
|
76
|
-
const parsed = Number.parseInt(String(raw), 10);
|
|
77
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function panelUserHeaders() {
|
|
81
|
-
const userId = await panelUserId();
|
|
82
|
-
if (!userId) return {};
|
|
83
|
-
const value = String(userId);
|
|
84
|
-
return {
|
|
85
|
-
"New-API-User": value,
|
|
86
|
-
"Veloera-User": value,
|
|
87
|
-
"voapi-user": value,
|
|
88
|
-
"User-id": value,
|
|
89
|
-
"X-User-Id": value,
|
|
90
|
-
"Rix-Api-User": value,
|
|
91
|
-
"neo-api-user": value,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
94
|
// Passive callers (the codex hook fires per prompt; several sessions may run
|
|
96
95
|
// at once) reuse a fresh snapshot instead of hitting the gateway every time.
|
|
97
96
|
// Same knob the statusline uses; 0 disables.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Turns gateway payloads into the compact one-line usage message.
|
|
2
2
|
|
|
3
|
-
import { newApiQuotaScale, providerUsageDays
|
|
3
|
+
import { newApiQuotaScale, providerUsageDays } from "./config.mjs";
|
|
4
4
|
|
|
5
5
|
export function pickNumber(obj, keys) {
|
|
6
6
|
for (const key of keys) {
|
|
@@ -159,45 +159,8 @@ export function formatOpenRouterLine(label, data) {
|
|
|
159
159
|
return parts.join(" | ");
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
export function
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function panelQuotaLooksRemaining(kind) {
|
|
167
|
-
return ["new-api", "anyrouter", "agentrouter", "done-hub", "donehub"].includes(kind);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export async function formatPanelUserSelfLine(label, data, kind) {
|
|
171
|
-
const root = usageRoot(data);
|
|
172
|
-
const scale = panelQuotaScale(kind);
|
|
173
|
-
const quota = pickNumber(root, ["quota"]);
|
|
174
|
-
const used = pickNumber(root, ["used_quota", "usedQuota"]);
|
|
175
|
-
const todayIncome = pickNumber(root, ["today_income", "todayIncome"]);
|
|
176
|
-
const todayUsed = pickNumber(root, ["today_quota_consumption", "todayQuotaConsumption"]);
|
|
177
|
-
|
|
178
|
-
if (quota === undefined && used === undefined) {
|
|
179
|
-
throw new Error("panel /api/user/self payload has no quota fields");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const quotaUsd = quota === undefined ? undefined : quota / scale;
|
|
183
|
-
const usedUsd = used === undefined ? undefined : used / scale;
|
|
184
|
-
const remainingUsd = panelQuotaLooksRemaining(kind)
|
|
185
|
-
? quotaUsd
|
|
186
|
-
: (quotaUsd === undefined || usedUsd === undefined ? quotaUsd : Math.max(0, quotaUsd - usedUsd));
|
|
187
|
-
const totalUsd = panelQuotaLooksRemaining(kind)
|
|
188
|
-
? (quotaUsd === undefined || usedUsd === undefined ? quotaUsd : quotaUsd + usedUsd)
|
|
189
|
-
: quotaUsd;
|
|
190
|
-
|
|
191
|
-
const parts = usageParts();
|
|
192
|
-
if (remainingUsd !== undefined) parts.push(`balance ${formatMoney(remainingUsd)}`);
|
|
193
|
-
if (usedUsd !== undefined && totalUsd !== undefined) {
|
|
194
|
-
parts.push(`used ${formatMoney(usedUsd)}/${formatMoney(totalUsd)}`);
|
|
195
|
-
} else if (usedUsd !== undefined) {
|
|
196
|
-
parts.push(`used ${formatMoney(usedUsd)}`);
|
|
197
|
-
}
|
|
198
|
-
if (todayUsed !== undefined) parts.push(`today ${formatMoney(todayUsed / scale)}`);
|
|
199
|
-
if (todayIncome !== undefined) parts.push(`income ${formatMoney(todayIncome / scale)}`);
|
|
200
|
-
return parts.join(" | ");
|
|
162
|
+
export function formatOneApiBillingLine(limit, used) {
|
|
163
|
+
return `API | balance ${formatMoney(Math.max(0, limit - used))} | used ${formatMoney(used)}/${formatMoney(limit)}`;
|
|
201
164
|
}
|
|
202
165
|
|
|
203
166
|
function formatQuotaLimitedLine(label, root) {
|
|
@@ -126,11 +126,11 @@ function mergeSetCookiePairs(cookieHeader, setCookieHeaders) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export async function requestJson(url, options = {}) {
|
|
129
|
-
const { key = "", headers = {}, name = "usage" } = options;
|
|
129
|
+
const { key = "", headers = {}, name = "usage", timeoutMs = REQUEST_TIMEOUT_MS } = options;
|
|
130
130
|
let cookieHeader = "";
|
|
131
131
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
132
132
|
const controller = new AbortController();
|
|
133
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
133
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
134
134
|
const response = await fetch(url, {
|
|
135
135
|
headers: {
|
|
136
136
|
accept: "application/json",
|