@magpie-community/opencode-workbuddy-auth 0.2.18 → 0.2.20

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 (3) hide show
  1. package/README.md +23 -7
  2. package/index.mjs +79 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -447,14 +447,30 @@ What is measured, and what is easy to read wrongly:
447
447
  rather than +08:00 guessed.
448
448
 
449
449
  A window that has lapsed reports nothing rather than 0%: nothing is
450
- exhausted, and the next request opens a fresh one. The China build
451
- (`copilot.tencent.com`) carries the same window: two Free-plan accounts
450
+ exhausted, and the next request opens a fresh one.
451
+
452
+ ### The China build's windows (measured, shown as estimates)
453
+
454
+ The China build (`copilot.tencent.com`) carries the same kind of window,
455
+ measured one model at a time. `deepseek-v4.1-flash`: two Free-plan accounts
452
456
  refused on 2026-11-02 at 200,389,043 and 200,485,923 tokens, so its cap is
453
- measured at about 2e8 — and its refusals name the reset in Chinese
454
- (`将在 … UTC+8 重置`), which reads as an edge the same way. Its card reads
455
- against 2e8. In 0.2.12 and 0.2.13 it read against a soft cap of 1.9e8, so
456
- that routing rested the model before the vendor refused it; with the sum set
457
- aside that headroom only stopped the model early, so it is gone.
457
+ about **2e8** — and its refusals name the reset in Chinese
458
+ (`将在 … UTC+8 重置`), which reads as an edge the same way.
459
+
460
+ Unlike the AI build's, the China model is **charged in credits** (`x0.11`)
461
+ — the token window and the credits meter are two limits on one model — so
462
+ the card is not a "free model" card. It is named after its model and says
463
+ its cap is an estimate: **`deepseek-v4.1-flash tokens (est.)`**, with
464
+ `~200M` in its text (`151.5M / ~200M`). The estimate is this machine's ledger
465
+ against a cap measured on two accounts; the vendor reports neither, and the
466
+ first refusal of a window pins the card full at the reset the vendor names.
467
+
468
+ The measured windows live in one table (`CN_ESTIMATED_WINDOWS`), one entry
469
+ per model. Only measured models have a card — a guessed cap would read wrong
470
+ every day. Add an entry when another model's window is measured; remove one
471
+ when the vendor stops applying it. Because the card is set aside (shown, not
472
+ routed on), a stale cap costs a wrong percentage, never a wrongly rested
473
+ model: the refusal is what stops a model, on both builds.
458
474
 
459
475
  ## When free models work but a credit model does not
460
476
 
package/index.mjs CHANGED
@@ -2212,7 +2212,7 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
2212
2212
 
2213
2213
  // The window WorkBuddy rate limits deepseek-v4.1-flash by, which its credit
2214
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
+ // China build at about 2e8 on 2026-11-02 (see CN_ESTIMATED_WINDOWS).
2216
2216
  //
2217
2217
  // A free account's usage() reports only the credits window, so a plan with
2218
2218
  // 160 credits untouched reads 0% while chats to deepseek-v4.1-flash are
@@ -2293,18 +2293,46 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
2293
2293
  // ~/.config/magpie otherwise. No endpoint reports the window; a ledger that
2294
2294
  // cannot be read yields no window rather than a made-up one.
2295
2295
 
2296
- // A window: the build's ledger rows it counts (provider), the cap it reports
2297
- // against, and how deep its display rounds. Two builds, two windows, two
2298
- // caps: the same phone number can be signed in to both, and their ledgers
2296
+ // A window: the build's ledger rows it counts (provider and model), the cap
2297
+ // it reports against, and how its card reads. Two builds, two sets of
2298
+ // windows: the same phone number can be signed in to both, and their ledgers
2299
2299
  // stay apart — one build's spending must not fill the other's window.
2300
2300
  //
