@magpie-community/opencode-workbuddy-auth 0.1.4 → 0.1.6
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/index.mjs +51 -17
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -86,10 +86,11 @@ const api = (site) => site.endpoint + "/v2"
|
|
|
86
86
|
// ---- WorkBuddy's API ------------------------------------------------------
|
|
87
87
|
|
|
88
88
|
class WBError extends Error {
|
|
89
|
-
constructor(message, code, status) {
|
|
89
|
+
constructor(message, code, status, said) {
|
|
90
90
|
super(message)
|
|
91
91
|
this.code = code
|
|
92
92
|
this.status = status
|
|
93
|
+
this.said = said // as magpie's built-in (wbCall) worded it
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -112,11 +113,13 @@ async function call(site, method, path, headers = {}, body) {
|
|
|
112
113
|
try {
|
|
113
114
|
env = JSON.parse(text)
|
|
114
115
|
} catch {}
|
|
116
|
+
const code = Number.parseInt(env.code, 10) || 0
|
|
115
117
|
if (!res.ok) {
|
|
116
118
|
const why = env.msg || env.message || env.error?.message || text.slice(0, 200) || res.statusText
|
|
117
|
-
|
|
119
|
+
const said = code ? env.msg || `error ${code}` : env.msg || statusText(res.status)
|
|
120
|
+
throw new WBError(`${site.name}: ${why} (HTTP ${res.status}${env.code ? ", code " + env.code : ""})`, env.code, res.status, said)
|
|
118
121
|
}
|
|
119
|
-
if (env.code) throw new WBError(`${site.name}: ${env.msg || "error"} (${env.code})`, env.code, res.status)
|
|
122
|
+
if (env.code) throw new WBError(`${site.name}: ${env.msg || "error"} (${env.code})`, env.code, res.status, env.msg || `error ${code}`)
|
|
120
123
|
return env.data
|
|
121
124
|
}
|
|
122
125
|
|
|
@@ -145,13 +148,14 @@ function stale(a) {
|
|
|
145
148
|
|
|
146
149
|
// fresh is the sign-in with an access token that isn't about to end:
|
|
147
150
|
// refreshed (and saved) when it is. A refresh that fails, or a refresh
|
|
148
|
-
// token that has ended, leaves the access token there is.
|
|
151
|
+
// token that has ended, leaves the access token there is. Its failures
|
|
152
|
+
// are worded as the built-in's (wbFresh), "WorkBuddy" on both sites.
|
|
149
153
|
async function fresh(site, client, a) {
|
|
150
154
|
if (!stale(a)) return a
|
|
151
155
|
const refreshable = a.refresh && !(a.refreshExpiresAt && Date.now() >= a.refreshExpiresAt)
|
|
152
156
|
if (!refreshable) {
|
|
153
157
|
if (a.access) return a
|
|
154
|
-
throw new Error(
|
|
158
|
+
throw new Error("this WorkBuddy account is signed out; sign in again")
|
|
155
159
|
}
|
|
156
160
|
try {
|
|
157
161
|
const got = await call(site, "POST", "/v2/plugin/auth/token/refresh", {
|
|
@@ -159,13 +163,14 @@ async function fresh(site, client, a) {
|
|
|
159
163
|
"X-Auth-Refresh-Source": "plugin",
|
|
160
164
|
"X-Domain": domainOf(site, a),
|
|
161
165
|
}, {})
|
|
162
|
-
if (!got?.accessToken) throw new Error(
|
|
166
|
+
if (!got?.accessToken) throw Object.assign(new Error("WorkBuddy gave no refreshed token"), { bare: true })
|
|
163
167
|
const next = merge(a, got)
|
|
164
168
|
await client?.auth?.set?.({ path: { id: site.id }, body: next })
|
|
165
169
|
return next
|
|
166
170
|
} catch (e) {
|
|
167
171
|
if (a.access) return a
|
|
168
|
-
|
|
172
|
+
if (e?.bare) throw e
|
|
173
|
+
throw new Error(`WorkBuddy token refresh: ${e?.said ?? e?.message ?? e}`)
|
|
169
174
|
}
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -460,18 +465,29 @@ function modelOf(site, provider, m) {
|
|
|
460
465
|
variants: variants(m.efforts),
|
|
461
466
|
// magpie's own: served at no cost to the plan's credits
|
|
462
467
|
free: !!m.free,
|
|
468
|
+
// magpie's own: the credits a request costs, as a multiple (0: none)
|
|
469
|
+
rate: m.rate ?? 0,
|
|
463
470
|
}
|
|
464
471
|
}
|
|
465
472
|
|
|
466
473
|
// liveModels asks WorkBuddy's product config for the plan's models: the
|
|
467
474
|
// "cli" agent's list, each with its details. The CLI User-Agent picks
|
|
468
475
|
// WorkBuddy's config (a bare WorkBuddy/<v> gets CodeBuddy IDE's, no cli).
|
|
476
|
+
// creditsOf reads a model's credits, the multiple WorkBuddy's picker shows
|
|
477
|
+
// by it: "x0.03", "0" or 0; "x0.00" is free. A rate it can't read (or
|
|
478
|
+
// none, or an infinite one) is undefined: the model is neither free nor
|
|
479
|
+
// rated. The same rule as magpie's built-in (wbCredits).
|
|
480
|
+
function creditsOf(c) {
|
|
481
|
+
if (c == null || c === "") return undefined
|
|
482
|
+
const s = String(c).trim().toLowerCase().replace(/^x/, "")
|
|
483
|
+
const n = Number(s)
|
|
484
|
+
return s !== "" && Number.isFinite(n) && n >= 0 ? n : undefined
|
|
485
|
+
}
|
|
486
|
+
|
|
469
487
|
// freeCredits says whether a model's credits are none: "x0.00", "0" or 0.
|
|
470
488
|
// A rate it can't read is not free.
|
|
471
489
|
function freeCredits(c) {
|
|
472
|
-
|
|
473
|
-
const s = String(c).trim().toLowerCase().replace(/^x/, "")
|
|
474
|
-
return s !== "" && Number.isFinite(Number(s)) && Number(s) === 0
|
|
490
|
+
return creditsOf(c) === 0
|
|
475
491
|
}
|
|
476
492
|
|
|
477
493
|
async function liveModels(site, a) {
|
|
@@ -495,7 +511,11 @@ async function liveModels(site, a) {
|
|
|
495
511
|
m.context = d.maxInputTokens
|
|
496
512
|
m.output = d.maxOutputTokens
|
|
497
513
|
m.images = d.supportsImages === true
|
|
498
|
-
|
|
514
|
+
const r = creditsOf(d.credits)
|
|
515
|
+
if (r !== undefined) {
|
|
516
|
+
m.free = r === 0
|
|
517
|
+
m.rate = r
|
|
518
|
+
}
|
|
499
519
|
const es = d.reasoning?.supportedEfforts ?? []
|
|
500
520
|
if (es.length) {
|
|
501
521
|
const canOff = d.reasoning?.canDisableThinking
|
|
@@ -570,20 +590,22 @@ function makePlugin(site) {
|
|
|
570
590
|
let body = init?.body
|
|
571
591
|
if (body === undefined && req) body = await req.clone().text()
|
|
572
592
|
const res = await fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body: withSystem(body) })
|
|
573
|
-
return res.status >= 400 ? explained(res) : res
|
|
593
|
+
return res.status >= 400 ? explained(res) : kept(res)
|
|
574
594
|
},
|
|
575
595
|
}
|
|
576
596
|
},
|
|
577
|
-
// the account's credits, as magpie's built-in showed them
|
|
597
|
+
// the account's credits, as magpie's built-in showed them; each
|
|
598
|
+
// read keeps the sign-in (signIn), as the built-in's neither marked
|
|
599
|
+
// an account lapsed nor cleared it
|
|
578
600
|
async usage(getAuth) {
|
|
579
601
|
const auth = await getAuth()
|
|
580
|
-
if (auth?.type !== "oauth" || !(auth.access || auth.source)) return { error: "not signed in" }
|
|
602
|
+
if (auth?.type !== "oauth" || !(auth.access || auth.source)) return { error: "not signed in", signIn: "kept" }
|
|
581
603
|
try {
|
|
582
604
|
const a = await current(site, client, auth)
|
|
583
605
|
// the meter has no /v2 prefix, on either site
|
|
584
|
-
return usageOf(await meter(site, a, "/billing/meter/get-user-resource-summary"), auth.plan)
|
|
606
|
+
return { ...usageOf(await meter(site, a, "/billing/meter/get-user-resource-summary"), auth.plan), signIn: "kept" }
|
|
585
607
|
} catch (e) {
|
|
586
|
-
return { error: e?.message ?? String(e) }
|
|
608
|
+
return { error: e?.message ?? String(e), signIn: "kept" }
|
|
587
609
|
}
|
|
588
610
|
},
|
|
589
611
|
methods: [
|
|
@@ -611,13 +633,24 @@ const REFUSED_HINT =
|
|
|
611
633
|
"WorkBuddy refuses chats from Codex and Claude Code (their system prompt); use it from Hermes, OpenCode or Pi, or add another provider to this group"
|
|
612
634
|
const REFUSED = /unapproved channel|illegal api invocation/i
|
|
613
635
|
|
|
636
|
+
// kept is an answer that went through, saying the sign-in is kept: the
|
|
637
|
+
// built-in never cleared a WorkBuddy account's mark, as it never set one.
|
|
638
|
+
function kept(res) {
|
|
639
|
+
const headers = new Headers(res.headers)
|
|
640
|
+
headers.set("X-Magpie-Sign-In", "kept")
|
|
641
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
|
|
642
|
+
}
|
|
643
|
+
|
|
614
644
|
// explained is res with REFUSED_HINT added to that refusal's message.
|
|
645
|
+
// Each refusal says the sign-in is kept: the built-in passed WorkBuddy's
|
|
646
|
+
// 401 on and never marked a WorkBuddy account lapsed.
|
|
615
647
|
async function explained(res) {
|
|
616
648
|
const text = await res.text()
|
|
617
649
|
const again = (b) => {
|
|
618
650
|
const headers = new Headers(res.headers)
|
|
619
651
|
headers.delete("content-length")
|
|
620
652
|
headers.delete("content-encoding")
|
|
653
|
+
headers.set("X-Magpie-Sign-In", "kept")
|
|
621
654
|
return new Response(b, { status: res.status, statusText: res.statusText, headers })
|
|
622
655
|
}
|
|
623
656
|
if (!REFUSED.test(text)) return again(text)
|
|
@@ -635,6 +668,7 @@ async function explained(res) {
|
|
|
635
668
|
headers.set("content-type", "application/json")
|
|
636
669
|
headers.delete("content-length")
|
|
637
670
|
headers.delete("content-encoding")
|
|
671
|
+
headers.set("X-Magpie-Sign-In", "kept")
|
|
638
672
|
return new Response(JSON.stringify({ error: { message: msg, type: "permission_error", code: null } }), { status: res.status, statusText: res.statusText, headers })
|
|
639
673
|
}
|
|
640
674
|
|
|
@@ -642,4 +676,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
642
676
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
643
677
|
|
|
644
678
|
// for tests
|
|
645
|
-
export const _internal = { withSystem, usageOf, desktopHeld, explained, REFUSED_HINT, freeCredits }
|
|
679
|
+
export const _internal = { withSystem, usageOf, desktopHeld, explained, REFUSED_HINT, freeCredits, creditsOf, fresh, SITES }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-workbuddy-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|