@magpie-community/opencode-workbuddy-auth 0.2.9 → 0.2.11

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 +17 -0
  2. package/index.mjs +82 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -329,6 +329,12 @@ provider settings in magpie, off by default (#430). When it is on:
329
329
 
330
330
  The price list is read again at most once a minute for requests.
331
331
 
332
+ The switch gives its label and description in magpie's other languages
333
+ (`labels`, `descriptions`: Chinese, Traditional Chinese, Japanese, German),
334
+ and says `filtersModels`, so while it is on magpie says under the models
335
+ that the list is filtered (#590). Both are magpie's own fields; an older
336
+ magpie shows the English words and no note.
337
+
332
338
  ## Credits in magpie
333
339
 
334
340
  magpie's usage card shows the account's credits from the resource summary.
@@ -338,6 +344,17 @@ client reads. The daily Free Plan slice isn't counted: it ends at the same
338
344
  midnight on every account. magpie's smart routing spends the account whose
339
345
  credits run out first.
340
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
+
341
358
  An enterprise (team) account's resource summary lists no packages. Its
342
359
  credits are read from the enterprise meter
343
360
  (`/billing/meter/get-enterprise-user-usage`), the one WorkBuddy's own usage
package/index.mjs CHANGED
@@ -2776,20 +2776,49 @@ function pinned(rows, account) {
2776
2776
  // CycleRemainCapacity at all leaves the window out. TotalCredits is not a
2777
2777
  // fallback: an account with no packages at all keeps its unknown allowance,
2778
2778
  // as before, so its free models are not skipped.
2779
- function usageOf(sum, plan) {
2779
+ //
2780
+ // The plan's own credits and the rest are two windows (akic404 on
2781
+ // Discord): summed, the gifted and activity packages' credits used long
2782
+ // ago put the card at most of its allowance on the first day of the
2783
+ // month, and the plan's pace line said nothing. "Credits" is the packages
2784
+ // whose credits come back each cycle — WorkBuddy's client's own
2785
+ // onlyRefreshCodes (PLAN_CODES), or the subscription the summary names —
2786
+ // and "Extra credits" the ones that only lapse (gift, activity, bought
2787
+ // credit packs, any code the client doesn't list). A summary with only
2788
+ // one kind keeps the one "Credits" window, as before.
2789
+ //
2790
+ // Only one of the two stops the account at a time: WorkBuddy serves it
2791
+ // while any credits are left, so the one set aside is the one whose
2792
+ // running out doesn't stop it — "Extra credits", until the plan's are
2793
+ // spent and extra ones are left.
2794
+ function usageOf(sum, plan, site) {
2780
2795
  const out = { plan: plan || (sum?.IsPaidUser ? "Pro" : "Free plan"), windows: [] }
2781
- let total = 0, used = 0, left = 0, bags = 0
2796
+ const ofPlan = planCodes(sum, site)
2797
+ const kinds = { plan: { total: 0, used: 0 }, extra: { total: 0, used: 0 } }
2798
+ let left = 0, bags = 0
2782
2799
  for (const p of sum?.Packages ?? []) {
2783
2800
  const hasTotal = p?.CycleTotalCapacity != null && p.CycleTotalCapacity !== ""
2784
2801
  if (hasTotal) {
2785
- total += capacity(p.CycleTotalCapacity)
2786
- used += capacity(p?.CycleUsedCapacity)
2802
+ const k = kinds[ofPlan(p?.PackageCode) ? "plan" : "extra"]
2803
+ k.total += capacity(p.CycleTotalCapacity)
2804
+ k.used += capacity(p?.CycleUsedCapacity)
2787
2805
  } else if (p?.CycleRemainCapacity != null && p.CycleRemainCapacity !== "") {
2788
2806
  left += capacity(p.CycleRemainCapacity)
2789
2807
  bags++
2790
2808
  }
2791
2809
  }
2792
- if (total > 0) out.windows.push({ name: "Credits", used: (100 * used) / total, display: `${compact(used)} / ${compact(total)}`, amount: used, limit: total, unit: "credits" })
2810
+ const window = (name, { used, total }) => ({ name, used: (100 * used) / total, display: `${compact(used)} / ${compact(total)}`, amount: used, limit: total, unit: "credits" })
2811
+ const { plan: mine, extra } = kinds
2812
+ if (mine.total > 0 && extra.total > 0) {
2813
+ const credits = window("Credits", mine), more = window("Extra credits", extra)
2814
+ if (mine.used >= mine.total && extra.used < extra.total) credits.aside = true
2815
+ else more.aside = true
2816
+ out.windows.push(credits, more)
2817
+ } else {
2818
+ const total = mine.total + extra.total, used = mine.used + extra.used
2819
+ if (total > 0) out.windows.push(window("Credits", { used, total }))
2820
+ }
2821
+ const total = mine.total + extra.total
2793
2822
  if (bags > 0 && (total === 0 || left > 0)) out.windows.push({ name: "Prepaid credits", balance: left, display: `${compact(left)} credits`, unit: "credits" })
2794
2823
  return out
2795
2824
  }
@@ -2845,6 +2874,26 @@ const FREE_CODES = [DAILY_CODE, CODE("008", "cfWoLwvjU4"), CODE("035", "ArVxJcGD
2845
2874
  // lost at the same midnight on every account, so they tell none apart
2846
2875
  const SLICE = "4"
2847
2876
 
2877
+ // PLAN_CODES are the packages whose credits come back each cycle, as
2878
+ // WorkBuddy's client (5.9.x) lists them (onlyRefreshCodes): Pro monthly,
2879
+ // Pro monthly plus, Pro yearly, youth, advanced, flagship, the plan's
2880
+ // bonus (bonus28; bonusIntl, "Plan Bonus Quota"), the international free
2881
+ // plan (freeMonIntl), the Pro trials, and on the China build alone the
2882
+ // free plan's monthly base (freeMon, 套餐基础积分). Every other code only
2883
+ // lapses: gift (006, "Bonus Pack" / Pro trial), activity (007, 赠送积分),
2884
+ // bonus29/30, and the credit packs (009, 036, 038).
2885
+ const PLAN_CODES = [CODE("002", "AkiJS3ZHF5"), CODE("005", "maRGyrHhw1"), CODE("003", "FAnt7lcmRT"), CODE("023", "4xbGhMrE6q"),
2886
+ CODE("026", "BaESVICNoi"), CODE("027", "0FCGVA6vSa"), CODE("028", "NtpWi0jzXs"), CODE("035", "ArVxJcGDsm"),
2887
+ CODE("039", "KRcQj7wUat"), CODE("040", "mi9rCYg46x"), CODE("037", "WxOD3MpI2o")]
2888
+ const FREE_MON = CODE("008", "cfWoLwvjU4")
2889
+
2890
+ // planCodes says whether a package of the summary is the plan's own: one
2891
+ // of PLAN_CODES on that site, or the subscription the summary names.
2892
+ function planCodes(sum, site) {
2893
+ const sub = sum?.SubscriptionPackageCode
2894
+ return (code) => !!code && (code === sub || PLAN_CODES.includes(code) || (!site?.ai && code === FREE_MON))
2895
+ }
2896
+
2848
2897
  // beijing is WorkBuddy's own time ("2026-10-13 00:39:27" is UTC+8, as its
2849
2898
  // DeductionEndTime for the same package says).
2850
2899
  const beijing = (ms) => new Date(ms + 8 * 3600_000).toISOString().slice(0, 19).replace("T", " ")
@@ -2887,11 +2936,12 @@ function endOf(p) {
2887
2936
  // packages are lost, and how many (cdredfox on X): smart routing spends
2888
2937
  // the account whose credits expire first. Only packages the summary
2889
2938
  // counts, with credits left, and not the daily slice; null when none.
2890
- function runsOutOf(sum, packages, now = Date.now()) {
2939
+ // only narrows it to the packages of one window (the split's, usageOf).
2940
+ function runsOutOf(sum, packages, now = Date.now(), only = () => true) {
2891
2941
  const counted = new Set((sum?.Packages ?? []).map((p) => p?.PackageCode))
2892
2942
  let at = 0, amount = 0
2893
2943
  for (const p of packages ?? []) {
2894
- if (!counted.has(p?.PackageCode) || p?.PackageCode === DAILY_CODE || String(p?.CapacityType) === SLICE) continue
2944
+ if (!counted.has(p?.PackageCode) || !only(p?.PackageCode) || p?.PackageCode === DAILY_CODE || String(p?.CapacityType) === SLICE) continue
2895
2945
  const left = capacity(p?.CycleCapacityRemainPrecise ?? p?.CycleCapacityRemain)
2896
2946
  const end = endOf(p)
2897
2947
  if (!(left > 0) || end <= now) continue
@@ -3387,6 +3437,17 @@ function makePlugin(site) {
3387
3437
  key: FREE_ONLY,
3388
3438
  label: "Free models only",
3389
3439
  description: `Lists only the models ${site.name} serves at no cost, and turns away a request for any other here instead of sending it, so no credits are spent. A model free only at night counts as paid.`,
3440
+ // the same words in magpie's other languages, and that the list
3441
+ // leaves models out while it is on (plugins#590); magpie's own
3442
+ // fields, which an older magpie and OpenCode leave alone
3443
+ labels: { zh: "仅免费模型", "zh-TW": "僅免費模型", ja: "無料モデルのみ", de: "Nur kostenlose Modelle" },
3444
+ descriptions: {
3445
+ zh: `只列出 ${site.name} 免费提供的模型;其他模型的请求在这里就被拒绝、不会发出,因此不消耗积分。只在夜间免费的模型按付费算。`,
3446
+ "zh-TW": `只列出 ${site.name} 免費提供的模型;其他模型的請求在這裡就被拒絕、不會送出,因此不消耗積分。只在夜間免費的模型以付費計。`,
3447
+ ja: `${site.name} が無料で提供するモデルだけを表示し、ほかのモデルへのリクエストは送信せずここで断るので、クレジットを消費しません。夜間だけ無料のモデルは有料として扱います。`,
3448
+ de: `Listet nur die Modelle, die ${site.name} kostenlos anbietet, und weist eine Anfrage an jedes andere hier ab, statt sie zu senden, sodass keine Credits verbraucht werden. Ein Modell, das nur nachts kostenlos ist, gilt als kostenpflichtig.`,
3449
+ },
3450
+ filtersModels: true,
3390
3451
  },
3391
3452
  ],
3392
3453
  async loader(getAuth) {
@@ -3615,7 +3676,7 @@ function makePlugin(site) {
3615
3676
  }
3616
3677
  said = "renewed"
3617
3678
  }
3618
- const out = usageOf(sum, auth.plan)
3679
+ const out = usageOf(sum, auth.plan, site)
3619
3680
  // an enterprise account's summary has no packages: its credits
3620
3681
  // are the enterprise meter's (#1904). A personal account's
3621
3682
  // refusal of it (10001 "uid or enterpriseID is empty") leaves
@@ -3630,11 +3691,22 @@ function makePlugin(site) {
3630
3691
  // when the plan's credits start again (cycleOf), and when the credits
3631
3692
  // left run out, for routing to spend them first (runsOutOf);
3632
3693
  // a list that can't be read leaves it unknown, the card stands
3694
+ //
3695
+ // Split (usageOf), the plan's cycle is the plan window's alone;
3696
+ // the soonest credits to lapse, of any package, stay on the window
3697
+ // that stops the account, as routing reads none set aside, and
3698
+ // the window set aside says its own.
3633
3699
  const credits = out.windows.find((w) => w.name === "Credits")
3700
+ const extra = out.windows.find((w) => w.name === "Extra credits")
3634
3701
  if (credits) {
3635
3702
  try {
3636
3703
  const packages = await packagesOf(site, a)
3637
- Object.assign(credits, cycleOf(sum, packages) ?? {}, runsOutOf(sum, packages) ?? {})
3704
+ Object.assign(credits, cycleOf(sum, packages) ?? {})
3705
+ const ofPlan = planCodes(sum, site)
3706
+ const stops = extra && !extra.aside ? extra : credits
3707
+ Object.assign(stops, runsOutOf(sum, packages) ?? {})
3708
+ const aside = extra ? (stops === credits ? extra : credits) : null
3709
+ if (aside) Object.assign(aside, runsOutOf(sum, packages, Date.now(), (code) => ofPlan(code) === (aside === credits)) ?? {})
3638
3710
  } catch {}
3639
3711
  }
3640
3712
  await spareFree(site, a, out.windows)
@@ -3790,4 +3862,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
3790
3862
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
3791
3863
 
3792
3864
  // for tests
3793
- 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 }
3865
+ 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_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.9",
3
+ "version": "0.2.11",
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",