2301
- // The China cap is the measured one, 2e8 (two accounts refused at 200.39M
2302
- // and 200.49M). It was a soft 1.9e8 in 0.2.12 and 0.2.13, so that routing
2303
- // rested the model before the vendor refused it; since the sum is set aside
2304
- // (freeWindowOf's card), the cap only scales what the card says, and the
2305
- // headroom bought nothing but a model stopped 10M tokens early (plugins#641).
2306
- const FREE_WINDOW_CN = { provider: "workbuddy", cap: 2e8, short: "200M" }
2307
- const FREE_WINDOW_AI = { provider: "workbuddy-ai", cap: 1e8, short: "100M" }
2301
+ // The China caps are measured on this machine from the vendor's own
2302
+ // refusals, never the vendor's word, and each is an estimate: 2e8 for
2303
+ // deepseek-v4.1-flash (two accounts refused at 200.39M and 200.49M on
2304
+ // 2026-11-02), with the card saying so (`~` and "(est.)"). The card is
2305
+ // shown only — set aside, plugins#641 — so a cap that goes stale costs a
2306
+ // wrong percentage, never a wrongly rested model; the first refusal of a
2307
+ // window pins the card full at the reset the vendor names.
2308
+ //
2309
+ // One entry per model, and only models that have been measured: a guessed
2310
+ // cap would read wrong every day. Add an entry when another model's window
2311
+ // is measured; remove one when the vendor stops applying it.
2312
+ const CN_ESTIMATED_WINDOWS = [
2313
+ {
2314
+ provider: "workbuddy",
2315
+ model: "deepseek-v4.1-flash",
2316
+ aliases: ["deepseek-v4.1-flash-sg"],
2317
+ cap: 2e8,
2318
+ name: "deepseek-v4.1-flash tokens (est.)",
2319
+ short: "~200M",
2320
+ // no unit: the name already says tokens, and magpie's narrow account
2321
+ // cards cut a unit short ("tok…") beside the nine-digit numbers
2322
+ unit: "",
2323
+ },
2324
+ ]
2325
+ // WorkBuddy AI's window keeps the name it shipped with: there the model is
2326
+ // served at x0.00, so "Free model tokens" is what it is.
2327
+ const FREE_WINDOW_AI = {
2328
+ provider: "workbuddy-ai",
2329
+ model: "deepseek-v4.1-flash",
2330
+ aliases: ["deepseek-v4.1-flash-sg"],
2331
+ cap: 1e8,
2332
+ name: "Free model tokens",
2333
+ short: "100M",
2334
+ unit: "tokens",
2335
+ }
2308
2336
  const FREE_WINDOW_SPAN = 24 * 3600 * 1000
2309
2337
  // The lead: how far back of an edge the read goes on. A chain read from a row
2310
2338
  // that opens a window needs the window the span makes of the rows above it,
@@ -2317,10 +2345,9 @@ const FREE_WINDOW_LEAD = 86400 * 1000
2317
2345
  // earlier row began at most 24h before it, and so ended 24h after it at the
2318
2346
  // latest. This is one of the two edges the ledger itself spells out.
2319
2347
  const FREE_WINDOW_GAP = FREE_WINDOW_SPAN
2320
- // the model the window is counted for; -sg shares it
2321
- const FREE_WINDOW_MODEL = "deepseek-v4.1-flash"
2322
- // both ids, so magpie's exact match finds the -sg one too
2323
- const FREE_WINDOW_MODELS = ["deepseek-v4.1-flash", "deepseek-v4.1-flash-sg"]
2348
+ // the model a window is counted for is its spec's `model`; `aliases` are the
2349
+ // ids that share the window (-sg is the same model under its paid id), and
2350
+ // both are named in the card's models so magpie's exact match finds them
2324
2351
 
2325
2352
  // windowOffset is the UTC offset a refusal spells out, in minutes, from
2326
2353
  // "UTC+8", "UTC+08:00", "UTC-5" or "GMT+8". null when it spells out none,
@@ -2367,7 +2394,7 @@ function freeWindowOf(rows, account, now = Date.now(), win = FREE_WINDOW_AI) {
2367
2394
  r?.provider === win.provider &&
2368
2395
  r.providerAccount === account &&
2369
2396
  typeof r.model === "string" &&
2370
- r.model.startsWith(FREE_WINDOW_MODEL) &&
2397
+ r.model.startsWith(win.model) &&
2371
2398
  r.t,
2372
2399
  )
2373
2400
  if (!mine.length) return null
