@neuralnomads/codenomad-dev 0.19.0-dev-20260828-0fbf7b25 → 0.19.0-dev-20260830-e4cd6d6f
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.
|
@@ -96,6 +96,12 @@ const openRouter = {
|
|
|
96
96
|
});
|
|
97
97
|
},
|
|
98
98
|
};
|
|
99
|
+
const CREDIT_LIMIT_TYPE = "CREDIT_LIMIT";
|
|
100
|
+
const UNIT_SECONDS = { 3: 3_600, 6: 604_800 };
|
|
101
|
+
function unitWindowSeconds(unit, count) {
|
|
102
|
+
const unitSeconds = typeof unit === "number" ? UNIT_SECONDS[unit] : undefined;
|
|
103
|
+
return unitSeconds !== undefined && count !== null ? unitSeconds * count : null;
|
|
104
|
+
}
|
|
99
105
|
function createTokenLimitProvider(input) {
|
|
100
106
|
return {
|
|
101
107
|
id: input.id,
|
|
@@ -109,16 +115,29 @@ function createTokenLimitProvider(input) {
|
|
|
109
115
|
const payload = await fetchJson(input.url, { headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" } });
|
|
110
116
|
const windows = {};
|
|
111
117
|
for (const limit of Array.isArray(payload?.data?.limits) ? payload.data.limits : []) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
118
|
+
const number = toNumber(limit?.number);
|
|
119
|
+
if (limit && (limit.type === "TOKENS_LIMIT" || limit.type === "TIME_LIMIT")) {
|
|
120
|
+
const seconds = limit.type === "TIME_LIMIT" ? 30 * 86_400 : unitWindowSeconds(limit.unit, number);
|
|
121
|
+
const label = limit.type === "TIME_LIMIT" ? "mcp-tools" : resolveWindowLabel(seconds);
|
|
122
|
+
windows[label] = toUsageWindow({
|
|
123
|
+
usedPercent: toNumber(limit.percentage),
|
|
124
|
+
windowSeconds: seconds,
|
|
125
|
+
resetAt: toTimestamp(limit.nextResetTime),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else if (limit && limit.type === CREDIT_LIMIT_TYPE) {
|
|
129
|
+
const usedPercent = toNumber(limit.percentage);
|
|
130
|
+
const remaining = toNumber(limit.remaining);
|
|
131
|
+
const allowance = toNumber(limit.usage);
|
|
132
|
+
if (usedPercent === null && (remaining === null || allowance === null))
|
|
133
|
+
continue;
|
|
134
|
+
const seconds = unitWindowSeconds(limit.unit, number);
|
|
135
|
+
windows[resolveWindowLabel(seconds)] = toUsageWindow({
|
|
136
|
+
usedPercent: usedPercent ?? (allowance !== null && allowance > 0 && remaining !== null ? ((allowance - remaining) / allowance) * 100 : null),
|
|
137
|
+
windowSeconds: seconds,
|
|
138
|
+
resetAt: toTimestamp(limit.nextResetTime),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
122
141
|
}
|
|
123
142
|
return { windows };
|
|
124
143
|
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { apiKeyProviders } from "./api-key";
|
|
7
|
+
const originalAuthFile = process.env.OPENCODE_AUTH_FILE;
|
|
8
|
+
const createdAuthDirs = [];
|
|
9
|
+
test.before(() => {
|
|
10
|
+
const authDir = fs.mkdtempSync(path.join(os.tmpdir(), "codenomad-usage-auth-"));
|
|
11
|
+
createdAuthDirs.push(authDir);
|
|
12
|
+
fs.writeFileSync(path.join(authDir, "auth.json"), JSON.stringify({ "zai-coding-plan": { type: "api", key: "test-zai-key" }, "zhipuai-coding-plan": { type: "api", key: "test-zhipu-key" } }));
|
|
13
|
+
process.env.OPENCODE_AUTH_FILE = path.join(authDir, "auth.json");
|
|
14
|
+
});
|
|
15
|
+
test.after(() => {
|
|
16
|
+
if (originalAuthFile === undefined)
|
|
17
|
+
delete process.env.OPENCODE_AUTH_FILE;
|
|
18
|
+
else
|
|
19
|
+
process.env.OPENCODE_AUTH_FILE = originalAuthFile;
|
|
20
|
+
const tmpRoot = fs.realpathSync(os.tmpdir());
|
|
21
|
+
for (const dir of createdAuthDirs) {
|
|
22
|
+
// Containment guard: never recurse outside the temp root we created.
|
|
23
|
+
if (fs.realpathSync(path.dirname(dir)) === tmpRoot && path.basename(dir).startsWith("codenomad-usage-auth-")) {
|
|
24
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const CREDIT_RESET_AT = 1787849891189;
|
|
29
|
+
const TOKENS_RESET_AT = 1787950772998;
|
|
30
|
+
function respondWith(payload) {
|
|
31
|
+
return new Response(JSON.stringify(payload), { status: 200, headers: { "Content-Type": "application/json" } });
|
|
32
|
+
}
|
|
33
|
+
async function withFetchStub(payload, run) {
|
|
34
|
+
const original = globalThis.fetch;
|
|
35
|
+
let requestUrl;
|
|
36
|
+
globalThis.fetch = (async (input) => {
|
|
37
|
+
requestUrl = String(input);
|
|
38
|
+
return respondWith(payload);
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
return { result: await run(), requestUrl };
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
globalThis.fetch = original;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const zai = apiKeyProviders.find((provider) => provider.id === "zai-coding-plan");
|
|
48
|
+
const zhipu = apiKeyProviders.find((provider) => provider.id === "zhipuai-coding-plan");
|
|
49
|
+
test("parses credit-based limits into usage windows", async () => {
|
|
50
|
+
const { result } = await withFetchStub({
|
|
51
|
+
code: 200,
|
|
52
|
+
msg: "Operation successful",
|
|
53
|
+
success: true,
|
|
54
|
+
data: {
|
|
55
|
+
level: "lite",
|
|
56
|
+
limits: [
|
|
57
|
+
{ type: "CREDIT_LIMIT", unit: 3, number: 5, usage: 2000, currentValue: 157, remaining: 1842, percentage: 7, nextResetTime: CREDIT_RESET_AT },
|
|
58
|
+
{ type: "CREDIT_LIMIT", unit: 6, number: 1, usage: 10000, currentValue: 157, remaining: 9843, percentage: 1, nextResetTime: TOKENS_RESET_AT },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
}, () => zai.fetchQuota());
|
|
62
|
+
assert.equal(result.ok, true);
|
|
63
|
+
const windows = result.usage.windows;
|
|
64
|
+
assert.equal(windows["5h"]?.usedPercent, 7);
|
|
65
|
+
assert.equal(windows["5h"]?.windowSeconds, 18_000);
|
|
66
|
+
assert.equal(windows["5h"]?.resetAt, CREDIT_RESET_AT);
|
|
67
|
+
assert.equal(windows["weekly"]?.usedPercent, 1);
|
|
68
|
+
assert.equal(windows["weekly"]?.windowSeconds, 604_800);
|
|
69
|
+
assert.equal("valueLabel" in (windows["weekly"] ?? {}), false);
|
|
70
|
+
});
|
|
71
|
+
test("derives usedPercent when the credit payload omits percentage", async () => {
|
|
72
|
+
const { result } = await withFetchStub({
|
|
73
|
+
data: { limits: [{ type: "CREDIT_LIMIT", unit: 6, number: 1, usage: 2000, currentValue: 158, remaining: 1842, nextResetTime: CREDIT_RESET_AT }] },
|
|
74
|
+
}, () => zai.fetchQuota());
|
|
75
|
+
const window = result.usage.windows["weekly"];
|
|
76
|
+
// (2000 - 1842) / 2000
|
|
77
|
+
assert.ok(Math.abs((window?.usedPercent ?? 0) - 7.9) < 0.001);
|
|
78
|
+
assert.equal(window?.windowSeconds, 604_800);
|
|
79
|
+
});
|
|
80
|
+
test("coerces string quota fields and skips unusable or unknown entries without throwing", async () => {
|
|
81
|
+
const { result } = await withFetchStub({
|
|
82
|
+
data: {
|
|
83
|
+
limits: [
|
|
84
|
+
null,
|
|
85
|
+
{ type: "UNKNOWN_LIMIT" },
|
|
86
|
+
{ type: "PROMO_LIMIT", percentage: 55, remaining: 10, usage: 20 },
|
|
87
|
+
{ type: "CREDIT_LIMIT", unit: 6, number: 1, usage: "10000", remaining: "9800", percentage: "2", nextResetTime: TOKENS_RESET_AT },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
}, () => zai.fetchQuota());
|
|
91
|
+
assert.equal(result.ok, true);
|
|
92
|
+
const windows = result.usage.windows;
|
|
93
|
+
assert.equal(Object.keys(windows).length, 1);
|
|
94
|
+
assert.equal(windows["weekly"]?.usedPercent, 2);
|
|
95
|
+
});
|
|
96
|
+
test("keeps legacy token and time windows intact for the bigmodel endpoint", async () => {
|
|
97
|
+
const { result, requestUrl } = await withFetchStub({
|
|
98
|
+
data: {
|
|
99
|
+
limits: [
|
|
100
|
+
{ type: "TOKENS_LIMIT", unit: 3, number: 1, percentage: 42, nextResetTime: TOKENS_RESET_AT },
|
|
101
|
+
{ type: "TIME_LIMIT", number: 1, percentage: 11 },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
}, () => zhipu.fetchQuota());
|
|
105
|
+
assert.equal(requestUrl, "https://open.bigmodel.cn/api/monitor/usage/quota/limit");
|
|
106
|
+
assert.equal(result.ok, true);
|
|
107
|
+
const windows = result.usage.windows;
|
|
108
|
+
assert.equal(windows["1h"]?.usedPercent, 42);
|
|
109
|
+
assert.equal(windows["mcp-tools"]?.usedPercent, 11);
|
|
110
|
+
});
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/assets/git-diff-vendor-CYS3ApzA.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="/assets/monaco-viewer-NKl8kwH2.js">
|
|
32
32
|
<link rel="modulepreload" crossorigin href="/assets/index-DzWXCPie.js">
|
|
33
|
-
<link rel="stylesheet" crossorigin href="/assets/index-By9pgzQk.css">
|
|
34
33
|
<link rel="stylesheet" crossorigin href="/assets/git-diff-vendor-HAZkIolJ.css">
|
|
34
|
+
<link rel="stylesheet" crossorigin href="/assets/index-By9pgzQk.css">
|
|
35
35
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script>
|
|
36
36
|
<meta name="theme-color" content="#1a1a1a">
|
|
37
37
|
<link rel="icon" href="/favicon.ico" sizes="48x48">
|
package/public/loading.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/assets/git-diff-vendor-CYS3ApzA.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/assets/monaco-viewer-NKl8kwH2.js">
|
|
22
22
|
<link rel="modulepreload" crossorigin href="/assets/index-DzWXCPie.js">
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/assets/git-diff-vendor-HAZkIolJ.css">
|
|
24
23
|
<link rel="stylesheet" crossorigin href="/assets/index-By9pgzQk.css">
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/git-diff-vendor-HAZkIolJ.css">
|
|
25
25
|
<link rel="stylesheet" crossorigin href="/assets/loading-CmEVQgyj.css">
|
|
26
26
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script>
|
|
27
27
|
<meta name="theme-color" content="#1a1a1a">
|