@magpie-community/opencode-workbuddy-auth 0.2.12 → 0.2.14
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 +12 -4
- package/index.mjs +70 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -399,6 +399,14 @@ What is measured, and what is easy to read wrongly:
|
|
|
399
399
|
429 whose named reset is still ahead makes the window full — `used: 100`,
|
|
400
400
|
`resetsAt` the reset the vendor named — whatever the rows add up to. The
|
|
401
401
|
sum carries the window only until the first refusal.
|
|
402
|
+
- **The sum is shown, not routed on.** Until the vendor refuses, the card is
|
|
403
|
+
set aside (`aside: true`): magpie draws it beside the account and never
|
|
404
|
+
counts it as the account's allowance, so an estimate near its cap does not
|
|
405
|
+
stop the model. The refusal's card does (`refused: true`), and magpie's
|
|
406
|
+
gateway rests this model alone on this account until the reset the
|
|
407
|
+
refusal names. Before 0.2.14 the sum stopped the model too, and a paid
|
|
408
|
+
China account read 99% of a cap it had never been refused at, with 2,457
|
|
409
|
+
credits left (plugins#641).
|
|
402
410
|
- **The card names both ids**, `deepseek-v4.1-flash` and
|
|
403
411
|
`deepseek-v4.1-flash-sg`: magpie matches a window's models by exact id
|
|
404
412
|
(only an `@size` suffix folds into the bare id), so one alone would leave
|
|
@@ -435,10 +443,10 @@ exhausted, and the next request opens a fresh one. The China build
|
|
|
435
443
|
(`copilot.tencent.com`) carries the same window: two Free-plan accounts
|
|
436
444
|
refused on 2026-11-02 at 200,389,043 and 200,485,923 tokens, so its cap is
|
|
437
445
|
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
|
|
439
|
-
|
|
440
|
-
the model before the vendor
|
|
441
|
-
|
|
446
|
+
(`将在 … UTC+8 重置`), which reads as an edge the same way. Its card reads
|
|
447
|
+
against 2e8. In 0.2.12 and 0.2.13 it read against a soft cap of 1.9e8, so
|
|
448
|
+
that routing rested the model before the vendor refused it; with the sum set
|
|
449
|
+
aside that headroom only stopped the model early, so it is gone.
|
|
442
450
|
|
|
443
451
|
## When free models work but a credit model does not
|
|
444
452
|
|
package/index.mjs
CHANGED
|
@@ -2276,6 +2276,17 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
|
|
|
2276
2276
|
// full whatever the sum says. That is the one exact signal there is,
|
|
2277
2277
|
// and routing must not pass over it either.
|
|
2278
2278
|
//
|
|
2279
|
+
// So the sum is an estimate, and it is shown, not routed on: its card is set
|
|
2280
|
+
// aside (aside: true), which magpie draws beside the account and never counts
|
|
2281
|
+
// as its allowance. Only a refusal's card stops the model. The estimate used
|
|
2282
|
+
// to stop it as well, and it read a paid China account at 99% of a cap it had
|
|
2283
|
+
// never been refused at, with 2,457 credits left (XiaoFeiyu9, plugins#641):
|
|
2284
|
+
// the cap is two accounts' measurement, the ledger is one machine's, and
|
|
2285
|
+
// neither is the vendor's word for this account. Nothing is lost by waiting
|
|
2286
|
+
// for that word: magpie's gateway reads the refusal's reset ("将在 … 重置",
|
|
2287
|
+
// "reset at …") and its "switch to the other models", and rests this model
|
|
2288
|
+
// alone, on this account alone, until the time the vendor named.
|
|
2289
|
+
//
|
|
2279
2290
|
// The ledger is magpie's own, in magpie's config folder (appdir.Config()):
|
|
2280
2291
|
// the folder the plugin host runs in, which is the portable data folder
|
|
2281
2292
|
// when magpie runs portable, $XDG_CONFIG_HOME/magpie when that is set, and
|
|
@@ -2287,12 +2298,12 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
|
|
|
2287
2298
|
// caps: the same phone number can be signed in to both, and their ledgers
|
|
2288
2299
|
// stay apart — one build's spending must not fill the other's window.
|
|
2289
2300
|
//
|
|
2290
|
-
// The China cap is
|
|
2291
|
-
//
|
|
2292
|
-
//
|
|
2293
|
-
//
|
|
2294
|
-
//
|
|
2295
|
-
const FREE_WINDOW_CN = { provider: "workbuddy", cap:
|
|
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" }
|
|
2296
2307
|
const FREE_WINDOW_AI = { provider: "workbuddy-ai", cap: 1e8, short: "100M" }
|
|
2297
2308
|
const FREE_WINDOW_SPAN = 24 * 3600 * 1000
|
|
2298
2309
|
// The lead: how far back of an edge the read goes on. A chain read from a row
|
|
@@ -2438,7 +2449,9 @@ function freeWindowOf(rows, account, now = Date.now(), win = FREE_WINDOW_AI) {
|
|
|
2438
2449
|
resetsAt: new Date(resetsAt).toISOString(),
|
|
2439
2450
|
span: FREE_WINDOW_SPAN / 1000,
|
|
2440
2451
|
models: FREE_WINDOW_MODELS.slice(),
|
|
2441
|
-
|
|
2452
|
+
// the ledger's sum is an estimate, shown and set aside; the vendor's
|
|
2453
|
+
// refusal is the one that stops the model (see the window above)
|
|
2454
|
+
...(refused ? { refused: true } : { aside: true }),
|
|
2442
2455
|
})
|
|
2443
2456
|
|
|
2444
2457
|
// A refusal still in force is exact: the account is spending this window
|
|
@@ -2674,7 +2687,7 @@ function readLedgerTail(path, st, now, chunks, account, win = FREE_WINDOW_AI) {
|
|
|
2674
2687
|
let carry = ""
|
|
2675
2688
|
let firstPass = true
|
|
2676
2689
|
let stop = false
|
|
2677
|
-
|
|
2690
|
+
const edge = pinning() // where this account's own rows read so far say the chain is read from
|
|
2678
2691
|
let reach = 0 // the row below which nothing is kept: 0 until an edge is found
|
|
2679
2692
|
let answered = false // whether an answered free-model row of it is read
|
|
2680
2693
|
|
|
@@ -2717,9 +2730,8 @@ function readLedgerTail(path, st, now, chunks, account, win = FREE_WINDOW_AI) {
|
|
|
2717
2730
|
}
|
|
2718
2731
|
if (!ofAccount(row, account, win)) continue
|
|
2719
2732
|
rev.push(row)
|
|
2720
|
-
chain.push(row)
|
|
2721
2733
|
if (Number(row.status) === 200) answered = true
|
|
2722
|
-
if (!reach
|
|
2734
|
+
if (!reach) reach = edge(row)
|
|
2723
2735
|
}
|
|
2724
2736
|
firstPass = false
|
|
2725
2737
|
}
|
|
@@ -2787,6 +2799,51 @@ function pinned(rows, account) {
|
|
|
2787
2799
|
return 0
|
|
2788
2800
|
}
|
|
2789
2801
|
|
|
2802
|
+
// pinning is pinned read a row at a time: it is handed the account's own
|
|
2803
|
+
// rows latest first, as readLedgerTail reads them, and answers each with what
|
|
2804
|
+
// pinned would answer for every row handed so far — in constant time.
|
|
2805
|
+
//
|
|
2806
|
+
// readLedgerTail used to call pinned on its whole chain after each row, and
|
|
2807
|
+
// pinned walks the chain, so a chain with no edge in it cost the square of
|
|
2808
|
+
// its rows: 10.5 s for 16,000 rows on mote, a minute and more for an account
|
|
2809
|
+
// a server spends on all day (a call a minute is 43,000 rows in a month).
|
|
2810
|
+
// That ran on the plugin host's one thread at every usage read, and the
|
|
2811
|
+
// ledger changes with every call, so nothing held it over: the host sat at
|
|
2812
|
+
// 99% CPU and every session through it stalled (xmoon76 on Discord, the
|
|
2813
|
+
// China build's window being read since 0.2.12).
|
|
2814
|
+
//
|
|
2815
|
+
// It holds because the rows only ever grow at their oldest end, and pinned
|
|
2816
|
+
// was asked only while it answered 0 — no readable refusal and no day's gap
|
|
2817
|
+
// among the rows so far — so the one new thing a row can bring is:
|
|
2818
|
+
//
|
|
2819
|
+
// - a refusal with a readable reset: the newest such is the first read,
|
|
2820
|
+
// and pinned answers it whatever else the rows hold, once a row that is
|
|
2821
|
+
// not a refusal follows (readLedgerTail asked pinned for those alone);
|
|
2822
|
+
// - a day's gap between it and the oldest answered row before it, the only
|
|
2823
|
+
// pair of answered rows not already looked at. pinned answers that with
|
|
2824
|
+
// the older row of the pair, this one.
|
|
2825
|
+
function pinning() {
|
|
2826
|
+
let denied = null // the newest readable refusal's reset
|
|
2827
|
+
let oldest = null // the oldest answered row's time so far
|
|
2828
|
+
return (row) => {
|
|
2829
|
+
const status = Number(row?.status)
|
|
2830
|
+
if (status === 429) {
|
|
2831
|
+
if (denied === null) {
|
|
2832
|
+
const reset = windowReset(row.err)
|
|
2833
|
+
if (Number.isFinite(reset)) denied = reset
|
|
2834
|
+
}
|
|
2835
|
+
return 0
|
|
2836
|
+
}
|
|
2837
|
+
if (denied !== null) return denied - FREE_WINDOW_LEAD
|
|
2838
|
+
if (status !== 200) return 0
|
|
2839
|
+
const t = Date.parse(row?.t)
|
|
2840
|
+
if (!Number.isFinite(t)) return 0
|
|
2841
|
+
if (oldest !== null && oldest - t >= FREE_WINDOW_GAP) return t - FREE_WINDOW_LEAD
|
|
2842
|
+
oldest = t
|
|
2843
|
+
return 0
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2790
2847
|
// _internal is where these are handed to a test: magpie's host calls every
|
|
2791
2848
|
// exported function of a plugin as one of its own factories (host.js,
|
|
2792
2849
|
// servers()), so freeWindowRows et al. must not be exports of the plugin.
|
|
@@ -3744,9 +3801,8 @@ function makePlugin(site) {
|
|
|
3744
3801
|
// the free model's own window, which the credit meter above does
|
|
3745
3802
|
// not show: what actually refuses a chat long before 160 credits
|
|
3746
3803
|
// are spent. Both builds: WorkBuddy AI at 1e8 (ten days of
|
|
3747
|
-
// refusals), the China build's
|
|
3748
|
-
//
|
|
3749
|
-
// window definitions above)
|
|
3804
|
+
// refusals), the China build's at about 2e8 (2026-11-02). Set
|
|
3805
|
+
// aside until the vendor refuses (see the window definitions above)
|
|
3750
3806
|
{
|
|
3751
3807
|
const account = a.accountId || a.user || a.uid || ""
|
|
3752
3808
|
const win = site.ai ? FREE_WINDOW_AI : FREE_WINDOW_CN
|
|
@@ -3896,4 +3952,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
3896
3952
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
3897
3953
|
|
|
3898
3954
|
// for tests
|
|
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 }
|
|
3955
|
+
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, 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 }
|
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.14",
|
|
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",
|