@magpie-community/opencode-zcode-auth 0.1.18 → 0.1.19

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 +15 -1
  2. package/index.mjs +71 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,7 +24,12 @@ over Anthropic's Messages API. Provider id: `zcode`.
24
24
  ZCode's does: ZCode's headers, ZCode's system prompt ahead of the
25
25
  agent's, the day in a `<system-reminder>` turn before the agent's
26
26
  turns, and the sign-in's device id in `metadata.user_id`. GLM Coding
27
- Plan and team requests go as the agent sent them.
27
+ Plan and team requests go as the agent sent them. One it still turns
28
+ away (a 405, the block page or code 3012) comes back with what to do
29
+ about it, as magpie's built-in said: "ZCode's Start Plan still turned
30
+ this request away, though magpie sends it as the ZCode app does; …
31
+ Use an account with a GLM Coding Plan, or add another provider to this
32
+ group".
28
33
  - The Start Plan's token can't be refreshed. When it runs out, sign in
29
34
  again.
30
35
  - **ZCode app's sign-in**
@@ -96,6 +101,15 @@ behind an error, so a gift hiccup must not black out a working Coding
96
101
  Plan. A gift that goes out of date while requests use it simply stops
97
102
  showing.
98
103
 
104
+ **Resets.** A GLM Coding Plan of the account's own, and a team seat,
105
+ show the resets the vendor's usage page lists
106
+ (`/api/biz/customer-package-reset/list`, `targetType=PERSONAL` with the
107
+ plan's key, `TEAM` with the team's sign-in): how many of the five hours
108
+ and of the week are still available, and when each runs out, soonest
109
+ first. A team's are marked a team's. They are only read; a reset is
110
+ spent on the vendor's own page. A list that can't be read, or has none
111
+ left, shows no count.
112
+
99
113
  **Gift plans ZCode holds but has not granted.** ZCode hands out one-off
