@neuralnomads/codenomad-dev 0.19.0-dev-20260827-14586cb3 → 0.19.0-dev-20260829-56156827
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.
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { fetch } from "undici";
|
|
2
2
|
import { compareVersionStrings, stripTagPrefix } from "./release-monitor";
|
|
3
3
|
const DEFAULT_POLL_INTERVAL_MS = 15 * 60 * 1000;
|
|
4
|
+
export function matchesDevReleaseChannel(tag, currentVersion) {
|
|
5
|
+
const v2 = "-dev-v2-";
|
|
6
|
+
return currentVersion.includes(v2) ? tag.includes(v2) : tag.includes("-dev-") && !tag.includes(v2);
|
|
7
|
+
}
|
|
4
8
|
export function startDevReleaseMonitor(options) {
|
|
5
9
|
let stopped = false;
|
|
6
10
|
let timer = null;
|
|
@@ -49,7 +53,10 @@ async function fetchLatestPrerelease(args) {
|
|
|
49
53
|
throw new Error(`GitHub releases API responded with ${response.status}`);
|
|
50
54
|
}
|
|
51
55
|
const list = (await response.json());
|
|
52
|
-
const latest = list.find((
|
|
56
|
+
const latest = list.find((release) => {
|
|
57
|
+
const tag = release?.tag_name || release?.name || "";
|
|
58
|
+
return release?.prerelease === true && release.draft !== true && matchesDevReleaseChannel(tag, args.currentVersion);
|
|
59
|
+
});
|
|
53
60
|
if (!latest) {
|
|
54
61
|
return null;
|
|
55
62
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { matchesDevReleaseChannel } from "./dev-release-monitor";
|
|
4
|
+
test("dev prereleases stay within their V1 or V2 channel", () => {
|
|
5
|
+
const v1 = "v0.19.0-dev-20260825-12345678";
|
|
6
|
+
const v2 = "v0.20.0-dev-v2-20260825-abcdef12";
|
|
7
|
+
assert.equal(matchesDevReleaseChannel(v1, v1), true);
|
|
8
|
+
assert.equal(matchesDevReleaseChannel(v2, v1), false);
|
|
9
|
+
assert.equal(matchesDevReleaseChannel(v2, v2), true);
|
|
10
|
+
assert.equal(matchesDevReleaseChannel(v1, v2), false);
|
|
11
|
+
});
|
|
@@ -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
|
+
});
|