@magpie-community/opencode-zcode-auth 0.1.8 → 0.1.9
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 +25 -18
- package/index.mjs +133 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,30 +54,32 @@ Before each request, the plugin checks which plan the account is on, and
|
|
|
54
54
|
asks again every 10 minutes:
|
|
55
55
|
|
|
56
56
|
- If the key's account has a GLM Coding Plan
|
|
57
|
-
(`/api/biz/subscription/list`), its requests go to that plan
|
|
58
|
-
|
|
59
|
-
the gift
|
|
57
|
+
(`/api/biz/subscription/list`), its requests go to that plan. A model
|
|
58
|
+
the account's gift plans also serve is listed again as
|
|
59
|
+
`<model>-Trial`; asking that entry spends the gift (see below).
|
|
60
60
|
- Otherwise they go to the gift plans.
|
|
61
61
|
|
|
62
62
|
Every request carries the key as both `x-api-key` and
|
|
63
63
|
`Authorization: Bearer`.
|
|
64
64
|
|
|
65
|
-
**
|
|
66
|
-
hold gift plans from ZCode activities, ones that expire on a fixed
|
|
67
|
-
Their allowance would lapse unused if every request went to the
|
|
68
|
-
plan, so the plugin reads the gift's balance (per the sign-in's
|
|
69
|
-
kept for 10 minutes) and
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
the
|
|
74
|
-
|
|
65
|
+
**The gift's trial entries.** An account with a GLM Coding Plan can
|
|
66
|
+
also hold gift plans from ZCode activities, ones that expire on a fixed
|
|
67
|
+
date. Their allowance would lapse unused if every request went to the
|
|
68
|
+
coding plan, so the plugin reads the gift's balance (per the sign-in's
|
|
69
|
+
session, kept for 10 minutes) and lists every model a live bucket
|
|
70
|
+
serves again as `<model>-Trial`, named in the catalog's spelling
|
|
71
|
+
(GLM-5.3-Flash-Trial). Asking a trial entry sends the model to the
|
|
72
|
+
gift, dressed as ZCode's own; asking the plain model spends the coding
|
|
73
|
+
plan — the choice is the user's. A bucket is judged spent only by its
|
|
74
|
+
`remaining_units` — a missing one is unknown, not spent. A trial entry
|
|
75
|
+
whose bucket is spent or blocked answers 429 without touching the
|
|
76
|
+
coding plan; when the gift answers a trial entry with a quota error
|
|
75
77
|
(code 1113 "Insufficient balance or no resource package", code 1005
|
|
76
|
-
"exceed quota limit", which it can answer inside an HTTP 200)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
"exceed quota limit", which it can answer inside an HTTP 200) that too
|
|
79
|
+
becomes a 429 and keeps the entry off for a minute. Any other gift
|
|
80
|
+
answer (its 405, its 401, its 5xx) goes on as it came: the user picked
|
|
81
|
+
the gift, so the coding plan is not spent behind their back. Every 429
|
|
82
|
+
counts as spent, Z.ai's 1302/1303 rate limits among them. A stream's
|
|
81
83
|
body is never read or buffered on the way.
|
|
82
84
|
|
|
83
85
|
## The usage card
|
|
@@ -105,3 +107,8 @@ minutes. When the config can't be read, the plugin falls back to this list:
|
|
|
105
107
|
| GLM-5-Turbo | 200K | 64K | none, high |
|
|
106
108
|
|
|
107
109
|
The Start Plan has all of these except GLM-5.3.
|
|
110
|
+
|
|
111
|
+
A gift plan's models appear again as `<model>-Trial` entries, so a
|
|
112
|
+
request can spend the gift's quota on purpose. A trial entry is listed
|
|
113
|
+
while any live bucket serves its model, spent buckets included (a spent
|
|
114
|
+
entry answers 429 rather than vanishing mid-session).
|
package/index.mjs
CHANGED
|
@@ -130,6 +130,29 @@ const num = (v) => {
|
|
|
130
130
|
return typeof n === "number" && Number.isFinite(n) ? n : undefined
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// the alias is the catalog spelling of the model plus -trial, so the picker
|
|
134
|
+
// reads GLM-5.3-Trial while the loader strips it back to the raw name
|
|
135
|
+
const giftID = (m) => (MODELS.find((x) => x.id.toLowerCase() === String(m).toLowerCase())?.id ?? m) + "-Trial"
|
|
136
|
+
const uniqueModels = (names) => {
|
|
137
|
+
const byName = new Map()
|
|
138
|
+
for (const m of names) if (!byName.has(m.toLowerCase())) byName.set(m.toLowerCase(), m)
|
|
139
|
+
return [...byName.values()]
|
|
140
|
+
}
|
|
141
|
+
const giftNames = (g) => g?.allModels ?? g?.models ?? []
|
|
142
|
+
const giftModel = (g, id) => giftNames(g).find((m) => giftID(m).toLowerCase() === id.toLowerCase())
|
|
143
|
+
const bucketModels = (x) => (Array.isArray(x.capabilities) ? x.capabilities : [])
|
|
144
|
+
.map((c) => String(c ?? "").trim()).filter((c) => c.startsWith("model:") || !c.includes(":"))
|
|
145
|
+
.map((c) => c.replace(/^model:/, "").trim()).filter(Boolean)
|
|
146
|
+
|
|
147
|
+
// Both the card and the request route use the same test for a spent bucket.
|
|
148
|
+
// Older balances may omit remaining_units but still tell used and total.
|
|
149
|
+
const giftSpent = (x) => {
|
|
150
|
+
const left = num(x.remaining_units)
|
|
151
|
+
if (left !== undefined) return left <= 0
|
|
152
|
+
const total = num(x.total_units), used = num(x.used_units)
|
|
153
|
+
return total !== undefined && used !== undefined && used >= total
|
|
154
|
+
}
|
|
155
|
+
|
|
133
156
|
// startPlan is the gift an account can spend now ({models}), as ZCode reads
|
|
134
157
|
// its balance: several plans can be active at once, and a model routes to
|
|
135
158
|
// the gift when any of them has a live bucket serving it — a bucket past
|
|
@@ -143,7 +166,8 @@ async function startPlan(jwt, device) {
|
|
|
143
166
|
const same = (x, p) => (x.user_plan_id && p.user_plan_id ? x.user_plan_id === p.user_plan_id : String(x.plan_id ?? "") === String(p.plan_id ?? ""))
|
|
144
167
|
const plans = Array.isArray(b.plans) ? b.plans : []
|
|
145
168
|
const balances = Array.isArray(b.balances) ? b.balances : []
|
|
146
|
-
const ms = new Set()
|
|
169
|
+
const ms = new Set(), all = new Map()
|
|
170
|
+
let ttl = 600_000
|
|
147
171
|
let any = false
|
|
148
172
|
let name = ""
|
|
149
173
|
for (const p of plans) {
|
|
@@ -151,22 +175,21 @@ async function startPlan(jwt, device) {
|
|
|
151
175
|
const end = num(p?.ends_at)
|
|
152
176
|
if (st !== "active" || (end > 0 && end <= now)) continue
|
|
153
177
|
any = true
|
|
178
|
+
if (end > 0) ttl = Math.min(ttl, (end - now) * 1000)
|
|
154
179
|
if (!name) name = String(p?.name ?? "").trim()
|
|
155
180
|
for (const x of balances) {
|
|
156
181
|
if (!same(x, p)) continue
|
|
157
182
|
const exp = num(x.expires_at)
|
|
158
183
|
if (exp > 0 && exp <= now) continue
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const m = String(c ?? "").trim().replace(/^model:/, "").trim()
|
|
165
|
-
if (m) ms.add(m)
|
|
184
|
+
if (exp > 0) ttl = Math.min(ttl, (exp - now) * 1000)
|
|
185
|
+
// Spent buckets still declare models; only live ones can spend now.
|
|
186
|
+
for (const m of bucketModels(x)) {
|
|
187
|
+
if (!all.has(m.toLowerCase())) all.set(m.toLowerCase(), m)
|
|
188
|
+
if (!giftSpent(x)) ms.add(m)
|
|
166
189
|
}
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
|
-
return any ? { name: name || "Start Plan", models: [...ms] } : null
|
|
192
|
+
return any ? { name: name || "Start Plan", models: [...ms], allModels: [...all.values()], ttl } : null
|
|
170
193
|
}
|
|
171
194
|
|
|
172
195
|
// plansOf says where an account's requests go and what more it can spend:
|
|
@@ -202,7 +225,7 @@ async function plansOf(s) {
|
|
|
202
225
|
sure = false
|
|
203
226
|
} catch {}
|
|
204
227
|
}
|
|
205
|
-
const out = { start, gift, at: Date.now(), ttl: sure ? 600_000 : 60_000 }
|
|
228
|
+
const out = { start, gift, at: Date.now(), ttl: Math.min(sure ? 600_000 : 60_000, gift?.ttl ?? 600_000) }
|
|
206
229
|
routes.set(id, out)
|
|
207
230
|
return out
|
|
208
231
|
}
|
|
@@ -214,6 +237,8 @@ const blocked = new Map()
|
|
|
214
237
|
// giftServes: the gift's buckets name their models lowercased ("glm-5.3-
|
|
215
238
|
// flash"); what is asked for arrives as the plan lists it ("GLM-5.3-Flash").
|
|
216
239
|
const giftServes = (gift, m) => (gift.models ?? []).some((x) => x.toLowerCase() === m.toLowerCase())
|
|
240
|
+
const giftUsable = (s, gift, m) => !jwtExpired(s.jwt) && giftServes(gift, m) &&
|
|
241
|
+
Date.now() >= (blocked.get(s.key + "\0" + s.jwt + "\0" + m.toLowerCase()) ?? 0)
|
|
217
242
|
|
|
218
243
|
// spentUp is the gift saying it has nothing left to spend: its own no-
|
|
219
244
|
// resource-package code 1113, its "exceed quota limit" 1005 — a spent gift
|
|
@@ -478,15 +503,15 @@ async function giftOf(s) {
|
|
|
478
503
|
const total = startNum(x.total_units), left = startNum(x.remaining_units)
|
|
479
504
|
let used = startNum(x.used_units)
|
|
480
505
|
if (total === undefined && used === undefined && left === undefined) continue
|
|
481
|
-
const models = (
|
|
482
|
-
.map((c) => String(c ?? "").trim().replace(/^model:/, "").trim()).filter(Boolean)
|
|
506
|
+
const models = bucketModels(x)
|
|
483
507
|
const base = first(str(x.show_name), models.join(", "), "Credits")
|
|
484
|
-
const w = { name: base, used: 0, _plan: str(owner.name) }
|
|
508
|
+
const w = { name: base, used: 0, _plan: str(owner.name), _spent: giftSpent(x) }
|
|
485
509
|
if (used === undefined) used = total !== undefined && left !== undefined ? total - left : 0
|
|
486
510
|
if (total > 0) {
|
|
487
511
|
w.used = (100 * used) / total
|
|
488
512
|
w.display = `${compact(used)} / ${compact(total)}`
|
|
489
513
|
}
|
|
514
|
+
if (w._spent) w.used = Math.max(100, w.used)
|
|
490
515
|
if (exp > 0) w.resetsAt = new Date(Math.trunc(exp) * 1000).toISOString()
|
|
491
516
|
let span = 0
|
|
492
517
|
for (const p of plans) {
|
|
@@ -513,27 +538,67 @@ const NO_START = "this account has no GLM Coding Plan, and ZCode's Start Plan ha
|
|
|
513
538
|
async function startUsage(s) {
|
|
514
539
|
const g = await giftOf(s)
|
|
515
540
|
if (!g) return { error: NO_START }
|
|
516
|
-
for (const w of g.windows)
|
|
541
|
+
for (const w of g.windows) {
|
|
542
|
+
if (w.models?.length) w.models = [...w.models, ...w.models.map(giftID)]
|
|
543
|
+
delete w._plan
|
|
544
|
+
delete w._spent
|
|
545
|
+
}
|
|
517
546
|
return g
|
|
518
547
|
}
|
|
519
548
|
|
|
520
|
-
// dualUsage
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
// gift
|
|
525
|
-
//
|
|
526
|
-
// so erroring here would hide a working Coding Plan over a gift hiccup.
|
|
549
|
+
// dualUsage shows both pools but only the pool a request can use counts
|
|
550
|
+
// for its model. Live gift models don't spend the Coding windows; spent
|
|
551
|
+
// or blocked gift buckets stay visible without holding up the Coding Plan.
|
|
552
|
+
// Fresh usage also updates the request cache, so a replenished or spent
|
|
553
|
+
// gift takes effect before its ten-minute route cache expires. A failed
|
|
554
|
+
// gift read leaves the working Coding Plan's card and route alone.
|
|
527
555
|
async function dualUsage(s) {
|
|
528
556
|
const out = await codingUsage(s)
|
|
557
|
+
const cached = routes.get(s.key + "\0" + s.jwt)
|
|
529
558
|
try {
|
|
530
559
|
const g = await giftOf(s)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
560
|
+
const gift = g ? { name: g.plan, models: [...new Set(g.windows.filter((w) => !w._spent)
|
|
561
|
+
.flatMap((w) => w.models ?? []).map((m) => m.toLowerCase()))],
|
|
562
|
+
allModels: uniqueModels(g.windows.flatMap((w) => w.models ?? [])) } : null
|
|
563
|
+
if (cached) {
|
|
564
|
+
cached.gift = gift
|
|
565
|
+
const ends = g ? [g.until, ...g.windows.map((w) => w.resetsAt)].map((t) => Date.parse(t)).filter(Number.isFinite) : []
|
|
566
|
+
if (ends.length) cached.ttl = Math.min(cached.ttl, Math.max(0, Math.min(...ends) - cached.at))
|
|
567
|
+
}
|
|
568
|
+
if (g) {
|
|
569
|
+
// the gift's models are its trial entries alone: a plain model stays
|
|
570
|
+
// on the coding plan, and the user picks the gift by its entry
|
|
571
|
+
const notModels = gift.allModels.map((m) => giftID(m).toLowerCase())
|
|
572
|
+
if (notModels.length) out.windows = out.windows.map((w) => w.aside ? w : { ...w, notModels })
|
|
573
|
+
// Several buckets may serve one model: one spent sibling cannot block it.
|
|
574
|
+
const best = new Map()
|
|
575
|
+
g.windows.forEach((w, i) => {
|
|
576
|
+
for (const m of w.models ?? []) {
|
|
577
|
+
const key = m.toLowerCase(), old = best.get(key), prev = g.windows[old]
|
|
578
|
+
if (old === undefined || (prev._spent && !w._spent) || (prev._spent === w._spent && w.used < prev.used)) best.set(key, i)
|
|
579
|
+
}
|
|
580
|
+
})
|
|
581
|
+
out.windows.push(...g.windows.map(({ _plan: p, _spent, ...w }, i) => {
|
|
582
|
+
const selected = gift.allModels.filter((m) => best.get(m.toLowerCase()) === i)
|
|
583
|
+
const models = selected.map(giftID)
|
|
584
|
+
const scoped = models.length ? { ...w, models } : { ...w, aside: true }
|
|
585
|
+
// the card names a bucket's model as the picker does (GLM-5.3-Flash-Trial);
|
|
586
|
+
// no plan prefix — the card's section header already says the account
|
|
587
|
+
let name = w.name
|
|
588
|
+
for (const m of selected) {
|
|
589
|
+
const at = name.toLowerCase().indexOf(m.toLowerCase())
|
|
590
|
+
if (at >= 0) name = name.slice(0, at) + giftID(m) + name.slice(at + m.length)
|
|
591
|
+
}
|
|
592
|
+
return { ...scoped, name }
|
|
593
|
+
}))
|
|
594
|
+
}
|
|
595
|
+
} catch {
|
|
596
|
+
if (cached) {
|
|
597
|
+
cached.gift = null
|
|
598
|
+
cached.at = Date.now()
|
|
599
|
+
cached.ttl = 60_000 // an uncertain gift is read again sooner
|
|
600
|
+
}
|
|
601
|
+
}
|
|
537
602
|
return out
|
|
538
603
|
}
|
|
539
604
|
|
|
@@ -1285,15 +1350,24 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1285
1350
|
async models(provider, { auth } = {}) {
|
|
1286
1351
|
const s = stateOf(auth)
|
|
1287
1352
|
if (!s) return provider.models
|
|
1288
|
-
const start = await plansOf(s).
|
|
1353
|
+
const { start, gift: known } = await plansOf(s).catch(() => ({ start: false, gift: null }))
|
|
1354
|
+
const gift = start ? await startPlan(s.jwt, s.device).catch(() => null) : known
|
|
1289
1355
|
const base = start ? START_BASE : s.base
|
|
1290
|
-
|
|
1356
|
+
const cfg = await zcodeConfig().catch(() => null)
|
|
1357
|
+
let ms = modelsOf(cfg, planID(base))
|
|
1291
1358
|
// ZCode's config can't be had: its table stands in, and magpie keeps
|
|
1292
1359
|
// the list it was told last, as the built-in keeps the one it fetched last
|
|
1293
1360
|
const fell = !ms.length
|
|
1294
1361
|
if (fell) ms = start ? START_MODELS : MODELS
|
|
1295
1362
|
const url = s.base + "/v1"
|
|
1296
1363
|
const out = Object.fromEntries(ms.map((m) => [m.id, model(m, url)]))
|
|
1364
|
+
const details = [...ms, ...modelsOf(cfg, planID(START_BASE)), ...START_MODELS]
|
|
1365
|
+
for (const raw of giftNames(gift)) {
|
|
1366
|
+
const id = giftID(raw)
|
|
1367
|
+
if (out[id]) continue
|
|
1368
|
+
const m = details.find((m) => m.id.toLowerCase() === raw.toLowerCase()) ?? { efforts: [] }
|
|
1369
|
+
out[id] = model({ ...m, id }, url)
|
|
1370
|
+
}
|
|
1297
1371
|
if (fell) out[Symbol.for("magpie.fellBack")] = true
|
|
1298
1372
|
return out
|
|
1299
1373
|
},
|
|
@@ -1313,7 +1387,8 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1313
1387
|
if (!s) throw new Error("not signed in to ZCode")
|
|
1314
1388
|
let url = input instanceof Request ? input.url : String(input)
|
|
1315
1389
|
const opts = input instanceof Request ? { method: input.method, headers: input.headers, body: input.body, signal: input.signal, duplex: "half", ...init } : { ...init }
|
|
1316
|
-
const { start: alone, gift } = await plansOf(s)
|
|
1390
|
+
const { start: alone, gift: known } = await plansOf(s)
|
|
1391
|
+
let gift = known
|
|
1317
1392
|
let key = s.key
|
|
1318
1393
|
if (isTeam(s) && !key) {
|
|
1319
1394
|
key = await teamKey(s)
|
|
@@ -1325,14 +1400,24 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1325
1400
|
// a dual account's body is read ahead of sending, so a gift that
|
|
1326
1401
|
// says it is spent can spend nothing and replay what was asked for
|
|
1327
1402
|
let start = alone
|
|
1328
|
-
let orig
|
|
1329
|
-
if (
|
|
1403
|
+
let orig, forcedGift = false
|
|
1404
|
+
if (opts.body != null) {
|
|
1330
1405
|
orig = opts.body = typeof opts.body === "string" ? opts.body : await new Response(opts.body).text()
|
|
1331
1406
|
delete opts.duplex
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1407
|
+
let m = modelOf(orig)
|
|
1408
|
+
if (m.toLowerCase().endsWith("-trial")) {
|
|
1409
|
+
gift ??= await startPlan(s.jwt, s.device).catch(() => null)
|
|
1410
|
+
const raw = giftModel(gift, m)
|
|
1411
|
+
const available = raw && giftUsable(s, gift, raw)
|
|
1412
|
+
if (!available) {
|
|
1413
|
+
const error = { type: raw ? "rate_limit_error" : "invalid_request_error", message: raw ? `ZCode's limited quota for ${raw} is exhausted or temporarily unavailable` : `ZCode has no active limited quota for ${m}` }
|
|
1414
|
+
return kept(new Response(JSON.stringify({ type: "error", error }), { status: raw ? 429 : 400, headers: { "content-type": "application/json" } }))
|
|
1415
|
+
}
|
|
1416
|
+
orig = opts.body = JSON.stringify({ ...JSON.parse(orig), model: raw })
|
|
1417
|
+
m = raw
|
|
1418
|
+
forcedGift = true
|
|
1419
|
+
}
|
|
1420
|
+
start = alone || forcedGift
|
|
1336
1421
|
}
|
|
1337
1422
|
const swap = (base) => {
|
|
1338
1423
|
for (const b of [START_BASE, ZAI_BASE, BIGMODEL_BASE]) {
|
|
@@ -1363,18 +1448,13 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1363
1448
|
h.set("Authorization", "Bearer " + key)
|
|
1364
1449
|
}
|
|
1365
1450
|
let res = await fetch(url, { ...opts, headers: h })
|
|
1366
|
-
// a
|
|
1367
|
-
//
|
|
1368
|
-
//
|
|
1369
|
-
// and
|
|
1370
|
-
//
|
|
1371
|
-
//
|
|
1372
|
-
|
|
1373
|
-
// Every 429 counts as spent, Z.ai's 1302/1303 rate limits among
|
|
1374
|
-
// them — replaying one to the coding plan is the good answer, and
|
|
1375
|
-
// the minute's block keeps a rate limit from being knocked twice.
|
|
1376
|
-
// A stream's body is never read here.
|
|
1377
|
-
if (start && !alone) {
|
|
1451
|
+
// a trial entry's gift answer that can't serve the request is
|
|
1452
|
+
// the agent's answer, not the coding plan's: the user picked the
|
|
1453
|
+
// gift, so a spent bucket says 429 (its quota error inside a 200
|
|
1454
|
+
// among them) and anything else goes on as it came. A quota
|
|
1455
|
+
// answer keeps that model off the gift for a minute, so a rate
|
|
1456
|
+
// limit is not knocked twice. A stream's body is never read here.
|
|
1457
|
+
if (start && forcedGift) {
|
|
1378
1458
|
const json = (res.headers.get("content-type") ?? "").includes("application/json")
|
|
1379
1459
|
if (res.status >= 400 || json) {
|
|
1380
1460
|
const text = await res.text()
|
|
@@ -1382,14 +1462,9 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1382
1462
|
if (!spent && res.status < 400) return kept(res, text)
|
|
1383
1463
|
if (spent) {
|
|
1384
1464
|
for (const [k, until] of blocked) if (Date.now() >= until) blocked.delete(k)
|
|
1385
|
-
blocked.set(s.key + "\0" + s.jwt + "\0" + modelOf(orig), Date.now() + 60_000)
|
|
1465
|
+
blocked.set(s.key + "\0" + s.jwt + "\0" + modelOf(orig).toLowerCase(), Date.now() + 60_000)
|
|
1386
1466
|
}
|
|
1387
|
-
|
|
1388
|
-
h = new Headers(opts.headers)
|
|
1389
|
-
h.delete("authorization")
|
|
1390
|
-
h.set("x-api-key", key)
|
|
1391
|
-
h.set("Authorization", "Bearer " + key)
|
|
1392
|
-
res = await fetch(url, { ...opts, body: orig, headers: h })
|
|
1467
|
+
return spent && res.status < 400 ? kept(new Response(text, { status: 429, headers: res.headers })) : kept(res, text)
|
|
1393
1468
|
}
|
|
1394
1469
|
}
|
|
1395
1470
|
// the plan's answer goes on as it came, the account kept: the
|
|
@@ -1411,6 +1486,9 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1411
1486
|
const saved = (out) => {
|
|
1412
1487
|
const plan = s.plan || auth?.plan
|
|
1413
1488
|
if (plan && !out.plan) out.plan = plan
|
|
1489
|
+
// MCP reads as a footnote: its window goes last on the card
|
|
1490
|
+
if (Array.isArray(out.windows) && out.windows.some((w) => w.name === "MCP · Month"))
|
|
1491
|
+
out.windows = [...out.windows.filter((w) => w.name !== "MCP · Month"), ...out.windows.filter((w) => w.name === "MCP · Month")]
|
|
1414
1492
|
out.signIn = "kept"
|
|
1415
1493
|
return out
|
|
1416
1494
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-zcode-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "OpenCode provider plugin: signs in to Z.ai's or BigModel's (智谱) GLM Coding Plan as ZCode does, including team seats and ZCode's Start Plan",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|