100
114
  trust-build / gift plans (a day's ZCode Trust Build, say) that are not
101
115
  on the account until the ZCode app claims them. The plugin reads the
package/index.mjs CHANGED
@@ -436,9 +436,12 @@ async function codingUsage(s) {
436
436
  const level = typeof d.level === "string" ? d.level : ""
437
437
  if (level) out.plan = "GLM Coding " + level[0].toUpperCase() + level.slice(1)
438
438
  out.windows = limitWindows(d)
439
+ const resets = personalResets(root, s.key)
439
440
  try {
440
441
  Object.assign(out, termOf(await call("GET", root + "/api/biz/subscription/list", { auth: s.key })))
441
442
  } catch {}
443
+ const r = await resets
444
+ if (r) out.resets = r
442
445
  return out
443
446
  }
444
447
 
@@ -708,31 +711,56 @@ async function teamUsage(s, key) {
708
711
  }
709
712
 
710
713
  // teamResets is how many resets a team member may spend, of the five hours
711
- // and of the week: those of customer-package-reset/list still available.
712
- // null when it can't be told or there are none.
714
+ // and of the week, marked a team's (team: true), as the built-in's
715
+ // zhipuTeamResets did. null when it can't be told or there are none.
713
716
  async function teamResets(root, auth, headers) {
717
+ const r = await resetsOf(root + "/api/biz/customer-package-reset/list?targetType=TEAM", auth, headers)
718
+ if (r) r.team = true
719
+ return r
720
+ }
721
+
722
+ // personalResets is how many resets a GLM Coding Plan of a person's own
723
+ // holds, as bigmodel.cn's usage page lists them, asked with the plan's
724
+ // key (the built-in's zhipuPersonalResets). Only read: a reset is spent
725
+ // on the vendor's own page, never from here.
726
+ async function personalResets(root, key) {
727
+ return resetsOf(root + "/api/biz/customer-package-reset/list?targetType=PERSONAL", key)
728
+ }
729
+
730
+ // resetsOf reads customer-package-reset/list: the resets of the five
731
+ // hours and of the week, of which only those still available count (one
732
+ // run out stays listed a week, available false), each with its own end
733
+ // (each, soonest first, one with no end last) and the soonest as until.
734
+ // null when the list can't be read, so no count is shown rather than 0,
735
+ // and when it holds none.
736
+ async function resetsOf(url, auth, headers) {
714
737
  let d
715
738
  try {
716
- d = (await call("GET", root + "/api/biz/customer-package-reset/list?targetType=TEAM", { auth, headers })) ?? {}
739
+ d = (await call("GET", url, { auth, headers })) ?? {}
717
740
  } catch {
718
741
  return null
719
742
  }
720
743
  let until = 0
721
- const count = (rs) => {
744
+ const each = []
745
+ const count = (rs, window) => {
722
746
  let n = 0
723
747
  for (const x of Array.isArray(rs) ? rs : []) {
724
748
  if (x?.available !== true) continue
725
749
  n++
726
750
  const t = when(x.expireTime)
727
751
  if (t && (!until || t < until)) until = t
752
+ each.push(t ? { until: new Date(t).toISOString(), window } : { window })
728
753
  }
729
754
  return n
730
755
  }
731
- const fiveHour = count(d.fiveHourResets)
732
- const weekly = count(d.weekResets)
756
+ const fiveHour = count(d.fiveHourResets, "fiveHour")
757
+ const weekly = count(d.weekResets, "weekly")
733
758
  if (fiveHour + weekly === 0) return null
759
+ const end = (c) => (c.until ? Date.parse(c.until) : Infinity)
760
+ each.sort((a, b) => (end(a) === end(b) ? 0 : end(a) < end(b) ? -1 : 1))
734
761
  const r = { count: fiveHour + weekly, byWindow: true, fiveHour, weekly }
735
762
  if (until) r.until = new Date(until).toISOString()
763
+ r.each = each
736
764
  return r
737
765
  }
738
766
 
@@ -1311,6 +1339,36 @@ function model(m, url) {
1311
1339
  // the plan serves (ARNO's "Freeflow", provider zcode-start) sends it: its
1312
1340
  // headers (startHeaders) and its body (dress). The texts are prompt.mjs.
1313
1341
 
1342
+ // START_BLOCKED is what the Start Plan still turning a request away means,
1343
+ // word for word magpie's provider.ZCodeStartBlockedHint (#425), which
1344
+ // magpie's Routing page knows and says in the user's language. It is put
1345
+ // in place of the block page, whose words magpie would otherwise read as a
1346
+ // network firewall's (provider.BlockedHint).
1347
+ const START_BLOCKED = "ZCode's Start Plan still turned this request away, though magpie sends it as the ZCode app does; it can be a network block of this IP, or ZCode checking for something new. Use an account with a GLM Coding Plan, or add another provider to this group"
1348
+ const EDGE_BLOCKED = /request has been blocked|errors\.aliyun\.com/i
1349
+ const START_REFUSED = /unusual activity|"code"\s*:\s*"?3012\b/i
1350
+
1351
+ // startRefused says a Start Plan answer is that refusal, as the built-in's
1352
+ // zcodeStartExplain read it: a 405, a block page or code 3012.
1353
+ const startRefused = (status, text) => status >= 400 && (status === 405 || EDGE_BLOCKED.test(text) || START_REFUSED.test(text))
1354
+
1355
+ // startBlocked is the refusal's answer as the built-in passed it on: what
1356
+ // the vendor said (its status for a block page), then START_BLOCKED.
1357
+ function startBlocked(res, text) {
1358
+ let said = ""
1359
+ if (!EDGE_BLOCKED.test(text)) {
1360
+ try {
1361
+ const v = JSON.parse(text)
1362
+ said = first(v?.error?.message, typeof v?.error === "string" ? v.error : "", v?.message, v?.msg)
1363
+ } catch {}
1364
+ }
1365
+ said ||= `${res.status} ${STATUS_TEXT[res.status] ?? res.statusText}`.trim()
1366
+ const h = new Headers(res.headers)
1367
+ h.set("content-type", "application/json")
1368
+ const error = { type: "permission_error", message: `${said} — ${START_BLOCKED}` }
1369
+ return new Response(JSON.stringify({ type: "error", error }), { status: res.status, statusText: res.statusText, headers: h })
1370
+ }
1371
+
1314
1372
  const osVersion = () => `${process.platform} ${release()} ${arch()}`
1315
1373
  const osCategory = () => (process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux")
1316
1374
 
@@ -1594,6 +1652,13 @@ export async function ZCodeAuthPlugin({ client }) {
1594
1652
  h.set("Authorization", "Bearer " + key)
1595
1653
  }
1596
1654
  let res = await fetch(url, { ...opts, headers: h })
1655
+ // the Start Plan turning the request away says what to do about
1656
+ // it, as the built-in's zcodeStartExplain did
1657
+ if (start && res.status >= 400) {
1658
+ const text = await res.text()
1659
+ if (startRefused(res.status, text)) return kept(startBlocked(res, text))
1660
+ res = new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers })
1661
+ }
1597
1662
  // a trial entry's gift answer that can't serve the request is
1598
1663
  // the agent's answer, not the coding plan's: the user picked the
1599
1664
  // gift, so a spent bucket says 429 (its quota error inside a 200
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-zcode-auth",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
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",