@@ -2440,15 +2467,17 @@ function freeWindowOf(rows, account, now = Date.now(), win = FREE_WINDOW_AI) {
2440
2467
  if (now >= end) return null // lapsed: nothing is exhausted, a new window opens on the next request
2441
2468
 
2442
2469
  const card = (amount, resetsAt, refused) => ({
2443
- name: "Free model tokens",
2470
+ name: win.name,
2444
2471
  used: Math.min(100, (100 * amount) / win.cap),
2445
2472
  amount,
2446
2473
  limit: win.cap,
2447
- unit: "tokens",
2474
+ unit: win.unit,
2448
2475
  display: `${tokensShort(amount)} / ${win.short}`,
2449
2476
  resetsAt: new Date(resetsAt).toISOString(),
2450
2477
  span: FREE_WINDOW_SPAN / 1000,
2451
- models: FREE_WINDOW_MODELS.slice(),
2478
+ // the window's own model and the ids that share it, so magpie's exact
2479
+ // match finds every id it limits
2480
+ models: [win.model, ...win.aliases],
2452
2481
  // the ledger's sum is an estimate, shown and set aside; the vendor's
2453
2482
  // refusal is the one that stops the model (see the window above)
2454
2483
  ...(refused ? { refused: true } : { aside: true }),
@@ -2550,10 +2579,11 @@ function freeWindowRows(account, now = Date.now(), cwd = process.cwd(), env = pr
2550
2579
  // way this ledger is written; one rewritten in place (a rotation or a
2551
2580
  // compaction) moves mtime even when its size lands the same.
2552
2581
  const file = path + "|" + st.mtimeMs + ":" + st.size
2553
- // the key carries the account and the build: one account's rows are its
2554
- // own, and the two builds' chains over one ledger must not answer for
2555
- // each other (the same phone number can be signed in to both)
2556
- const key = file + "|" + win.provider + "|" + account
2582
+ // the key carries the account, the build and the model: one account's
2583
+ // rows are its own, the two builds' chains over one ledger must not
2584
+ // answer for each other (the same phone number can be signed in to both),
2585
+ // and two windows of one build count different models
2586
+ const key = file + "|" + win.provider + "|" + win.model + "|" + account
2557
2587
  if (ledgerParsed?.key === key) return ledgerParsed.rows
2558
2588
  let got
2559
2589
  try {
@@ -2617,15 +2647,15 @@ function rowAccount(line) {
2617
2647
  }
2618
2648
 
2619
2649
  // ofAccount is whether a row belongs to the chain read for account: this
2620
- // account's own row of the build's free model, either id. A refusal of
2621
- // another account's, of the other build, or of a paid model says nothing
2622
- // about where this chain opens.
2650
+ // account's own row of the window's model, either id. A refusal of another
2651
+ // account's, of the other build, or of another model says nothing about
2652
+ // where this chain opens.
2623
2653
  function ofAccount(row, account, win = FREE_WINDOW_AI) {
2624
2654
  return (
2625
2655
  row?.provider === win.provider &&
2626
2656
  row.providerAccount === account &&
2627
2657
  typeof row.model === "string" &&
2628
- row.model.startsWith(FREE_WINDOW_MODEL)
2658
+ row.model.startsWith(win.model)
2629
2659
  )
2630
2660
  }
2631
2661
 
@@ -3879,21 +3909,33 @@ function makePlugin(site) {
3879
3909
  const { runsOutAt, runsOut, ...counts } = more
3880
3910
  const w = { ...creditWindow("Extra credits", counts), aside: true, ...(runsOutAt ? { runsOutAt, runsOut } : {}) }
3881
3911
  out.windows.splice(out.windows.indexOf(credits) + 1, 0, w)
3912
+ // and, as with the split's, the soonest of them to lapse is
3913
+ // the account's too, on the window that stops it: routing
3914
+ // reads none set aside, so an account whose extra credits
3915
+ // expire first went unknown to it (Kayphoon on Discord)
3916
+ const at = runsOutAt ? Date.parse(runsOutAt) : 0
3917
+ const was = credits.runsOutAt ? Date.parse(credits.runsOutAt) : 0
3918
+ if (at > 0 && (!was || at < was)) Object.assign(credits, { runsOutAt, runsOut })
3919
+ else if (at > 0 && at === was) credits.runsOut = (credits.runsOut ?? 0) + runsOut
3882
3920
  }
3883
3921
  } catch {}
3884
3922
  }
3885
3923
  await spareFree(site, a, out.windows)
3886
- // the free model's own window, which the credit meter above does
3887
- // not show: what actually refuses a chat long before 160 credits
3888
- // are spent. Both builds: WorkBuddy AI at 1e8 (ten days of
3889
- // refusals), the China build's at about 2e8 (2026-11-02). Set
3890
- // aside until the vendor refuses (see the window definitions above)
3924
+ // each measured model's own window, which the credit meter above
3925
+ // does not show: what actually refuses a chat long before the
3926
+ // credits are spent. WorkBuddy AI has one (deepseek, 1e8, ten days
3927
+ // of refusals); the China build's are measured one model at a time
3928
+ // (deepseek at about 2e8, 2026-11-02), each its own card. Shown
3929
+ // only, set aside: the vendor's refusal is what stops a model
3930
+ // (see the window definitions above)
3891
3931
  {
3892
3932
  const account = a.accountId || a.user || a.uid || ""
3893
- const win = site.ai ? FREE_WINDOW_AI : FREE_WINDOW_CN
3894
- const rows = freeWindowRows(account, Date.now(), process.cwd(), process.env, homedir(), win)
3895
- const w = rows && freeWindowOf(rows, account, Date.now(), win)
3896
- if (w) out.windows.push(w)
3933
+ const wins = site.ai ? [FREE_WINDOW_AI] : CN_ESTIMATED_WINDOWS
3934
+ for (const win of wins) {
3935
+ const rows = freeWindowRows(account, Date.now(), process.cwd(), process.env, homedir(), win)
3936
+ const w = rows && freeWindowOf(rows, account, Date.now(), win)
3937
+ if (w) out.windows.push(w)
3938
+ }
3897
3939
  }
3898
3940
  return { ...out, signIn: said }
3899
3941
  } catch (e) {
@@ -4037,4 +4079,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
4037
4079
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
4038
4080
 
4039
4081
  // for tests
4040
- export const _internal = { planCodes, listedExtra, idFor, OFF_TAKEN, replyAfterToolsOn, withDefaultEffort, SMALL_BUDGET, 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, pinning, 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 }
4082
+ export const _internal = { planCodes, listedExtra, idFor, OFF_TAKEN, replyAfterToolsOn, withDefaultEffort, SMALL_BUDGET, 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, pinning, rowTime, rowAccount, ofAccount, windowOffset, windowReset, CN_ESTIMATED_WINDOWS, 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.18",
3
+ "version": "0.2.20",
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": "./opencode.mjs",