@magpie-community/opencode-zcode-auth 0.1.17 → 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.
- package/README.md +15 -1
- package/index.mjs +95 -6
- 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:
|
|
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",
|
|
739
|
+
d = (await call("GET", url, { auth, headers })) ?? {}
|
|
717
740
|
} catch {
|
|
718
741
|
return null
|
|
719
742
|
}
|
|
720
743
|
let until = 0
|
|
721
|
-
const
|
|
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
|
|
|
@@ -1026,6 +1054,27 @@ function ownTeam(store, key) {
|
|
|
1026
1054
|
return null
|
|
1027
1055
|
}
|
|
1028
1056
|
|
|
1057
|
+
// configKey is the coding plan key ZCode's config.json gives its built-in
|
|
1058
|
+
// coding plan provider, Z.ai's first, as ZCode lists them; ZCode reads it
|
|
1059
|
+
// as it is, and one written encrypted is read too.
|
|
1060
|
+
function configKey(key) {
|
|
1061
|
+
const providers = readJSON(join(zcodeDir(), "config.json"))?.provider
|
|
1062
|
+
if (!providers || typeof providers !== "object") return null
|
|
1063
|
+
for (const [ids, base] of [
|
|
1064
|
+
[["builtin:zai-coding-plan", "account:zai-individual-coding-plan"], ZAI_BASE],
|
|
1065
|
+
[["builtin:bigmodel-coding-plan", "account:bigmodel-individual-coding-plan"], BIGMODEL_BASE],
|
|
1066
|
+
]) {
|
|
1067
|
+
for (const id of ids) {
|
|
1068
|
+
let k = providers[id]?.options?.apiKey
|
|
1069
|
+
if (typeof k !== "string") continue
|
|
1070
|
+
k = k.trim()
|
|
1071
|
+
if (k.startsWith("enc:")) k = first(decrypt(key, k))
|
|
1072
|
+
if (k.includes(".") && !/[\s{$]/.test(k)) return { key: k, base }
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return null
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1029
1078
|
// ownSignIn is what ZCode is signed in to now: the account, its coding
|
|
1030
1079
|
// plan key, ZCode's session token (the Start Plan's key) and the team plan
|
|
1031
1080
|
// it is switched to; null when it has none of them.
|
|
@@ -1043,6 +1092,9 @@ function ownSignIn() {
|
|
|
1043
1092
|
// Z.ai's first, as ZCode lists it
|
|
1044
1093
|
if (!s.key || (base === ZAI_BASE && s.base !== ZAI_BASE)) s = { key: k, base }
|
|
1045
1094
|
}
|
|
1095
|
+
// ZCode 3.15 keeps no plan key in its credentials; the one it uses sits
|
|
1096
|
+
// in config.json, the provider's options.apiKey (yetone/magpie#2130)
|
|
1097
|
+
if (!s.key) s = configKey(key) || s
|
|
1046
1098
|
const jwt = first(first(decrypt(key, store.zcodejwttoken)).replace(/^Bearer /, ""))
|
|
1047
1099
|
const team = ownTeam(store, key)
|
|
1048
1100
|
if (team) s = { ...team, jwt }
|
|
@@ -1287,6 +1339,36 @@ function model(m, url) {
|
|
|
1287
1339
|
// the plan serves (ARNO's "Freeflow", provider zcode-start) sends it: its
|
|
1288
1340
|
// headers (startHeaders) and its body (dress). The texts are prompt.mjs.
|
|
1289
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
|
+
|
|
1290
1372
|
const osVersion = () => `${process.platform} ${release()} ${arch()}`
|
|
1291
1373
|
const osCategory = () => (process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux")
|
|
1292
1374
|
|
|
@@ -1570,6 +1652,13 @@ export async function ZCodeAuthPlugin({ client }) {
|
|
|
1570
1652
|
h.set("Authorization", "Bearer " + key)
|
|
1571
1653
|
}
|
|
1572
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
|
+
}
|
|
1573
1662
|
// a trial entry's gift answer that can't serve the request is
|
|
1574
1663
|
// the agent's answer, not the coding plan's: the user picked the
|
|
1575
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.
|
|
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",
|