@magpie-community/opencode-workbuddy-auth 0.2.10 → 0.2.12
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 +20 -3
- package/index.mjs +134 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -344,13 +344,24 @@ client reads. The daily Free Plan slice isn't counted: it ends at the same
|
|
|
344
344
|
midnight on every account. magpie's smart routing spends the account whose
|
|
345
345
|
credits run out first.
|
|
346
346
|
|
|
347
|
+
The plan's own credits and the rest are two windows. "Credits" is the
|
|
348
|
+
packages whose credits come back each cycle, as WorkBuddy's client lists
|
|
349
|
+
them (Pro monthly or yearly, youth, advanced, flagship, the plan's bonus,
|
|
350
|
+
the free plan's monthly base, the Pro trials, or the subscription the
|
|
351
|
+
summary names), with the plan's cycle, so its pace line is the month's.
|
|
352
|
+
"Extra credits" is the packages that only lapse: gifted and activity
|
|
353
|
+
credits (赠送积分) and bought credit packs. Only one of the two stops the
|
|
354
|
+
account, since WorkBuddy serves it while any credits are left: the extra
|
|
355
|
+
window is set aside until the plan's credits are spent. A summary with
|
|
356
|
+
only one kind keeps the one "Credits" window (akic404 on Discord).
|
|
357
|
+
|
|
347
358
|
An enterprise (team) account's resource summary lists no packages. Its
|
|
348
359
|
credits are read from the enterprise meter
|
|
349
360
|
(`/billing/meter/get-enterprise-user-usage`), the one WorkBuddy's own usage
|
|
350
361
|
page reads: the cycle's credits used of its limit, and when the cycle
|
|
351
362
|
resets (magpie #1904).
|
|
352
363
|
|
|
353
|
-
## The free model's daily window (
|
|
364
|
+
## The free model's daily window (both builds)
|
|
354
365
|
|
|
355
366
|
The credits above are not the only limit. `deepseek-v4.1-flash` is served at
|
|
356
367
|
`x0.00` and costs the plan nothing, yet WorkBuddy AI refuses it long before
|
|
@@ -420,8 +431,14 @@ What is measured, and what is easy to read wrongly:
|
|
|
420
431
|
rather than +08:00 guessed.
|
|
421
432
|
|
|
422
433
|
A window that has lapsed reports nothing rather than 0%: nothing is
|
|
423
|
-
exhausted, and the next request opens a fresh one. The China build
|
|
424
|
-
|
|
434
|
+
exhausted, and the next request opens a fresh one. The China build
|
|
435
|
+
(`copilot.tencent.com`) carries the same window: two Free-plan accounts
|
|
436
|
+
refused on 2026-11-02 at 200,389,043 and 200,485,923 tokens, so its cap is
|
|
437
|
+
measured at about 2e8 — and its refusals name the reset in Chinese
|
|
438
|
+
(`将在 … UTC+8 重置`), which reads as an edge the same way. Its card stops
|
|
439
|
+
at a **soft cap of 1.9e8**, 5% short of the measured one, so routing rests
|
|
440
|
+
the model before the vendor refuses it; the headroom absorbs the account's
|
|
441
|
+
spending on other machines, which this ledger cannot see.
|
|
425
442
|
|
|
426
443
|
## When free models work but a credit model does not
|
|
427
444
|
|
package/index.mjs
CHANGED
|
@@ -2210,8 +2210,9 @@ function capacity(v) {
|
|
|
2210
2210
|
// compact is a count as magpie says one: whole, else to two places.
|
|
2211
2211
|
const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/0+$/, "").replace(/\.$/, ""))
|
|
2212
2212
|
|
|
2213
|
-
// The window WorkBuddy
|
|
2214
|
-
// meter does not show
|
|
2213
|
+
// The window WorkBuddy rate limits deepseek-v4.1-flash by, which its credit
|
|
2214
|
+
// meter does not show: WorkBuddy AI measured at about 1e8 over ten days, the
|
|
2215
|
+
// China build at about 2e8 on 2026-11-02 (see FREE_WINDOW_CN).
|
|
2215
2216
|
//
|
|
2216
2217
|
// A free account's usage() reports only the credits window, so a plan with
|
|
2217
2218
|
// 160 credits untouched reads 0% while chats to deepseek-v4.1-flash are
|
|
@@ -2223,6 +2224,11 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
|
|
|
2223
2224
|
// reset at 2026-10-20 19:47:41 UTC+8, alternatively, you can switch to
|
|
2224
2225
|
// the other models to continue using it.
|
|
2225
2226
|
//
|
|
2227
|
+
// The China build (copilot.tencent.com) says the same in its own words:
|
|
2228
|
+
//
|
|
2229
|
+
// 429 WorkBuddy: 您的使用量已超出频率限制,将在 2026-11-02 19:17:02
|
|
2230
|
+
// UTC+8 重置,您也可以切换其他模型继续使用。
|
|
2231
|
+
//
|
|
2226
2232
|
// Measured on a free plan over ten days (one account, deepseek-v4.1-flash
|
|
2227
2233
|
// only), counting in + cache_read + out:
|
|
2228
2234
|
//
|
|
@@ -2244,6 +2250,12 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
|
|
|
2244
2250
|
// were seen between a reset and the next request, each opening the new
|
|
2245
2251
|
// window.
|
|
2246
2252
|
//
|
|
2253
|
+
// The China build was measured after that, on 2026-11-02: two Free-plan
|
|
2254
|
+
// accounts on copilot.tencent.com, both spending the machine's ledger only
|
|
2255
|
+
// on this model, were refused at 200,389,043 and 200,485,923 tokens counted
|
|
2256
|
+
// the same way. Its cap sits at about 2e8 — and its refusals name the reset
|
|
2257
|
+
// in Chinese (将在 … UTC+8 重置), which windowReset reads as well.
|
|
2258
|
+
//
|
|
2247
2259
|
// deepseek-v4.1-flash-sg is the same model under its paid id (the product
|
|
2248
2260
|
// config's ModelRateLimitCap.lines[0] names it as paidId to this freeId,
|
|
2249
2261
|
// allowPaidSwitch: true) and draws on the same window: it was refused in
|
|
@@ -2270,7 +2282,18 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
|
|
|
2270
2282
|
// ~/.config/magpie otherwise. No endpoint reports the window; a ledger that
|
|
2271
2283
|
// cannot be read yields no window rather than a made-up one.
|
|
2272
2284
|
|
|
2273
|
-
|
|
2285
|
+
// A window: the build's ledger rows it counts (provider), the cap it reports
|
|
2286
|
+
// against, and how deep its display rounds. Two builds, two windows, two
|
|
2287
|
+
// caps: the same phone number can be signed in to both, and their ledgers
|
|
2288
|
+
// stay apart — one build's spending must not fill the other's window.
|
|
2289
|
+
//
|
|
2290
|
+
// The China cap is a soft one. Measured at about 2e8 (two accounts refusing
|
|
2291
|
+
// at 200.39M and 200.49M), the card stops the model at 1.9e8: 5% of headroom
|
|
2292
|
+
// for the account's spending on other machines (which this ledger cannot
|
|
2293
|
+
// see), so routing rests the model before the vendor's refusal does. A
|
|
2294
|
+
// refusal in force still reads the window full whatever the sum says.
|
|
2295
|
+
const FREE_WINDOW_CN = { provider: "workbuddy", cap: 1.9e8, short: "190M" }
|
|
2296
|
+
const FREE_WINDOW_AI = { provider: "workbuddy-ai", cap: 1e8, short: "100M" }
|
|
2274
2297
|
const FREE_WINDOW_SPAN = 24 * 3600 * 1000
|
|
2275
2298
|
// The lead: how far back of an edge the read goes on. A chain read from a row
|
|
2276
2299
|
// that opens a window needs the window the span makes of the rows above it,
|
|
@@ -2287,8 +2310,6 @@ const FREE_WINDOW_GAP = FREE_WINDOW_SPAN
|
|
|
2287
2310
|
const FREE_WINDOW_MODEL = "deepseek-v4.1-flash"
|
|
2288
2311
|
// both ids, so magpie's exact match finds the -sg one too
|
|
2289
2312
|
const FREE_WINDOW_MODELS = ["deepseek-v4.1-flash", "deepseek-v4.1-flash-sg"]
|
|
2290
|
-
// the provider whose rows count
|
|
2291
|
-
const FREE_WINDOW_PROVIDER = "workbuddy-ai"
|
|
2292
2313
|
|
|
2293
2314
|
// windowOffset is the UTC offset a refusal spells out, in minutes, from
|
|
2294
2315
|
// "UTC+8", "UTC+08:00", "UTC-5" or "GMT+8". null when it spells out none,
|
|
@@ -2304,10 +2325,15 @@ function windowOffset(text) {
|
|
|
2304
2325
|
}
|
|
2305
2326
|
|
|
2306
2327
|
// windowReset is the instant a refusal names, or NaN when it names none or
|
|
2307
|
-
// names it in an offset that cannot be read.
|
|
2328
|
+
// names it in an offset that cannot be read. Both builds name it — WorkBuddy
|
|
2329
|
+
// AI in its English "will reset at …", the China build in its Chinese
|
|
2330
|
+
// "将在 … 重置" — with the time in the same shape, so the two patterns share
|
|
2331
|
+
// the group numbering and the offset below reads the "UTC+8" either way.
|
|
2308
2332
|
function windowReset(text) {
|
|
2309
2333
|
const s = String(text ?? "")
|
|
2310
|
-
const at =
|
|
2334
|
+
const at =
|
|
2335
|
+
/reset at (\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})/.exec(s) ||
|
|
2336
|
+
/将在\s*(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})/.exec(s)
|
|
2311
2337
|
if (!at) return NaN
|
|
2312
2338
|
const off = windowOffset(s)
|
|
2313
2339
|
if (off === null) return NaN
|
|
@@ -2316,17 +2342,18 @@ function windowReset(text) {
|
|
|
2316
2342
|
|
|
2317
2343
|
// freeWindowOf is the window account is in now, from rows (magpie's usage
|
|
2318
2344
|
// records), or null when it has lapsed — the next request opens a new one.
|
|
2319
|
-
// now is passed in so the reading is testable
|
|
2345
|
+
// now is passed in so the reading is testable; win is the build's window
|
|
2346
|
+
// (which build's rows count, what cap they fill), AI by default.
|
|
2320
2347
|
//
|
|
2321
2348
|
// The window is a chain: it begins at the first request answered after the
|
|
2322
2349
|
// previous one ended, so where this window began depends on every window
|
|
2323
2350
|
// before it. A window ends at a refusal's reset when the ledger holds one,
|
|
2324
2351
|
// and otherwise a day after it began. readLedgerTail (which see) stops on
|
|
2325
2352
|
// both, so that these rows read the same as the whole ledger.
|
|
2326
|
-
function freeWindowOf(rows, account, now = Date.now()) {
|
|
2353
|
+
function freeWindowOf(rows, account, now = Date.now(), win = FREE_WINDOW_AI) {
|
|
2327
2354
|
const mine = (Array.isArray(rows) ? rows : []).filter(
|
|
2328
2355
|
(r) =>
|
|
2329
|
-
r?.provider ===
|
|
2356
|
+
r?.provider === win.provider &&
|
|
2330
2357
|
r.providerAccount === account &&
|
|
2331
2358
|
typeof r.model === "string" &&
|
|
2332
2359
|
r.model.startsWith(FREE_WINDOW_MODEL) &&
|
|
@@ -2403,11 +2430,11 @@ function freeWindowOf(rows, account, now = Date.now()) {
|
|
|
2403
2430
|
|
|
2404
2431
|
const card = (amount, resetsAt, refused) => ({
|
|
2405
2432
|
name: "Free model tokens",
|
|
2406
|
-
used: Math.min(100, (100 * amount) /
|
|
2433
|
+
used: Math.min(100, (100 * amount) / win.cap),
|
|
2407
2434
|
amount,
|
|
2408
|
-
limit:
|
|
2435
|
+
limit: win.cap,
|
|
2409
2436
|
unit: "tokens",
|
|
2410
|
-
display: `${tokensShort(amount)} /
|
|
2437
|
+
display: `${tokensShort(amount)} / ${win.short}`,
|
|
2411
2438
|
resetsAt: new Date(resetsAt).toISOString(),
|
|
2412
2439
|
span: FREE_WINDOW_SPAN / 1000,
|
|
2413
2440
|
models: FREE_WINDOW_MODELS.slice(),
|
|
@@ -2425,7 +2452,7 @@ function freeWindowOf(rows, account, now = Date.now()) {
|
|
|
2425
2452
|
if (!Number.isFinite(t) || t <= now || t <= start) continue
|
|
2426
2453
|
if (live === null || t > live) live = t
|
|
2427
2454
|
}
|
|
2428
|
-
if (live !== null) return card(
|
|
2455
|
+
if (live !== null) return card(win.cap, live, true)
|
|
2429
2456
|
|
|
2430
2457
|
return card(used, end, false)
|
|
2431
2458
|
}
|
|
@@ -2493,7 +2520,7 @@ function freeWindowLedger(cwd = process.cwd(), env = process.env, home = homedir
|
|
|
2493
2520
|
// ledger mid-write, the one row holding the free window's start was
|
|
2494
2521
|
// dropped without a word, and the panel showed no window at all until the
|
|
2495
2522
|
// next read happened to succeed.
|
|
2496
|
-
function freeWindowRows(account, now = Date.now(), cwd = process.cwd(), env = process.env, home = homedir()) {
|
|
2523
|
+
function freeWindowRows(account, now = Date.now(), cwd = process.cwd(), env = process.env, home = homedir(), win = FREE_WINDOW_AI) {
|
|
2497
2524
|
if (!account) return null
|
|
2498
2525
|
let found = null
|
|
2499
2526
|
for (const path of freeWindowLedger(cwd, env, home)) {
|
|
@@ -2510,11 +2537,14 @@ function freeWindowRows(account, now = Date.now(), cwd = process.cwd(), env = pr
|
|
|
2510
2537
|
// way this ledger is written; one rewritten in place (a rotation or a
|
|
2511
2538
|
// compaction) moves mtime even when its size lands the same.
|
|
2512
2539
|
const file = path + "|" + st.mtimeMs + ":" + st.size
|
|
2513
|
-
|
|
2540
|
+
// the key carries the account and the build: one account's rows are its
|
|
2541
|
+
// own, and the two builds' chains over one ledger must not answer for
|
|
2542
|
+
// each other (the same phone number can be signed in to both)
|
|
2543
|
+
const key = file + "|" + win.provider + "|" + account
|
|
2514
2544
|
if (ledgerParsed?.key === key) return ledgerParsed.rows
|
|
2515
2545
|
let got
|
|
2516
2546
|
try {
|
|
2517
|
-
got = readLedgerTail(path, st, now, undefined, account)
|
|
2547
|
+
got = readLedgerTail(path, st, now, undefined, account, win)
|
|
2518
2548
|
} catch {
|
|
2519
2549
|
return null
|
|
2520
2550
|
}
|
|
@@ -2574,11 +2604,12 @@ function rowAccount(line) {
|
|
|
2574
2604
|
}
|
|
2575
2605
|
|
|
2576
2606
|
// ofAccount is whether a row belongs to the chain read for account: this
|
|
2577
|
-
// account's own row of the free model, either id. A refusal of
|
|
2578
|
-
// account's, or of a paid model
|
|
2579
|
-
|
|
2607
|
+
// account's own row of the build's free model, either id. A refusal of
|
|
2608
|
+
// another account's, of the other build, or of a paid model says nothing
|
|
2609
|
+
// about where this chain opens.
|
|
2610
|
+
function ofAccount(row, account, win = FREE_WINDOW_AI) {
|
|
2580
2611
|
return (
|
|
2581
|
-
row?.provider ===
|
|
2612
|
+
row?.provider === win.provider &&
|
|
2582
2613
|
row.providerAccount === account &&
|
|
2583
2614
|
typeof row.model === "string" &&
|
|
2584
2615
|
row.model.startsWith(FREE_WINDOW_MODEL)
|
|
@@ -2625,7 +2656,7 @@ function ofAccount(row, account) {
|
|
|
2625
2656
|
// plugin_accounts.go). The label then no longer equals the accountId rows are
|
|
2626
2657
|
// written under, no row is found for this account at all, and the card
|
|
2627
2658
|
// reports no free window.
|
|
2628
|
-
function readLedgerTail(path, st, now, chunks, account) {
|
|
2659
|
+
function readLedgerTail(path, st, now, chunks, account, win = FREE_WINDOW_AI) {
|
|
2629
2660
|
const CHUNK = 1 << 16
|
|
2630
2661
|
const lapsed = now - FREE_WINDOW_SPAN // a read past this holds no window now
|
|
2631
2662
|
const fd = openSync(path, "r")
|
|
@@ -2684,7 +2715,7 @@ function readLedgerTail(path, st, now, chunks, account) {
|
|
|
2684
2715
|
if (!(firstPass && tornEnd && i === lines.length - 1)) bad++
|
|
2685
2716
|
continue
|
|
2686
2717
|
}
|
|
2687
|
-
if (!ofAccount(row, account)) continue
|
|
2718
|
+
if (!ofAccount(row, account, win)) continue
|
|
2688
2719
|
rev.push(row)
|
|
2689
2720
|
chain.push(row)
|
|
2690
2721
|
if (Number(row.status) === 200) answered = true
|
|
@@ -2699,7 +2730,7 @@ function readLedgerTail(path, st, now, chunks, account) {
|
|
|
2699
2730
|
if (!reach || !Number.isFinite(t) || t >= reach) {
|
|
2700
2731
|
try {
|
|
2701
2732
|
const row = JSON.parse(carry)
|
|
2702
|
-
if (ofAccount(row, account)) rev.push(row)
|
|
2733
|
+
if (ofAccount(row, account, win)) rev.push(row)
|
|
2703
2734
|
} catch {
|
|
2704
2735
|
bad++
|
|
2705
2736
|
}
|
|
@@ -2776,20 +2807,49 @@ function pinned(rows, account) {
|
|
|
2776
2807
|
// CycleRemainCapacity at all leaves the window out. TotalCredits is not a
|
|
2777
2808
|
// fallback: an account with no packages at all keeps its unknown allowance,
|
|
2778
2809
|
// as before, so its free models are not skipped.
|
|
2779
|
-
|
|
2810
|
+
//
|
|
2811
|
+
// The plan's own credits and the rest are two windows (akic404 on
|
|
2812
|
+
// Discord): summed, the gifted and activity packages' credits used long
|
|
2813
|
+
// ago put the card at most of its allowance on the first day of the
|
|
2814
|
+
// month, and the plan's pace line said nothing. "Credits" is the packages
|
|
2815
|
+
// whose credits come back each cycle — WorkBuddy's client's own
|
|
2816
|
+
// onlyRefreshCodes (PLAN_CODES), or the subscription the summary names —
|
|
2817
|
+
// and "Extra credits" the ones that only lapse (gift, activity, bought
|
|
2818
|
+
// credit packs, any code the client doesn't list). A summary with only
|
|
2819
|
+
// one kind keeps the one "Credits" window, as before.
|
|
2820
|
+
//
|
|
2821
|
+
// Only one of the two stops the account at a time: WorkBuddy serves it
|
|
2822
|
+
// while any credits are left, so the one set aside is the one whose
|
|
2823
|
+
// running out doesn't stop it — "Extra credits", until the plan's are
|
|
2824
|
+
// spent and extra ones are left.
|
|
2825
|
+
function usageOf(sum, plan, site) {
|
|
2780
2826
|
const out = { plan: plan || (sum?.IsPaidUser ? "Pro" : "Free plan"), windows: [] }
|
|
2781
|
-
|
|
2827
|
+
const ofPlan = planCodes(sum, site)
|
|
2828
|
+
const kinds = { plan: { total: 0, used: 0 }, extra: { total: 0, used: 0 } }
|
|
2829
|
+
let left = 0, bags = 0
|
|
2782
2830
|
for (const p of sum?.Packages ?? []) {
|
|
2783
2831
|
const hasTotal = p?.CycleTotalCapacity != null && p.CycleTotalCapacity !== ""
|
|
2784
2832
|
if (hasTotal) {
|
|
2785
|
-
|
|
2786
|
-
|
|
2833
|
+
const k = kinds[ofPlan(p?.PackageCode) ? "plan" : "extra"]
|
|
2834
|
+
k.total += capacity(p.CycleTotalCapacity)
|
|
2835
|
+
k.used += capacity(p?.CycleUsedCapacity)
|
|
2787
2836
|
} else if (p?.CycleRemainCapacity != null && p.CycleRemainCapacity !== "") {
|
|
2788
2837
|
left += capacity(p.CycleRemainCapacity)
|
|
2789
2838
|
bags++
|
|
2790
2839
|
}
|
|
2791
2840
|
}
|
|
2792
|
-
|
|
2841
|
+
const window = (name, { used, total }) => ({ name, used: (100 * used) / total, display: `${compact(used)} / ${compact(total)}`, amount: used, limit: total, unit: "credits" })
|
|
2842
|
+
const { plan: mine, extra } = kinds
|
|
2843
|
+
if (mine.total > 0 && extra.total > 0) {
|
|
2844
|
+
const credits = window("Credits", mine), more = window("Extra credits", extra)
|
|
2845
|
+
if (mine.used >= mine.total && extra.used < extra.total) credits.aside = true
|
|
2846
|
+
else more.aside = true
|
|
2847
|
+
out.windows.push(credits, more)
|
|
2848
|
+
} else {
|
|
2849
|
+
const total = mine.total + extra.total, used = mine.used + extra.used
|
|
2850
|
+
if (total > 0) out.windows.push(window("Credits", { used, total }))
|
|
2851
|
+
}
|
|
2852
|
+
const total = mine.total + extra.total
|
|
2793
2853
|
if (bags > 0 && (total === 0 || left > 0)) out.windows.push({ name: "Prepaid credits", balance: left, display: `${compact(left)} credits`, unit: "credits" })
|
|
2794
2854
|
return out
|
|
2795
2855
|
}
|
|
@@ -2845,6 +2905,26 @@ const FREE_CODES = [DAILY_CODE, CODE("008", "cfWoLwvjU4"), CODE("035", "ArVxJcGD
|
|
|
2845
2905
|
// lost at the same midnight on every account, so they tell none apart
|
|
2846
2906
|
const SLICE = "4"
|
|
2847
2907
|
|
|
2908
|
+
// PLAN_CODES are the packages whose credits come back each cycle, as
|
|
2909
|
+
// WorkBuddy's client (5.9.x) lists them (onlyRefreshCodes): Pro monthly,
|
|
2910
|
+
// Pro monthly plus, Pro yearly, youth, advanced, flagship, the plan's
|
|
2911
|
+
// bonus (bonus28; bonusIntl, "Plan Bonus Quota"), the international free
|
|
2912
|
+
// plan (freeMonIntl), the Pro trials, and on the China build alone the
|
|
2913
|
+
// free plan's monthly base (freeMon, 套餐基础积分). Every other code only
|
|
2914
|
+
// lapses: gift (006, "Bonus Pack" / Pro trial), activity (007, 赠送积分),
|
|
2915
|
+
// bonus29/30, and the credit packs (009, 036, 038).
|
|
2916
|
+
const PLAN_CODES = [CODE("002", "AkiJS3ZHF5"), CODE("005", "maRGyrHhw1"), CODE("003", "FAnt7lcmRT"), CODE("023", "4xbGhMrE6q"),
|
|
2917
|
+
CODE("026", "BaESVICNoi"), CODE("027", "0FCGVA6vSa"), CODE("028", "NtpWi0jzXs"), CODE("035", "ArVxJcGDsm"),
|
|
2918
|
+
CODE("039", "KRcQj7wUat"), CODE("040", "mi9rCYg46x"), CODE("037", "WxOD3MpI2o")]
|
|
2919
|
+
const FREE_MON = CODE("008", "cfWoLwvjU4")
|
|
2920
|
+
|
|
2921
|
+
// planCodes says whether a package of the summary is the plan's own: one
|
|
2922
|
+
// of PLAN_CODES on that site, or the subscription the summary names.
|
|
2923
|
+
function planCodes(sum, site) {
|
|
2924
|
+
const sub = sum?.SubscriptionPackageCode
|
|
2925
|
+
return (code) => !!code && (code === sub || PLAN_CODES.includes(code) || (!site?.ai && code === FREE_MON))
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2848
2928
|
// beijing is WorkBuddy's own time ("2026-10-13 00:39:27" is UTC+8, as its
|
|
2849
2929
|
// DeductionEndTime for the same package says).
|
|
2850
2930
|
const beijing = (ms) => new Date(ms + 8 * 3600_000).toISOString().slice(0, 19).replace("T", " ")
|
|
@@ -2887,11 +2967,12 @@ function endOf(p) {
|
|
|
2887
2967
|
// packages are lost, and how many (cdredfox on X): smart routing spends
|
|
2888
2968
|
// the account whose credits expire first. Only packages the summary
|
|
2889
2969
|
// counts, with credits left, and not the daily slice; null when none.
|
|
2890
|
-
|
|
2970
|
+
// only narrows it to the packages of one window (the split's, usageOf).
|
|
2971
|
+
function runsOutOf(sum, packages, now = Date.now(), only = () => true) {
|
|
2891
2972
|
const counted = new Set((sum?.Packages ?? []).map((p) => p?.PackageCode))
|
|
2892
2973
|
let at = 0, amount = 0
|
|
2893
2974
|
for (const p of packages ?? []) {
|
|
2894
|
-
if (!counted.has(p?.PackageCode) || p?.PackageCode === DAILY_CODE || String(p?.CapacityType) === SLICE) continue
|
|
2975
|
+
if (!counted.has(p?.PackageCode) || !only(p?.PackageCode) || p?.PackageCode === DAILY_CODE || String(p?.CapacityType) === SLICE) continue
|
|
2895
2976
|
const left = capacity(p?.CycleCapacityRemainPrecise ?? p?.CycleCapacityRemain)
|
|
2896
2977
|
const end = endOf(p)
|
|
2897
2978
|
if (!(left > 0) || end <= now) continue
|
|
@@ -3626,7 +3707,7 @@ function makePlugin(site) {
|
|
|
3626
3707
|
}
|
|
3627
3708
|
said = "renewed"
|
|
3628
3709
|
}
|
|
3629
|
-
const out = usageOf(sum, auth.plan)
|
|
3710
|
+
const out = usageOf(sum, auth.plan, site)
|
|
3630
3711
|
// an enterprise account's summary has no packages: its credits
|
|
3631
3712
|
// are the enterprise meter's (#1904). A personal account's
|
|
3632
3713
|
// refusal of it (10001 "uid or enterpriseID is empty") leaves
|
|
@@ -3641,22 +3722,36 @@ function makePlugin(site) {
|
|
|
3641
3722
|
// when the plan's credits start again (cycleOf), and when the credits
|
|
3642
3723
|
// left run out, for routing to spend them first (runsOutOf);
|
|
3643
3724
|
// a list that can't be read leaves it unknown, the card stands
|
|
3725
|
+
//
|
|
3726
|
+
// Split (usageOf), the plan's cycle is the plan window's alone;
|
|
3727
|
+
// the soonest credits to lapse, of any package, stay on the window
|
|
3728
|
+
// that stops the account, as routing reads none set aside, and
|
|
3729
|
+
// the window set aside says its own.
|
|
3644
3730
|
const credits = out.windows.find((w) => w.name === "Credits")
|
|
3731
|
+
const extra = out.windows.find((w) => w.name === "Extra credits")
|
|
3645
3732
|
if (credits) {
|
|
3646
3733
|
try {
|
|
3647
3734
|
const packages = await packagesOf(site, a)
|
|
3648
|
-
Object.assign(credits, cycleOf(sum, packages) ?? {}
|
|
3735
|
+
Object.assign(credits, cycleOf(sum, packages) ?? {})
|
|
3736
|
+
const ofPlan = planCodes(sum, site)
|
|
3737
|
+
const stops = extra && !extra.aside ? extra : credits
|
|
3738
|
+
Object.assign(stops, runsOutOf(sum, packages) ?? {})
|
|
3739
|
+
const aside = extra ? (stops === credits ? extra : credits) : null
|
|
3740
|
+
if (aside) Object.assign(aside, runsOutOf(sum, packages, Date.now(), (code) => ofPlan(code) === (aside === credits)) ?? {})
|
|
3649
3741
|
} catch {}
|
|
3650
3742
|
}
|
|
3651
3743
|
await spareFree(site, a, out.windows)
|
|
3652
3744
|
// the free model's own window, which the credit meter above does
|
|
3653
3745
|
// not show: what actually refuses a chat long before 160 credits
|
|
3654
|
-
// are spent. WorkBuddy AI
|
|
3655
|
-
// measured
|
|
3656
|
-
|
|
3746
|
+
// are spent. Both builds: WorkBuddy AI at 1e8 (ten days of
|
|
3747
|
+
// refusals), the China build's refusals measured at about 2e8 on
|
|
3748
|
+
// 2026-11-02, its card stopping at the 1.9e8 soft cap (see the
|
|
3749
|
+
// window definitions above)
|
|
3750
|
+
{
|
|
3657
3751
|
const account = a.accountId || a.user || a.uid || ""
|
|
3658
|
-
const
|
|
3659
|
-
const
|
|
3752
|
+
const win = site.ai ? FREE_WINDOW_AI : FREE_WINDOW_CN
|
|
3753
|
+
const rows = freeWindowRows(account, Date.now(), process.cwd(), process.env, homedir(), win)
|
|
3754
|
+
const w = rows && freeWindowOf(rows, account, Date.now(), win)
|
|
3660
3755
|
if (w) out.windows.push(w)
|
|
3661
3756
|
}
|
|
3662
3757
|
return { ...out, signIn: said }
|
|
@@ -3801,4 +3896,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
3801
3896
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
3802
3897
|
|
|
3803
3898
|
// for tests
|
|
3804
|
-
export const _internal = { idFor, OFF_TAKEN, replyAfterToolsOn, withDefaultEffort, defaultEffortOf, gzipped, FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, accountName, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, normalizeToolChoice, fitTools, fitted, renamed, unsaid, FILTER_SAYS, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, REFUSED_DOC, commaPaused, commaPausedOn, opened, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, scopeOf, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, freeWindowOf, tokensShort, freeWindowRows, freeWindowLedger, readLedgerTail, pinned, rowTime, rowAccount, ofAccount, windowOffset, windowReset, FREE_WINDOW_MODELS, FREE_WINDOW_SPAN, FREE_WINDOW_GAP, FREE_WINDOW_LEAD, checkinOf, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
|
3899
|
+
export const _internal = { planCodes, idFor, OFF_TAKEN, replyAfterToolsOn, withDefaultEffort, defaultEffortOf, gzipped, FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, accountName, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, normalizeToolChoice, fitTools, fitted, renamed, unsaid, FILTER_SAYS, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, REFUSED_DOC, commaPaused, commaPausedOn, opened, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, scopeOf, turnKey, conversationKey, signatureOf, isMessages, withoutUnsigned, withoutSignedThinking, prepared, answeredBefore, nativeOf, modelOf, configModels, freeWindowOf, tokensShort, freeWindowRows, freeWindowLedger, readLedgerTail, pinned, rowTime, rowAccount, ofAccount, windowOffset, windowReset, FREE_WINDOW_MODELS, FREE_WINDOW_CN, FREE_WINDOW_AI, FREE_WINDOW_SPAN, FREE_WINDOW_GAP, FREE_WINDOW_LEAD, checkinOf, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-workbuddy-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "OpenCode provider plugins for WorkBuddy's plans: WorkBuddy (CodeBuddy, copilot.tencent.com) and WorkBuddy AI (www.workbuddy.ai)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|